Kaynağa Gözat

fixed processing index.txt
the admin panel has been moved to /admin
when accessing /ccd under the user's password, the user's configuration file is returned. User accounts are taken from apache authorization.

root 8 ay önce
ebeveyn
işleme
b9ad0fc924

+ 44 - 0
addons/apache/site.conf

@@ -0,0 +1,44 @@
+<VirtualHost *:80>
+        ServerAdmin webmaster@localhost
+        DocumentRoot /var/www/vpn
+        ServerName vpn.example.com
+
+        ErrorLog ${APACHE_LOG_DIR}/vpn-error.log
+        CustomLog ${APACHE_LOG_DIR}/vpn-access.log combined
+
+        DirectoryIndex index.php
+
+        <Directory "/var/www/vpn">
+            Options -Indexes -FollowSymLinks
+            AllowOverride All
+            Require all granted
+            AddType application/x-httpd-php .php
+        </Directory>
+
+       <Location "/admin">
+            AuthType Basic
+            AuthName "VPN Admin"
+            AuthUserFile /etc/apache2/.htpasswd-admin
+            Require valid-user
+
+            #Require ip 192.168.1.0/24
+            #Satisfy all
+
+            # Запрещаем кеширование авторизации
+            AuthBasicProvider file
+       </Location>
+
+       <Location "/ccd">
+            AuthType Basic
+            AuthName "VPN User"
+            AuthUserFile /etc/apache2/.htpasswd-ccd
+            Require valid-user
+
+            #Require ip 192.168.1.0/24
+            #Satisfy all
+
+            # Запрещаем кеширование авторизации
+            AuthBasicProvider file
+       </Location>
+
+</VirtualHost>

+ 1 - 2
addons/set_perm.sh

@@ -1,8 +1,7 @@
 #!/bin/bash
 
 chown nobody:www-data /etc/openvpn/server/server1/ccd/*
-chmod 464 /etc/openvpn/server/server1/ccd/*
+chmod 664 /etc/openvpn/server/server1/ccd/*
 chmod 644 /etc/openvpn/server/server1/ipp.txt
-chmod 644 /etc/openvpn/server/server1/rsa/pki/index.txt
 
 exit

+ 17 - 0
addons/show_index.sh

@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+show_usage() {
+    echo "Usage: $0 [path_to_index.txt]"
+    exit 1
+}
+
+# Argument handling
+[[ $# -lt 1 ]] && show_usage
+
+[ -e "${1}" ] && cat "${1}"
+
+exit 0

+ 1 - 0
addons/sudoers.d/www-data

@@ -1 +1,2 @@
 www-data ALL=(root)      NOPASSWD: /etc/openvpn/server/show_client_crt.sh
+www-data ALL=(root)      NOPASSWD: /etc/openvpn/server/show_index.sh

+ 1 - 0
config.php → html/admin/config.php

@@ -4,6 +4,7 @@ defined('CONFIG') or die('Direct access not allowed');
 
 define('REQUEST_INTERVAL', 60);
 define('SHOW_CERT_SCRIPT','/etc/openvpn/server/show_client_crt.sh');
+define('SHOW_PKI_INDEX','/etc/openvpn/server/show_index.sh');
 
 // config.php - конфигурация OpenVPN серверов
 return [

+ 22 - 17
functions.php → html/admin/functions.php

@@ -136,23 +136,28 @@ function getAccountList($server) {
     $accounts = [];
 
     // Получаем список из index.txt (неотозванные сертификаты)
-    if (!empty($server['cert_index']) && file_exists($server['cert_index'])) {
-        $index_content = file_get_contents($server['cert_index']);
-        $lines = explode("\n", $index_content);
-
-        foreach ($lines as $line) {
-            if (empty(trim($line))) continue;
-
-            $parts = preg_split('/\s+/', $line);
-            if (count($parts) >= 6 && $parts[0] === 'V') { // Только валидные сертификаты
-                $username = $parts[5];
-                $accounts[$username] = [
-                    "username" => $username,
-                    "ip" => null,
-                    "banned" => isClientBanned($server, $username)
-                ];
-            }
-        }
+    if (!empty($server['cert_index']) && !empty(SHOW_PKI_INDEX) && file_exists(SHOW_PKI_INDEX)) {
+        // Безопасное выполнение скрипта
+	$command = sprintf(
+	    'sudo %s %s 2>&1',
+            escapeshellcmd(SHOW_PKI_INDEX),
+            escapeshellarg($server['cert_index']),
+        );
+	exec($command,  $index_content, $return_var);
+        if ($return_var == 0) {
+            foreach ($index_content as $line) {
+	        if (empty(trim($line))) continue;
+    	        $parts = preg_split('/\s+/', $line);
+        	if (count($parts) >= 1 && $parts[0] === 'V') { // Только валидные сертификаты
+		    $username = ltrim($parts[4], '/CN=');
+                    $accounts[$username] = [
+	                "username" => $username,
+    	                "ip" => null,
+        	        "banned" => isClientBanned($server, $username)
+            	    ];
+        	}
+    	    }
+	}
     }
 
     // Получаем список выданных IP из ipp.txt

+ 6 - 6
get_server_data.php → html/admin/get_server_data.php

@@ -69,9 +69,9 @@ ob_start();
             <?php foreach ($clients as $client): ?>
             <tr class="<?= $client['banned'] ? 'banned' : '' ?>">
                 <td>
-		    <a href="#" onclick="return generateConfig('<?= $server_name ?>', '<?= htmlspecialchars($client['name']) ?>', event)">
-			<?= htmlspecialchars($client['name']) ?>
-		    </a>
+                    <a href="#" onclick="return generateConfig('<?= $server_name ?>', '<?= htmlspecialchars($client['name']) ?>', event)">
+                        <?= htmlspecialchars($client['name']) ?>
+                    </a>
                 </td>
                 <td><?= htmlspecialchars($client['real_ip']) ?></td>
                 <td><?= htmlspecialchars($client['virtual_ip']) ?></td>
@@ -122,9 +122,9 @@ ob_start();
                     ?>
                     <tr>
                         <td>
-			    <a href="#" onclick="return generateConfig('<?= $server_name ?>', '<?= htmlspecialchars($account['username']) ?>', event)">
-			        <?= htmlspecialchars($account['username']) ?>
-			    </a>
+                            <a href="#" onclick="return generateConfig('<?= $server_name ?>', '<?= htmlspecialchars($account['username']) ?>', event)">
+                                <?= htmlspecialchars($account['username']) ?>
+                            </a>
                         </td>
                         <td><?= htmlspecialchars($account['ip'] ?? 'N/A') ?></td>
                         <td>

+ 2 - 1
handle_action.php → html/admin/handle_action.php

@@ -9,7 +9,8 @@ $config_file = __DIR__ . '/config.php';
 if (!file_exists($config_file)) {
     die("Configuration file not found: $config_file");
 }
-$servers = require_once $config_file;
+
+$servers = require $config_file;
 
 // Проверяем AJAX-запрос
 if (empty($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] !== 'XMLHttpRequest') {

+ 2 - 1
index.php → html/admin/index.php

@@ -10,7 +10,8 @@ $config_file = __DIR__ . '/config.php';
 if (!file_exists($config_file)) {
     die("Configuration file not found: $config_file");
 }
-$servers = require_once $config_file;
+
+$servers = require $config_file;
 
 session_start();
 

+ 29 - 0
html/admin/server1.ovpn.template

@@ -0,0 +1,29 @@
+client
+dev tun
+
+<connection>
+remote vpn1.example.com 443 udp
+nobind
+connect-timeout 15
+</connection>
+
+<connection>
+remote vpn2.example.com 443 tcp
+nobind
+connect-timeout 15
+</connection>
+
+resolv-retry infinite
+nobind
+persist-key
+persist-tun
+ns-cert-type server
+
+verb 2
+mute 20
+
+<ca>
+</ca>
+
+<tls-crypt>
+</tls-crypt>

+ 123 - 0
html/ccd/index.php

@@ -0,0 +1,123 @@
+<?php
+//error_reporting(E_ALL);
+//ini_set('display_errors', 1);
+
+define("CONFIG", 1);
+
+// Подключаем конфигурационный файл
+$config_file = __DIR__ . '/../admin/config.php';
+if (!file_exists($config_file)) {
+    die("Configuration file not found: $config_file");
+}
+
+$servers = require $config_file;
+
+// Проверяем авторизацию Apache
+$is_authenticated = false;
+$username = '';
+
+// Получаем username из Apache auth или из GET/POST
+if (isset($_SERVER['PHP_AUTH_USER'])) {
+    // Авторизация через Apache Basic Auth
+    $is_authenticated = true;
+    $username = $_SERVER['PHP_AUTH_USER'];
+} elseif (isset($_SERVER['REMOTE_USER'])) {
+    // Альтернативный способ получения username
+    $is_authenticated = true;
+    $username = $_SERVER['REMOTE_USER'];
+} else {
+        showApacheAuthRequired();
+}
+
+$server_name = $_GET['server'] ?? $_POST['server'] ?? 'server1';
+
+// Если авторизованы через Apache - генерируем конфиг
+if ($is_authenticated && !empty($username)) {
+    $server_name = $_GET['server'] ?? $_POST['server'] ?? 'server1';
+    generateConfig($username, $server_name, $servers);
+    exit;
+}
+
+// Если не авторизованы
+showApacheAuthRequired();
+
+function generateConfig($username, $server_name, $servers) {
+    if (empty($username) || !isset($servers[$server_name])) {
+        die('Invalid parameters');
+    }
+
+    $server = $servers[$server_name];
+    $script_path = SHOW_CERT_SCRIPT;
+    $pki_dir = dirname($server['cert_index']);
+    $template_path = '../admin/'.$server['cfg_template'] ?? '';
+
+    $output = [];
+    if (!empty($pki_dir)) {
+        // Безопасное выполнение скрипта
+        $command = sprintf(
+            'sudo %s %s %s 2>&1',
+            escapeshellcmd($script_path),
+            escapeshellarg($username),
+            escapeshellarg($pki_dir)
+        );
+        exec($command, $output, $return_var);
+        if ($return_var !== 0) {
+            die('Failed to generate config: ' . implode("\n", $output));
+        }
+    }
+
+    // Формируем контент
+    $template_content = file_exists($template_path) && is_readable($template_path)
+        ? file_get_contents($template_path)
+        : die ('Error: Neither template: '.$template_path);
+
+    // Получаем вывод скрипта
+    $script_output = !empty($output) ? implode("\n", $output) : null;
+
+    // Формируем итоговый контент по приоритетам
+    if ($template_content !== null && $script_output !== null) {
+        // Оба источника доступны - объединяем
+        $config_content = $template_content . "\n" . $script_output;
+    } elseif ($template_content !== null) {
+        // Только шаблон доступен
+        $config_content = $template_content;
+    } elseif ($script_output !== null) {
+        // Только вывод скрипта доступен
+        $config_content = $script_output;
+    } else {
+        // Ничего не доступно - ошибка
+        die('Error: Neither template nor script output available');
+    }
+
+    // Прямая отдача контента
+    header('Content-Type: application/octet-stream');
+    header('Content-Disposition: attachment; filename="' . $server['name'].'-'.$username . '.ovpn"');
+    header('Content-Length: ' . strlen($config_content));
+    echo $config_content;
+    exit;
+}
+
+function showApacheAuthRequired() {
+    header('WWW-Authenticate: Basic realm="OpenVPN Config Download"');
+    header('HTTP/1.0 401 Unauthorized');
+    echo '<!DOCTYPE html>
+    <html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <title>Authentication Required</title>
+        <style>
+            body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
+            .info { background: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; margin: 20px 0; }
+        </style>
+    </head>
+    <body>
+        <h2>Authentication Required</h2>
+        <div class="info">
+            <p>This site uses Apache Basic Authentication. Please enter your credentials in the browser authentication dialog.</p>
+            <p>If you don\'t see the login prompt, try refreshing the page or check your browser settings.</p>
+        </div>
+    </body>
+    </html>';
+    exit;
+}

+ 225 - 0
html/index.php

@@ -0,0 +1,225 @@
+<!DOCTYPE html>
+<html lang="ru">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Ваш IP-адрес</title>
+    <meta name="description" content="Здесь Вы можете узнать свой IP-адрeс">
+    <meta name="robots" content="noindex, nofollow, noarchive, nosnippet, notranslate, noimageindex">
+
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            padding: 20px;
+        }
+
+        .container {
+            background: rgba(255, 255, 255, 0.95);
+            backdrop-filter: blur(10px);
+            border-radius: 15px;
+            padding: 2rem;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+            max-width: 700px;
+            width: 100%;
+            text-align: center;
+        }
+
+        h1 {
+            color: #2c3e50;
+            margin-bottom: 1.5rem;
+            font-size: 2rem;
+        }
+
+        .network-info {
+            width: 100%;
+            border-collapse: collapse;
+            margin: 1rem 0;
+            border-radius: 10px;
+            overflow: hidden;
+        }
+
+        .network-info th {
+            background: linear-gradient(135deg, #43AA2E 0%, #2E8B57 100%);
+            color: white;
+            padding: 1rem;
+            font-size: 1.2rem;
+            border: none;
+        }
+
+        .network-info td {
+            background: #f8f9fa;
+            padding: 1.2rem;
+            font-size: 1.1rem;
+            color: #2c3e50;
+            border: 2px solid #43AA2E;
+        }
+
+        .ip-address {
+            font-weight: bold;
+            color: #e74c3c;
+            font-size: 1.3rem;
+        }
+
+        .external-ip {
+            font-weight: bold;
+            color: #3498db;
+            font-size: 1.3rem;
+        }
+
+        .status-message {
+            margin: 1.5rem 0;
+            padding: 1rem;
+            background: #E0EED3;
+            border: 2px solid #43AA2E;
+            border-radius: 8px;
+            color: #2c3e50;
+            font-size: 1.1rem;
+        }
+
+        .external-status {
+            margin: 1.5rem 0;
+            padding: 1rem;
+            background: #D6EAF8;
+            border: 2px solid #3498db;
+            border-radius: 8px;
+            color: #2c3e50;
+            font-size: 1.1rem;
+        }
+
+        .footer {
+            margin-top: 2rem;
+            color: #7f8c8d;
+            font-size: 0.9rem;
+        }
+
+        .loading {
+            color: #7f8c8d;
+            font-style: italic;
+        }
+
+        @media (max-width: 768px) {
+            .container {
+                padding: 1.5rem;
+                margin: 1rem;
+            }
+
+            h1 {
+                font-size: 1.5rem;
+            }
+
+            .network-info th,
+            .network-info td {
+                padding: 0.8rem;
+                font-size: 1rem;
+            }
+
+            .ip-address, .external-ip {
+                font-size: 1.1rem;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>Определение IP-адреса в сети</h1>
+
+        <?php
+
+        function get_user_ip() {
+            if (!empty(getenv("HTTP_CLIENT_IP"))) { return getenv("HTTP_CLIENT_IP"); }
+            if (!empty(getenv("HTTP_X_FORWARDED_FOR"))) { return getenv("HTTP_X_FORWARDED_FOR"); }
+            if (!empty(getenv("REMOTE_ADDR"))) { return getenv("REMOTE_ADDR"); }
+            if (!empty($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; }
+            return 'Не удалось определить';
+        }
+
+        $ip = get_user_ip();
+
+        ?>
+
+        <table class="network-info">
+            <tr>
+                <th>IP-Адрес для этого сервера</th>
+                <td class="ip-address"><?php echo htmlspecialchars($ip); ?></td>
+            </tr>
+            <tr>
+                <th>Ваш IP-адрес для мира</th>
+                <td class="external-ip" id="external-ip">
+                    <span class="loading">Определение...</span>
+                </td>
+            </tr>
+                <td><a href="/admin/" target="_blank">Панель администратора</a></td>
+                <td><a href="/admin/" target="_blank">Уголок пользователя</a></td>
+            </tr>
+        </table>
+
+        <div class="external-status" id="external-status">
+            Определение внешнего IP-адреса...
+        </div>
+
+        <div class="footer">
+            &copy; <?php echo date('Y'); ?> Сервис определения сетевого статуса
+        </div>
+    </div>
+
+    <script>
+        // Функция для получения IP через внешний API
+        function fetchExternalIP() {
+            // Пробуем несколько сервисов на случай недоступности одного из них
+            const services = [
+                'https://api.ipify.org?format=json',
+                'https://ipinfo.io/json',
+                'https://api.myip.com'
+            ];
+
+            let currentService = 0;
+
+            function tryNextService() {
+                if (currentService >= services.length) {
+                    document.getElementById('external-ip').innerHTML = 'Не удалось определить';
+                    document.getElementById('external-status').innerHTML = 'Не удалось определить внешний IP-адрес';
+                    return;
+                }
+
+                fetch(services[currentService])
+                    .then(response => response.json())
+                    .then(data => {
+                        let ip;
+                        if (data.ip) ip = data.ip;
+                        else if (data.query) ip = data.query;
+                        else if (data.ipAddress) ip = data.ipAddress;
+
+                        if (ip) {
+                            document.getElementById('external-ip').innerHTML = ip;
+                            document.getElementById('external-status').innerHTML='Внешний IP-адрес успешно получен через внешний API';
+                        } else {
+                            currentService++;
+                            tryNextService();
+                        }
+                    })
+                    .catch(error => {
+                        console.log('Ошибка получения IP:', error);
+                        currentService++;
+                        tryNextService();
+                    });
+            }
+
+            tryNextService();
+        }
+
+        fetchExternalIP();
+    </script>
+</body>
+</html>

+ 0 - 34
server1.ovpn.template

@@ -1,34 +0,0 @@
-client
-dev tun
-
-<connection>
-remote vpn.example.com 1194 udp
-nobind
-connect-timeout 15
-</connection>
-
-resolv-retry infinite
-
-persist-key
-persist-tun
-
-remote-cert-tls server
-
-verb 3
-mute 20
-
-#cipher AES-256-CBC
-#cipher AES-128-CBC
-#cipher AES-256-GCM
-#cipher AES-128-GCM
-
-#data-ciphers "AES-256-CBC:AES-128-CBC:AES-256-GCM:AES-128-GCM"
-
-#auth sha256
-#auth-nocache
-
-<ca>
------BEGIN CERTIFICATE-----
-...
------END CERTIFICATE-----
-</ca>