functions.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. <?php
  2. defined('CONFIG') or die('Direct access not allowed');
  3. function canRequestStatus($server) {
  4. if (!isset($_SESSION['last_request_time'][$server['name']])) { return true; }
  5. if (time() - $_SESSION['last_request_time'][$server['name']] >= REQUEST_INTERVAL) { return true; }
  6. return false;
  7. }
  8. function updateLastRequestTime($server) {
  9. $_SESSION['last_request_time'][$server['name']] = time();
  10. }
  11. function openvpnManagementCommand($server, $command) {
  12. if (empty($server['host']) || empty($server['port']) || empty($server['password'])) { return false; }
  13. $mgmt_host = $server['host'];
  14. $mgmt_port = $server['port'];
  15. $mgmt_pass = $server['password'];
  16. $timeout = 5;
  17. $socket = @fsockopen($mgmt_host, $mgmt_port, $errno, $errstr, $timeout);
  18. if (!$socket) {
  19. error_log("OpenVPN management connection failed to {$server['name']}: $errstr ($errno)");
  20. return false;
  21. }
  22. stream_set_timeout($socket, $timeout);
  23. try {
  24. // Читаем приветственное сообщение
  25. $welcome = '';
  26. while (!feof($socket)) {
  27. $line = fgets($socket);
  28. if ($line === false) break;
  29. $welcome .= $line;
  30. if (strpos($welcome, 'ENTER PASSWORD:') !== false) break;
  31. }
  32. // Отправляем пароль
  33. if (@fwrite($socket, "$mgmt_pass\n") === false) {
  34. throw new Exception("Failed to send password");
  35. }
  36. // Ждем подтверждения аутентификации
  37. $authResponse = '';
  38. while (!feof($socket)) {
  39. $line = fgets($socket);
  40. if ($line === false) break;
  41. $authResponse .= $line;
  42. if (strpos($authResponse, 'SUCCESS:') !== false || strpos($authResponse, '>INFO:') !== false) break;
  43. }
  44. // Отправляем команду
  45. if (@fwrite($socket, "$command\n") === false) {
  46. throw new Exception("Failed to send command");
  47. }
  48. // Читаем ответ
  49. $response = '';
  50. $expectedEnd = strpos($command, 'status') !== false ? "END\r\n" : ">";
  51. while (!feof($socket)) {
  52. $line = fgets($socket);
  53. if ($line === false) break;
  54. $response .= $line;
  55. if (strpos($response, $expectedEnd) !== false) break;
  56. }
  57. return $response;
  58. } catch (Exception $e) {
  59. error_log("OpenVPN management error ({$server['name']}): " . $e->getMessage());
  60. return false;
  61. } finally {
  62. @fwrite($socket, "quit\n");
  63. @fclose($socket);
  64. }
  65. }
  66. function getOpenVPNStatus($server) {
  67. // Проверяем, можно ли делать запрос
  68. if (!canRequestStatus($server)) {
  69. // Возвращаем кэшированные данные или пустой массив
  70. return $_SESSION['cached_status'][$server['name']] ?? [];
  71. }
  72. // Обновляем время последнего запроса
  73. updateLastRequestTime($server);
  74. $response = openvpnManagementCommand($server, "status 2");
  75. if (!$response) return $_SESSION['cached_status'][$server['name']] ?? [];
  76. $banned = getBannedClients($server);
  77. $clients = [];
  78. $lines = explode("\n", $response);
  79. $in_client_list = false;
  80. foreach ($lines as $line) {
  81. $line = trim($line);
  82. if (strpos($line, 'HEADER,CLIENT_LIST') === 0) {
  83. $in_client_list = true;
  84. continue;
  85. }
  86. if (strpos($line, 'HEADER,ROUTING_TABLE') === 0) {
  87. $in_client_list = false;
  88. continue;
  89. }
  90. //CLIENT_LIST,Common Name,Real Address,Virtual Address,Virtual IPv6 Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,Client ID,Peer ID,Data Channel Cipher
  91. if ($in_client_list && strpos($line, 'CLIENT_LIST') === 0) {
  92. $parts = explode(',', $line);
  93. if (count($parts) >= 9) {
  94. $clients[] = [
  95. 'name' => $parts[1],
  96. 'real_ip' => $parts[2],
  97. 'virtual_ip' => $parts[3],
  98. 'bytes_received' => formatBytes($parts[5]),
  99. 'bytes_sent' => formatBytes($parts[6]),
  100. 'connected_since' => $parts[7],
  101. 'username' => $parts[8] ?? $parts[1],
  102. 'cipher' => end($parts),
  103. 'banned' => isset($banned[$parts[1]]),
  104. ];
  105. }
  106. }
  107. }
  108. // Кэшируем результат
  109. $_SESSION['cached_status'][$server['name']] = $clients;
  110. return $clients;
  111. }
  112. function get_servers_crt($cert_index) {
  113. // Проверка входных параметров
  114. if (empty($cert_index) || !is_string($cert_index)) {
  115. return false;
  116. }
  117. // // Проверка существования исполняемого файла
  118. // if (empty(SHOW_SERVERS_CRT) || !file_exists(SHOW_SERVERS_CRT) || !is_executable(SHOW_SERVERS_CRT)) {
  119. // error_log('SHOW_SERVERS_CRT is not configured properly', 0);
  120. // return false;
  121. // }
  122. if (empty(SHOW_SERVERS_CRT)) {
  123. error_log('SHOW_SERVERS_CRT is not configured properly', 0);
  124. return false;
  125. }
  126. $command = sprintf(
  127. 'sudo %s %s 2>&1',
  128. escapeshellcmd(SHOW_SERVERS_CRT),
  129. escapeshellarg($cert_index)
  130. );
  131. exec($command, $cert_content, $return_var);
  132. if ($return_var !== 0) {
  133. error_log(sprintf(
  134. 'Command failed: %s (return code: %d, output: %s)',
  135. $command,
  136. $return_var,
  137. implode("\n", $cert_content)
  138. ), 0);
  139. return false;
  140. }
  141. if (empty($cert_content)) {
  142. error_log('Empty certificate content for file: '.$cert_index, 0);
  143. return false;
  144. }
  145. $result = array_fill_keys($cert_content, true);
  146. return $result;
  147. }
  148. function getBannedClients($server) {
  149. // Проверка входных параметров
  150. if (empty($server["ccd"]) || !is_string($server["ccd"])) {
  151. return [];
  152. }
  153. // // Проверка существования исполняемого файла
  154. // if (empty(SHOW_BANNED) || !file_exists(SHOW_BANNED) || !is_executable(SHOW_BANNED)) {
  155. // error_log('SHOW_BANNED is not configured properly', 0);
  156. // return [];
  157. // }
  158. if (empty(SHOW_BANNED)) {
  159. error_log('SHOW_BANNED is not configured properly', 0);
  160. return [];
  161. }
  162. $command = sprintf(
  163. 'sudo %s %s 2>&1',
  164. escapeshellcmd(SHOW_BANNED),
  165. escapeshellarg($server["ccd"])
  166. );
  167. exec($command, $banned_content, $return_var);
  168. if ($return_var !== 0) {
  169. error_log(sprintf(
  170. 'Command failed: %s (return code: %d)',
  171. $command,
  172. $return_var,
  173. ), 0);
  174. return [];
  175. }
  176. if (empty($banned_content)) { return []; }
  177. $result = array_fill_keys($banned_content, true);
  178. return $result;
  179. }
  180. function getClientIPsCCD($server) {
  181. // Проверка входных параметров
  182. if (empty($server["ccd"]) || !is_string($server["ccd"])) {
  183. return [];
  184. }
  185. // // Проверка существования исполняемого файла
  186. // if (empty(GET_IPS_FROM_CCD) || !file_exists(GET_IPS_FROM_CCD) || !is_executable(GET_IPS_FROM_CCD)) {
  187. // error_log('SHOW_BANNED is not configured properly', 0);
  188. // return [];
  189. // }
  190. if (empty(GET_IPS_FROM_CCD)) {
  191. error_log('SHOW_BANNED is not configured properly', 0);
  192. return [];
  193. }
  194. $command = sprintf(
  195. 'sudo %s %s 2>&1',
  196. escapeshellcmd(GET_IPS_FROM_CCD),
  197. escapeshellarg($server["ccd"])
  198. );
  199. exec($command, $ccd_content, $return_var);
  200. if ($return_var !== 0) {
  201. error_log(sprintf(
  202. 'Command failed: %s (return code: %d)',
  203. $command,
  204. $return_var,
  205. ), 0);
  206. return [];
  207. }
  208. if (empty($ccd_content)) { return []; }
  209. $result=[];
  210. foreach ($ccd_content as $line) {
  211. if (empty($line)) { continue; }
  212. list($login, $ip) = explode(' ', trim($line), 2);
  213. $result[$login] = $ip;
  214. }
  215. return $result;
  216. }
  217. function getClientIPsIPP($server) {
  218. // Проверка входных параметров
  219. if (empty($server["ipp_file"]) || !is_string($server["ipp_file"])) {
  220. return [];
  221. }
  222. // // Проверка существования исполняемого файла
  223. // if (empty(GET_IPS_FROM_IPP) || !file_exists(GET_IPS_FROM_IPP) || !is_executable(GET_IPS_FROM_IPP)) {
  224. // error_log('SHOW_BANNED is not configured properly', 0);
  225. // return [];
  226. // }
  227. if (empty(GET_IPS_FROM_IPP)) {
  228. error_log('SHOW_BANNED is not configured properly', 0);
  229. return [];
  230. }
  231. $command = sprintf(
  232. 'sudo %s %s 2>&1',
  233. escapeshellcmd(GET_IPS_FROM_IPP),
  234. escapeshellarg($server["ipp_file"])
  235. );
  236. exec($command, $ipp_content, $return_var);
  237. if ($return_var !== 0) {
  238. error_log(sprintf(
  239. 'Command failed: %s (return code: %d)',
  240. $command,
  241. $return_var,
  242. ), 0);
  243. return [];
  244. }
  245. if (empty($ipp_content)) { return []; }
  246. $result=[];
  247. foreach ($ipp_content as $line) {
  248. if (empty($line)) { continue; }
  249. list($login, $ip) = explode(',', trim($line), 2);
  250. $result[$login] = $ip;
  251. }
  252. return $result;
  253. }
  254. function getAccountList($server) {
  255. $accounts = [];
  256. $banned = getBannedClients($server);
  257. // Получаем список из index.txt (неотозванные сертификаты)
  258. // if (!empty($server['cert_index']) && !empty(SHOW_PKI_INDEX) && file_exists(SHOW_PKI_INDEX)) {
  259. if (!empty($server['cert_index']) && !empty(SHOW_PKI_INDEX)) {
  260. $servers_list = get_servers_crt($server['cert_index']);
  261. // Безопасное выполнение скрипта
  262. $command = sprintf(
  263. 'sudo %s %s 2>&1',
  264. escapeshellcmd(SHOW_PKI_INDEX),
  265. escapeshellarg($server['cert_index']),
  266. );
  267. exec($command, $index_content, $return_var);
  268. if ($return_var == 0) {
  269. foreach ($index_content as $line) {
  270. if (empty(trim($line))) { continue; }
  271. if (preg_match('/\/CN=([^\/]+)/', $line, $matches)) {
  272. $username = trim($matches[1]);
  273. }
  274. if (empty($username)) { continue; }
  275. $revoked = false;
  276. if (preg_match('/^R\s+/',$line)) { $revoked = true; }
  277. if (isset($servers_list[$username])) { continue; }
  278. $accounts[$username] = [
  279. "username" => $username,
  280. "ip" => null,
  281. "banned" => isset($banned[$username]) || $revoked,
  282. "revoked" => $revoked
  283. ];
  284. }
  285. }
  286. }
  287. // Получаем список выданных IP из ipp.txt
  288. // if (!empty($server['ipp_file']) && file_exists($server['ipp_file'])) {
  289. if (!empty($server['ipp_file'])) {
  290. $ipps = getClientIPsIPP($server);
  291. foreach ($ipps as $username => $ip) {
  292. if (!isset($accounts[$username]) && empty($server['cert_index'])) {
  293. $accounts[$username] = [
  294. "username" => $username,
  295. "banned" => isset($banned[$username]),
  296. "ip" => $ip,
  297. "revoked" => false,
  298. ];
  299. }
  300. if (isset($accounts[$username]) and !empty($server['cert_index'])) {
  301. $accounts[$username]["ip"] = $ip;
  302. }
  303. }
  304. }
  305. // Ищем IP-адреса в CCD файлах
  306. if (!empty($server['ccd']) && is_dir($server['ccd'])) {
  307. $ccds = getClientIPsCCD($server);
  308. foreach ($ccds as $username => $ip) {
  309. if (!isset($accounts[$username]) && empty($server['cert_index'])) {
  310. $accounts[$username] = [
  311. "username" => $username,
  312. "banned" => isset($banned[$username]),
  313. "ip" => $ip,
  314. "revoked" => false,
  315. ];
  316. }
  317. if (isset($accounts[$username]) and !empty($server['cert_index'])) {
  318. $accounts[$username]["ip"] = $ip;
  319. }
  320. }
  321. }
  322. return $accounts;
  323. }
  324. function kickClient($server, $client_name) {
  325. return openvpnManagementCommand($server, "kill $client_name");
  326. }
  327. function removeCCD($server, $client_name) {
  328. // if (empty($server["ccd"]) || empty($client_name) || empty(REMOVE_CCD) || !file_exists(REMOVE_CCD)) { return false; }
  329. if (empty($server["ccd"]) || empty($client_name) || empty(REMOVE_CCD)) { return false; }
  330. $script_path = REMOVE_CCD;
  331. $ccd_file = "{$server['ccd']}/$client_name";
  332. $command = sprintf(
  333. 'sudo %s %s 2>&1',
  334. escapeshellcmd($script_path),
  335. escapeshellarg($ccd_file)
  336. );
  337. exec($command, $output, $return_var);
  338. $_SESSION['last_request_time'] = [];
  339. if ($return_var === 0) {
  340. return true;
  341. } else {
  342. return false;
  343. }
  344. }
  345. function unbanClient($server, $client_name) {
  346. // if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT) || !file_exists(BAN_CLIENT)) { return false; }
  347. if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT)) { return false; }
  348. $script_path = BAN_CLIENT;
  349. $ccd_file = "{$server['ccd']}/$client_name";
  350. $command = sprintf(
  351. 'sudo %s %s unban 2>&1',
  352. escapeshellcmd($script_path),
  353. escapeshellarg($ccd_file)
  354. );
  355. exec($command, $output, $return_var);
  356. $_SESSION['last_request_time'] = [];
  357. if ($return_var === 0) {
  358. return true;
  359. } else {
  360. return false;
  361. }
  362. }
  363. function banClient($server, $client_name) {
  364. // if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT) || !file_exists(BAN_CLIENT)) { return false; }
  365. if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT)) { return false; }
  366. $script_path = BAN_CLIENT;
  367. $ccd_file = "{$server['ccd']}/$client_name";
  368. $command = sprintf(
  369. 'sudo %s %s ban 2>&1',
  370. escapeshellcmd($script_path),
  371. escapeshellarg($ccd_file)
  372. );
  373. exec($command, $output, $return_var);
  374. $_SESSION['last_request_time'] = [];
  375. if ($return_var === 0) {
  376. // Кикаем клиента
  377. kickClient($server, $client_name);
  378. return true;
  379. } else {
  380. return false;
  381. }
  382. }
  383. function revokeClient($server, $client_name) {
  384. // if (empty(REVOKE_CRT) || !file_exists(REVOKE_CRT)) {
  385. if (empty(REVOKE_CRT)) {
  386. return banClient($server, $client_name);
  387. }
  388. $script_path = REVOKE_CRT;
  389. $rsa_dir = dirname(dirname($server['cert_index']));
  390. $command = sprintf(
  391. 'sudo %s %s %s %s 2>&1',
  392. escapeshellcmd($script_path),
  393. escapeshellarg('openvpn-server@'.$server['name']),
  394. escapeshellarg($rsa_dir),
  395. escapeshellarg($client_name)
  396. );
  397. exec($command, $output, $return_var);
  398. if ($return_var === 0) {
  399. return true;
  400. } else {
  401. return false;
  402. }
  403. }
  404. function formatBytes($bytes) {
  405. $bytes = (int)$bytes;
  406. if ($bytes <= 0) return '0 B';
  407. $units = ['B', 'KB', 'MB', 'GB', 'TB'];
  408. $pow = floor(log($bytes)/log(1024));
  409. return round($bytes/pow(1024,$pow),2).' '.$units[$pow];
  410. }
  411. function isClientActive($active_clients,$username) {
  412. $active_names = array_column($active_clients, 'name');
  413. if (in_array($username,$active_names)) { return true; }
  414. return false;
  415. }