functions.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  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 get_crt_date_info($server, $client_name) {
  149. $default = [
  150. 'date' => '-',
  151. 'status' => 'unknown',
  152. 'days' => null,
  153. 'valid' => false,
  154. 'html' => '<span class="cert-date error">-</span>'
  155. ];
  156. if (empty($server) || empty($client_name) || empty(SHOW_CRT_DATE)) {
  157. return $default;
  158. }
  159. $pki_dir = dirname($server['cert_index']);
  160. $command = sprintf(
  161. 'sudo %s %s %s 2>&1',
  162. escapeshellcmd(SHOW_CRT_DATE),
  163. escapeshellarg($client_name),
  164. escapeshellarg($pki_dir)
  165. );
  166. exec($command, $output, $return_var);
  167. if ($return_var !== 0 || empty($output)) {
  168. error_log("Cert check failed for $client_name");
  169. return $default;
  170. }
  171. $parts = explode(';', trim($output[0]));
  172. if (count($parts) < 5) {
  173. return $default;
  174. }
  175. $until_str = $parts[2];
  176. $status = $parts[3];
  177. $days = (int)$parts[4];
  178. $timestamp = strtotime($until_str);
  179. if ($timestamp === false) {
  180. return $default;
  181. }
  182. $formatted_date = date('Y-m-d', $timestamp);
  183. $is_valid = ($status === 'VALID');
  184. // Генерируем HTML
  185. if (!$is_valid) {
  186. $html = '<span class="cert-date expired">' . $formatted_date . ' (expired ' . $days . 'd ago)</span>';
  187. } else {
  188. if ($days < 7) {
  189. $html = '<span class="cert-date expiring-soon">' . $formatted_date . ' (' . $days . 'd left)</span>';
  190. } else {
  191. $html = '<span class="cert-date valid">' . $formatted_date . ' (' . $days . 'd left)</span>';
  192. }
  193. }
  194. return [
  195. 'date' => $formatted_date,
  196. 'status' => $status,
  197. 'days' => $days,
  198. 'valid' => $is_valid,
  199. 'html' => $html
  200. ];
  201. }
  202. function getBannedClients($server) {
  203. // Проверка входных параметров
  204. if (empty($server["ccd"]) || !is_string($server["ccd"])) {
  205. return [];
  206. }
  207. // // Проверка существования исполняемого файла
  208. // if (empty(SHOW_BANNED) || !file_exists(SHOW_BANNED) || !is_executable(SHOW_BANNED)) {
  209. // error_log('SHOW_BANNED is not configured properly', 0);
  210. // return [];
  211. // }
  212. if (empty(SHOW_BANNED)) {
  213. error_log('SHOW_BANNED is not configured properly', 0);
  214. return [];
  215. }
  216. $command = sprintf(
  217. 'sudo %s %s 2>&1',
  218. escapeshellcmd(SHOW_BANNED),
  219. escapeshellarg($server["ccd"])
  220. );
  221. exec($command, $banned_content, $return_var);
  222. if ($return_var !== 0) {
  223. error_log(sprintf(
  224. 'Command failed: %s (return code: %d)',
  225. $command,
  226. $return_var,
  227. ), 0);
  228. return [];
  229. }
  230. if (empty($banned_content)) { return []; }
  231. $result = array_fill_keys($banned_content, true);
  232. return $result;
  233. }
  234. function getClientIPsCCD($server) {
  235. // Проверка входных параметров
  236. if (empty($server["ccd"]) || !is_string($server["ccd"])) {
  237. return [];
  238. }
  239. // // Проверка существования исполняемого файла
  240. // if (empty(GET_IPS_FROM_CCD) || !file_exists(GET_IPS_FROM_CCD) || !is_executable(GET_IPS_FROM_CCD)) {
  241. // error_log('SHOW_BANNED is not configured properly', 0);
  242. // return [];
  243. // }
  244. if (empty(GET_IPS_FROM_CCD)) {
  245. error_log('SHOW_BANNED is not configured properly', 0);
  246. return [];
  247. }
  248. $command = sprintf(
  249. 'sudo %s %s 2>&1',
  250. escapeshellcmd(GET_IPS_FROM_CCD),
  251. escapeshellarg($server["ccd"])
  252. );
  253. exec($command, $ccd_content, $return_var);
  254. if ($return_var !== 0) {
  255. error_log(sprintf(
  256. 'Command failed: %s (return code: %d)',
  257. $command,
  258. $return_var,
  259. ), 0);
  260. return [];
  261. }
  262. if (empty($ccd_content)) { return []; }
  263. $result=[];
  264. foreach ($ccd_content as $line) {
  265. if (empty($line)) { continue; }
  266. list($login, $ip) = explode(' ', trim($line), 2);
  267. $result[$login] = $ip;
  268. }
  269. return $result;
  270. }
  271. function getClientIPsIPP($server) {
  272. // Проверка входных параметров
  273. if (empty($server["ipp_file"]) || !is_string($server["ipp_file"])) {
  274. return [];
  275. }
  276. // // Проверка существования исполняемого файла
  277. // if (empty(GET_IPS_FROM_IPP) || !file_exists(GET_IPS_FROM_IPP) || !is_executable(GET_IPS_FROM_IPP)) {
  278. // error_log('SHOW_BANNED is not configured properly', 0);
  279. // return [];
  280. // }
  281. if (empty(GET_IPS_FROM_IPP)) {
  282. error_log('SHOW_BANNED is not configured properly', 0);
  283. return [];
  284. }
  285. $command = sprintf(
  286. 'sudo %s %s 2>&1',
  287. escapeshellcmd(GET_IPS_FROM_IPP),
  288. escapeshellarg($server["ipp_file"])
  289. );
  290. exec($command, $ipp_content, $return_var);
  291. if ($return_var !== 0) {
  292. error_log(sprintf(
  293. 'Command failed: %s (return code: %d)',
  294. $command,
  295. $return_var,
  296. ), 0);
  297. return [];
  298. }
  299. if (empty($ipp_content)) { return []; }
  300. $result=[];
  301. foreach ($ipp_content as $line) {
  302. if (empty($line)) { continue; }
  303. list($login, $ip) = explode(',', trim($line), 2);
  304. $result[$login] = $ip;
  305. }
  306. return $result;
  307. }
  308. function getAccountList($server) {
  309. $accounts = [];
  310. $banned = getBannedClients($server);
  311. // Получаем список из index.txt
  312. if (!empty($server['cert_index']) && !empty(SHOW_PKI_INDEX)) {
  313. $servers_list = get_servers_crt($server['cert_index']);
  314. $command = sprintf(
  315. 'sudo %s %s 2>&1',
  316. escapeshellcmd(SHOW_PKI_INDEX),
  317. escapeshellarg($server['cert_index'])
  318. );
  319. exec($command, $index_content, $return_var);
  320. if ($return_var == 0) {
  321. foreach ($index_content as $line) {
  322. $line = trim($line);
  323. if (empty($line)) { continue; }
  324. // Парсим строку index.txt
  325. $cert_info = parse_index_line($line);
  326. $username = $cert_info['username'];
  327. if (empty($username)) { continue; }
  328. if (isset($servers_list[$username])) { continue; }
  329. // Парсим дату окончания
  330. $cert_date = '-';
  331. $days_left = null;
  332. $valid = $cert_info['is_valid'];
  333. $revoked = $cert_info['is_revoked'];
  334. $expired = $cert_info['is_expired'];
  335. if (!empty($cert_info['expires'])) {
  336. $timestamp = parse_openvpn_date($cert_info['expires']);
  337. if ($timestamp) {
  338. $cert_date = date('Y-m-d', $timestamp);
  339. $days_left = ceil(($timestamp - time()) / 86400);
  340. // Корректируем статус если истек по дате, но не помечен как E
  341. if ($valid && $days_left < 0) {
  342. $expired = true;
  343. $valid = false;
  344. $days_left = abs($days_left);
  345. }
  346. }
  347. }
  348. $accounts[$username] = [
  349. "username" => $username,
  350. "ip" => null,
  351. "banned" => isset($banned[$username]) || $revoked || $expired,
  352. "revoked" => $revoked,
  353. "expired" => $expired,
  354. "valid" => $valid && !$revoked && !$expired,
  355. "cert_date" => $cert_date,
  356. "days_left" => $days_left,
  357. "serial" => $cert_info['serial'],
  358. "status_code" => $cert_info['status'],
  359. "revoke_date" => $cert_info['revoked_date']
  360. ];
  361. }
  362. } else {
  363. error_log("Failed to execute SHOW_PKI_INDEX: " . implode("\n", $index_content));
  364. }
  365. }
  366. // Получаем список выданных IP из ipp.txt
  367. if (!empty($server['ipp_file'])) {
  368. $ipps = getClientIPsIPP($server);
  369. foreach ($ipps as $username => $ip) {
  370. if (!isset($accounts[$username]) && empty($server['cert_index'])) {
  371. $accounts[$username] = getDefaultAccount($username, isset($banned[$username]));
  372. }
  373. if (isset($accounts[$username]) && !empty($server['cert_index'])) {
  374. $accounts[$username]["ip"] = $ip;
  375. }
  376. }
  377. }
  378. // Ищем IP-адреса в CCD файлах
  379. if (!empty($server['ccd']) && is_dir($server['ccd'])) {
  380. $ccds = getClientIPsCCD($server);
  381. foreach ($ccds as $username => $ip) {
  382. if (!isset($accounts[$username]) && empty($server['cert_index'])) {
  383. $accounts[$username] = getDefaultAccount($username, isset($banned[$username]));
  384. }
  385. if (isset($accounts[$username]) && !empty($server['cert_index'])) {
  386. $accounts[$username]["ip"] = $ip;
  387. }
  388. }
  389. }
  390. return $accounts;
  391. }
  392. function parse_index_line($line) {
  393. // Разбиваем по табуляции (стандартный разделитель index.txt)
  394. $parts = explode("\t", trim($line));
  395. if (count($parts) < 6) {
  396. // Если табуляция не сработала, пробуем разбить по пробелам с учетом пустых полей
  397. $parts = preg_split('/\s+/', $line);
  398. }
  399. // Структура index.txt:
  400. // [0] - Status (V/R/E)
  401. // [1] - Expiration date (YYMMDDHHMMSSZ)
  402. // [2] - Revocation date (пусто или дата)
  403. // [3] - Serial number (hex)
  404. // [4] - Distinguished Name (например: "unknown /CN=username")
  405. // [5] - может быть еще одно поле если DN содержит пробелы
  406. $status = $parts[0] ?? '';
  407. $expires = $parts[1] ?? '';
  408. $revoked = $parts[2] ?? '';
  409. $serial = $parts[3] ?? '';
  410. // Последнее поле - это DN, может содержать пробелы
  411. // Объединяем все оставшиеся части
  412. $dn = implode(' ', array_slice($parts, 4));
  413. // Извлекаем username из DN
  414. $username = '';
  415. if (preg_match('/\/CN=([^\/\s]+)/', $dn, $matches)) {
  416. $username = trim($matches[1]);
  417. }
  418. return [
  419. 'status' => $status,
  420. 'expires' => $expires,
  421. 'revoked_date' => $revoked ?: null,
  422. 'serial' => $serial,
  423. 'dn' => $dn,
  424. 'username' => $username,
  425. 'is_valid' => ($status === 'V'),
  426. 'is_revoked' => ($status === 'R'),
  427. 'is_expired' => ($status === 'E')
  428. ];
  429. }
  430. // Вспомогательная функция для создания записи по умолчанию
  431. function getDefaultAccount($username, $is_banned = false) {
  432. return [
  433. "username" => $username,
  434. "ip" => null,
  435. "banned" => $is_banned,
  436. "revoked" => false,
  437. "expired" => false,
  438. "valid" => true,
  439. "cert_date" => '-',
  440. "days_left" => null,
  441. "serial" => null,
  442. "status_code" => 'V',
  443. "revoke_date" => null
  444. ];
  445. }
  446. // Функция парсинга дат OpenVPN (YYMMDDHHMMSSZ)
  447. function parse_openvpn_date($date_str) {
  448. if (empty($date_str) || $date_str === 'unknown') {
  449. return false;
  450. }
  451. // Формат: YYMMDDHHMMSSZ (например: 351119103201Z)
  452. // 35 = 2035 год, 11 = ноябрь, 19 = день, 10:32:01
  453. if (preg_match('/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z$/', $date_str, $matches)) {
  454. $year = 2000 + intval($matches[1]);
  455. $month = intval($matches[2]);
  456. $day = intval($matches[3]);
  457. $hour = intval($matches[4]);
  458. $minute = intval($matches[5]);
  459. $second = intval($matches[6]);
  460. // Проверка валидности даты
  461. if (checkdate($month, $day, $year)) {
  462. return mktime($hour, $minute, $second, $month, $day, $year);
  463. }
  464. }
  465. return false;
  466. }
  467. function kickClient($server, $client_name) {
  468. return openvpnManagementCommand($server, "kill $client_name");
  469. }
  470. function removeCCD($server, $client_name) {
  471. // if (empty($server["ccd"]) || empty($client_name) || empty(REMOVE_CCD) || !file_exists(REMOVE_CCD)) { return false; }
  472. if (empty($server["ccd"]) || empty($client_name) || empty(REMOVE_CCD)) { return false; }
  473. $script_path = REMOVE_CCD;
  474. $ccd_file = "{$server['ccd']}/$client_name";
  475. $command = sprintf(
  476. 'sudo %s %s 2>&1',
  477. escapeshellcmd($script_path),
  478. escapeshellarg($ccd_file)
  479. );
  480. exec($command, $output, $return_var);
  481. $_SESSION['last_request_time'] = [];
  482. if ($return_var === 0) {
  483. return true;
  484. } else {
  485. return false;
  486. }
  487. }
  488. function unbanClient($server, $client_name) {
  489. // if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT) || !file_exists(BAN_CLIENT)) { return false; }
  490. if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT)) { return false; }
  491. $script_path = BAN_CLIENT;
  492. $ccd_file = "{$server['ccd']}/$client_name";
  493. $command = sprintf(
  494. 'sudo %s %s unban 2>&1',
  495. escapeshellcmd($script_path),
  496. escapeshellarg($ccd_file)
  497. );
  498. exec($command, $output, $return_var);
  499. $_SESSION['last_request_time'] = [];
  500. if ($return_var === 0) {
  501. return true;
  502. } else {
  503. return false;
  504. }
  505. }
  506. function banClient($server, $client_name) {
  507. // if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT) || !file_exists(BAN_CLIENT)) { return false; }
  508. if (empty($server["ccd"]) || empty($client_name) || empty(BAN_CLIENT)) { return false; }
  509. $script_path = BAN_CLIENT;
  510. $ccd_file = "{$server['ccd']}/$client_name";
  511. $command = sprintf(
  512. 'sudo %s %s ban 2>&1',
  513. escapeshellcmd($script_path),
  514. escapeshellarg($ccd_file)
  515. );
  516. exec($command, $output, $return_var);
  517. $_SESSION['last_request_time'] = [];
  518. if ($return_var === 0) {
  519. // Кикаем клиента
  520. kickClient($server, $client_name);
  521. return true;
  522. } else {
  523. return false;
  524. }
  525. }
  526. function revokeClient($server, $client_name) {
  527. // if (empty(REVOKE_CRT) || !file_exists(REVOKE_CRT)) {
  528. if (empty(REVOKE_CRT)) {
  529. return banClient($server, $client_name);
  530. }
  531. $script_path = REVOKE_CRT;
  532. $rsa_dir = dirname(dirname($server['cert_index']));
  533. $command = sprintf(
  534. 'sudo %s %s %s %s 2>&1',
  535. escapeshellcmd($script_path),
  536. escapeshellarg('openvpn-server@'.$server['name']),
  537. escapeshellarg($rsa_dir),
  538. escapeshellarg($client_name)
  539. );
  540. exec($command, $output, $return_var);
  541. if ($return_var === 0) {
  542. return true;
  543. } else {
  544. return false;
  545. }
  546. }
  547. function formatBytes($bytes) {
  548. $bytes = (int)$bytes;
  549. if ($bytes <= 0) return '0 B';
  550. $units = ['B', 'KB', 'MB', 'GB', 'TB'];
  551. $pow = floor(log($bytes)/log(1024));
  552. return round($bytes/pow(1024,$pow),2).' '.$units[$pow];
  553. }
  554. function isClientActive($active_clients,$username) {
  555. $active_names = array_column($active_clients, 'name');
  556. if (in_array($username,$active_names)) { return true; }
  557. return false;
  558. }
  559. function process_create_user($servers, $server_name = null, $username = null, $force = false) {
  560. // Если параметры не переданы (явно null), берем из $_POST
  561. if ($server_name === null) {
  562. $server_name = $_POST['server'] ?? '';
  563. }
  564. if ($username === null) {
  565. $username = trim($_POST['username'] ?? '');
  566. }
  567. // Проверка наличия скрипта создания
  568. if (empty(CREATE_CRT)) {
  569. send_json_response(false, 'Create certificate script not configured');
  570. return true;
  571. }
  572. if (empty($username) || !isset($servers[$server_name]) || empty($servers[$server_name]['cert_index'])) {
  573. send_json_response(false, 'Invalid parameters');
  574. return true;
  575. }
  576. // Нормализация имени пользователя
  577. // mb_internal_encoding('UTF-8');
  578. // $username = mb_strtolower($username);
  579. // Проверка на пробельные символы
  580. if (preg_match('/\s/', $username)) {
  581. send_json_response(false, 'Username cannot contain spaces');
  582. return true;
  583. }
  584. // Проверка на специальные символы
  585. if (!preg_match('/^[a-zA-Z0-9_-]+$/', $username)) {
  586. send_json_response(false, 'Username can only contain letters, numbers, underscores and hyphens');
  587. return true;
  588. }
  589. // Проверка длины имени
  590. if (strlen($username) < 3 || strlen($username) > 32) {
  591. send_json_response(false, 'Username must be between 3 and 32 characters');
  592. return true;
  593. }
  594. $server = $servers[$server_name];
  595. $rsa_dir = dirname(dirname($server['cert_index']));
  596. // Выполнение команды создания пользователя
  597. if (!$force) {
  598. $command = sprintf(
  599. 'sudo %s %s %s 2>&1',
  600. escapeshellcmd(CREATE_CRT),
  601. escapeshellarg($rsa_dir),
  602. escapeshellarg($username)
  603. );
  604. } else {
  605. $command = sprintf(
  606. 'sudo %s %s %s --force 2>&1',
  607. escapeshellcmd(CREATE_CRT),
  608. escapeshellarg($rsa_dir),
  609. escapeshellarg($username)
  610. );
  611. }
  612. error_log("Creating user: $username on server: $server_name");
  613. error_log("Command: $command");
  614. exec($command, $output, $return_var);
  615. if ($return_var === 0) {
  616. // Логируем успешное создание
  617. error_log("User $username created successfully on server $server_name");
  618. send_json_response(true, 'User created successfully');
  619. } else {
  620. $error_message = implode("\n", $output);
  621. error_log("Failed to create user $username: $error_message");
  622. send_json_response(false, 'Failed to create user: ' . $error_message);
  623. }
  624. return true;
  625. }
  626. // Вспомогательная функция для отправки JSON ответов
  627. function send_json_response($success, $message, $data = []) {
  628. header('Content-Type: application/json');
  629. echo json_encode(array_merge([
  630. 'success' => $success,
  631. 'message' => $message
  632. ], $data));
  633. exit;
  634. }