feat: ssh auth, protocol management, and cleanup
This commit is contained in:
+292
-48
@@ -7,8 +7,10 @@ use Endroid\QrCode\Label\Label;
|
||||
use Endroid\QrCode\Label\LabelAlignment;
|
||||
use Endroid\QrCode\Encoding\Encoding;
|
||||
|
||||
class QrUtil {
|
||||
public static function pngBase64(string $text, int $size = 300, int $margin = 1, string $label = 'Amnezia QR (old)') : string {
|
||||
class QrUtil
|
||||
{
|
||||
public static function pngBase64(string $text, int $size = 300, int $margin = 1, string $label = 'Amnezia QR (old)'): string
|
||||
{
|
||||
// Try to load Composer autoload if not yet loaded
|
||||
if (!class_exists(QrCode::class)) {
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
@@ -53,11 +55,13 @@ class QrUtil {
|
||||
throw new RuntimeException('QR library not available');
|
||||
}
|
||||
|
||||
private static function urlsafe_b64_encode(string $bytes): string {
|
||||
private static function urlsafe_b64_encode(string $bytes): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
public static function encodeOldPayloadFromJson(string $jsonText): string {
|
||||
public static function encodeOldPayloadFromJson(string $jsonText): string
|
||||
{
|
||||
$json = self::normalizeJson($jsonText);
|
||||
// Old format uses zlib (gzcompress) with header [version, compressed_len, uncompressed_len]
|
||||
$compressed = gzcompress($json, 9);
|
||||
@@ -71,13 +75,15 @@ class QrUtil {
|
||||
return self::urlsafe_b64_encode($header . $compressed);
|
||||
}
|
||||
|
||||
public static function encodeOldPayloadFromConf(string $confText): string {
|
||||
public static function encodeOldPayloadFromConf(string $confText): string
|
||||
{
|
||||
$payload = self::buildOldEnvelopeFromConf($confText);
|
||||
return self::encodeOldPayloadFromJson(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private static function resolveServerDescription(?string $endpointHost): string {
|
||||
$desc = (string)($endpointHost ?? '');
|
||||
private static function resolveServerDescription(?string $endpointHost): string
|
||||
{
|
||||
$desc = (string) ($endpointHost ?? '');
|
||||
try {
|
||||
$cfgPath = __DIR__ . '/config.php';
|
||||
$dbPath = __DIR__ . '/Database.php';
|
||||
@@ -98,21 +104,32 @@ class QrUtil {
|
||||
return $desc;
|
||||
}
|
||||
|
||||
private static function buildOldEnvelopeFromConf(string $conf): array {
|
||||
$endpointHost = null; $endpointPort = null; $mtu = null; $dns = []; $keepAlive = null;
|
||||
$privKey = null; $pubKeyServer = null; $psk = null; $address = null; $allowedIps = [];
|
||||
public static function parseWireGuardConfig(string $conf): array
|
||||
{
|
||||
$endpointHost = null;
|
||||
$endpointPort = null;
|
||||
$mtu = null;
|
||||
$dns = [];
|
||||
$keepAlive = null;
|
||||
$privKey = null;
|
||||
$pubKeyServer = null;
|
||||
$psk = null;
|
||||
$address = null;
|
||||
$allowedIps = [];
|
||||
foreach (explode("\n", $conf) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') { continue; }
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (stripos($line, 'Endpoint') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
if (preg_match('/^\[?([^\]]+)\]?:([0-9]{2,5})$/', $v, $m)) {
|
||||
$endpointHost = $m[1];
|
||||
$endpointPort = (int)$m[2];
|
||||
$endpointPort = (int) $m[2];
|
||||
}
|
||||
} elseif (stripos($line, 'MTU') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$mtu = (int)$v;
|
||||
$mtu = (int) $v;
|
||||
} elseif (stripos($line, 'DNS') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$dns = array_map('trim', preg_split('/[,\s]+/', $v));
|
||||
@@ -133,13 +150,19 @@ class QrUtil {
|
||||
$allowedIps = array_map('trim', preg_split('/[,\s]+/', $v));
|
||||
} elseif (stripos($line, 'PersistentKeepalive') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$keepAlive = (int)$v;
|
||||
$keepAlive = (int) $v;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$endpointPort) { $endpointPort = 51820; }
|
||||
if (!$mtu) { $mtu = 1280; }
|
||||
if (!$keepAlive) { $keepAlive = 25; }
|
||||
if (!$endpointPort) {
|
||||
$endpointPort = 51820;
|
||||
}
|
||||
if (!$mtu) {
|
||||
$mtu = 1280;
|
||||
}
|
||||
if (!$keepAlive) {
|
||||
$keepAlive = 25;
|
||||
}
|
||||
$dns1 = $dns[0] ?? '1.1.1.1';
|
||||
$dns2 = $dns[1] ?? '1.0.0.1';
|
||||
|
||||
@@ -155,9 +178,15 @@ class QrUtil {
|
||||
|
||||
// Collect obfuscation params from conf if present
|
||||
$params = [
|
||||
'H1' => null, 'H2' => null, 'H3' => null, 'H4' => null,
|
||||
'Jc' => null, 'Jmin' => null, 'Jmax' => null,
|
||||
'S1' => null, 'S2' => null,
|
||||
'H1' => null,
|
||||
'H2' => null,
|
||||
'H3' => null,
|
||||
'H4' => null,
|
||||
'Jc' => null,
|
||||
'Jmin' => null,
|
||||
'Jmax' => null,
|
||||
'S1' => null,
|
||||
'S2' => null,
|
||||
];
|
||||
foreach (explode("\n", $conf) as $line) {
|
||||
$line = trim($line);
|
||||
@@ -171,27 +200,173 @@ class QrUtil {
|
||||
|
||||
// Build last_config JSON object (stringified, pretty-printed)
|
||||
$lastConfigObj = [
|
||||
'H1' => (string)($params['H1'] ?? ''),
|
||||
'H2' => (string)($params['H2'] ?? ''),
|
||||
'H3' => (string)($params['H3'] ?? ''),
|
||||
'H4' => (string)($params['H4'] ?? ''),
|
||||
'Jc' => (string)($params['Jc'] ?? ''),
|
||||
'Jmax' => (string)($params['Jmax'] ?? ''),
|
||||
'Jmin' => (string)($params['Jmin'] ?? ''),
|
||||
'S1' => (string)($params['S1'] ?? ''),
|
||||
'S2' => (string)($params['S2'] ?? ''),
|
||||
'H1' => (string) ($params['H1'] ?? ''),
|
||||
'H2' => (string) ($params['H2'] ?? ''),
|
||||
'H3' => (string) ($params['H3'] ?? ''),
|
||||
'H4' => (string) ($params['H4'] ?? ''),
|
||||
'Jc' => (string) ($params['Jc'] ?? ''),
|
||||
'Jmax' => (string) ($params['Jmax'] ?? ''),
|
||||
'Jmin' => (string) ($params['Jmin'] ?? ''),
|
||||
'S1' => (string) ($params['S1'] ?? ''),
|
||||
'S2' => (string) ($params['S2'] ?? ''),
|
||||
'allowed_ips' => $allowedIps ?: ['0.0.0.0/0', '::/0'],
|
||||
'clientId' => $clientPubKey ?: '',
|
||||
'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string)($address ?? '')),
|
||||
'client_priv_key' => (string)($privKey ?? ''),
|
||||
'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string) ($address ?? '')),
|
||||
'client_priv_key' => (string) ($privKey ?? ''),
|
||||
'client_pub_key' => $clientPubKey ?: '',
|
||||
'config' => $conf,
|
||||
'hostName' => (string)($endpointHost ?? ''),
|
||||
'mtu' => (string)$mtu,
|
||||
'persistent_keep_alive' => (string)$keepAlive,
|
||||
'hostName' => (string) ($endpointHost ?? ''),
|
||||
'mtu' => (string) $mtu,
|
||||
'persistent_keep_alive' => (string) $keepAlive,
|
||||
'port' => $endpointPort,
|
||||
'psk_key' => (string)($psk ?? ''),
|
||||
'server_pub_key' => (string)($pubKeyServer ?? ''),
|
||||
'psk_key' => (string) ($psk ?? ''),
|
||||
'server_pub_key' => (string) ($pubKeyServer ?? ''),
|
||||
];
|
||||
|
||||
$serverDesc = self::resolveServerDescription($endpointHost);
|
||||
|
||||
$vars = [
|
||||
'last_config_json' => json_encode($lastConfigObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
|
||||
'port' => (string) $endpointPort,
|
||||
'description' => $serverDesc,
|
||||
'dns1' => $dns1,
|
||||
'dns2' => $dns2,
|
||||
'hostName' => $endpointHost,
|
||||
'client_pub_key' => $clientPubKey,
|
||||
'client_priv_key' => $privKey,
|
||||
'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string) ($address ?? '')),
|
||||
'psk_key' => $psk,
|
||||
'server_pub_key' => $pubKeyServer,
|
||||
'mtu' => $mtu,
|
||||
'persistent_keep_alive' => $keepAlive,
|
||||
'config' => $conf,
|
||||
];
|
||||
|
||||
// Add params to vars
|
||||
foreach ($params as $k => $v) {
|
||||
$vars[$k] = (string) ($v ?? '');
|
||||
}
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
private static function buildOldEnvelopeFromConf(string $conf): array
|
||||
{
|
||||
$endpointHost = null;
|
||||
$endpointPort = null;
|
||||
$mtu = null;
|
||||
$dns = [];
|
||||
$keepAlive = null;
|
||||
$privKey = null;
|
||||
$pubKeyServer = null;
|
||||
$psk = null;
|
||||
$address = null;
|
||||
$allowedIps = [];
|
||||
foreach (explode("\n", $conf) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') {
|
||||
continue;
|
||||
}
|
||||
if (stripos($line, 'Endpoint') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
if (preg_match('/^\[?([^\]]+)\]?:([0-9]{2,5})$/', $v, $m)) {
|
||||
$endpointHost = $m[1];
|
||||
$endpointPort = (int) $m[2];
|
||||
}
|
||||
} elseif (stripos($line, 'MTU') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$mtu = (int) $v;
|
||||
} elseif (stripos($line, 'DNS') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$dns = array_map('trim', preg_split('/[,\s]+/', $v));
|
||||
} elseif (stripos($line, 'PrivateKey') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$privKey = $v;
|
||||
} elseif (stripos($line, 'PublicKey') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$pubKeyServer = $v;
|
||||
} elseif (stripos($line, 'PresharedKey') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$psk = $v;
|
||||
} elseif (stripos($line, 'Address') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$address = $v;
|
||||
} elseif (stripos($line, 'AllowedIPs') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$allowedIps = array_map('trim', preg_split('/[,\s]+/', $v));
|
||||
} elseif (stripos($line, 'PersistentKeepalive') === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$keepAlive = (int) $v;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$endpointPort) {
|
||||
$endpointPort = 51820;
|
||||
}
|
||||
if (!$mtu) {
|
||||
$mtu = 1280;
|
||||
}
|
||||
if (!$keepAlive) {
|
||||
$keepAlive = 25;
|
||||
}
|
||||
$dns1 = $dns[0] ?? '1.1.1.1';
|
||||
$dns2 = $dns[1] ?? '1.0.0.1';
|
||||
|
||||
// Derive client public key if sodium available
|
||||
$clientPubKey = '';
|
||||
if ($privKey && function_exists('sodium_crypto_scalarmult_base')) {
|
||||
$bin = base64_decode($privKey, true);
|
||||
if ($bin !== false && strlen($bin) === 32) {
|
||||
$pub = sodium_crypto_scalarmult_base($bin);
|
||||
$clientPubKey = base64_encode($pub);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect obfuscation params from conf if present
|
||||
$params = [
|
||||
'H1' => null,
|
||||
'H2' => null,
|
||||
'H3' => null,
|
||||
'H4' => null,
|
||||
'Jc' => null,
|
||||
'Jmin' => null,
|
||||
'Jmax' => null,
|
||||
'S1' => null,
|
||||
'S2' => null,
|
||||
];
|
||||
foreach (explode("\n", $conf) as $line) {
|
||||
$line = trim($line);
|
||||
foreach (array_keys($params) as $k) {
|
||||
if (stripos($line, $k) === 0 && strpos($line, '=') !== false) {
|
||||
[, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$params[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build last_config JSON object (stringified, pretty-printed)
|
||||
$lastConfigObj = [
|
||||
'H1' => (string) ($params['H1'] ?? ''),
|
||||
'H2' => (string) ($params['H2'] ?? ''),
|
||||
'H3' => (string) ($params['H3'] ?? ''),
|
||||
'H4' => (string) ($params['H4'] ?? ''),
|
||||
'Jc' => (string) ($params['Jc'] ?? ''),
|
||||
'Jmax' => (string) ($params['Jmax'] ?? ''),
|
||||
'Jmin' => (string) ($params['Jmin'] ?? ''),
|
||||
'S1' => (string) ($params['S1'] ?? ''),
|
||||
'S2' => (string) ($params['S2'] ?? ''),
|
||||
'allowed_ips' => $allowedIps ?: ['0.0.0.0/0', '::/0'],
|
||||
'clientId' => $clientPubKey ?: '',
|
||||
'client_ip' => preg_replace('/\/(\d{1,2})$/', '', (string) ($address ?? '')),
|
||||
'client_priv_key' => (string) ($privKey ?? ''),
|
||||
'client_pub_key' => $clientPubKey ?: '',
|
||||
'config' => $conf,
|
||||
'hostName' => (string) ($endpointHost ?? ''),
|
||||
'mtu' => (string) $mtu,
|
||||
'persistent_keep_alive' => (string) $keepAlive,
|
||||
'port' => $endpointPort,
|
||||
'psk_key' => (string) ($psk ?? ''),
|
||||
'server_pub_key' => (string) ($pubKeyServer ?? ''),
|
||||
];
|
||||
|
||||
$serverDesc = self::resolveServerDescription($endpointHost);
|
||||
@@ -202,17 +377,17 @@ class QrUtil {
|
||||
[
|
||||
// awg first, then container (as in the working QR)
|
||||
'awg' => [
|
||||
'H1' => (string)($params['H1'] ?? ''),
|
||||
'H2' => (string)($params['H2'] ?? ''),
|
||||
'H3' => (string)($params['H3'] ?? ''),
|
||||
'H4' => (string)($params['H4'] ?? ''),
|
||||
'Jc' => (string)($params['Jc'] ?? ''),
|
||||
'Jmax' => (string)($params['Jmax'] ?? ''),
|
||||
'Jmin' => (string)($params['Jmin'] ?? ''),
|
||||
'S1' => (string)($params['S1'] ?? ''),
|
||||
'S2' => (string)($params['S2'] ?? ''),
|
||||
'H1' => (string) ($params['H1'] ?? ''),
|
||||
'H2' => (string) ($params['H2'] ?? ''),
|
||||
'H3' => (string) ($params['H3'] ?? ''),
|
||||
'H4' => (string) ($params['H4'] ?? ''),
|
||||
'Jc' => (string) ($params['Jc'] ?? ''),
|
||||
'Jmax' => (string) ($params['Jmax'] ?? ''),
|
||||
'Jmin' => (string) ($params['Jmin'] ?? ''),
|
||||
'S1' => (string) ($params['S1'] ?? ''),
|
||||
'S2' => (string) ($params['S2'] ?? ''),
|
||||
'last_config' => json_encode($lastConfigObj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
|
||||
'port' => (string)$endpointPort,
|
||||
'port' => (string) $endpointPort,
|
||||
'transport_proto' => 'udp',
|
||||
],
|
||||
'container' => 'amnezia-awg',
|
||||
@@ -227,9 +402,78 @@ class QrUtil {
|
||||
return $envelope;
|
||||
}
|
||||
|
||||
private static function normalizeJson(string $text): string {
|
||||
private static function normalizeJson(string $text): string
|
||||
{
|
||||
$decoded = json_decode($text, true);
|
||||
if (!is_array($decoded)) throw new InvalidArgumentException('Invalid JSON');
|
||||
if (!is_array($decoded))
|
||||
throw new InvalidArgumentException('Invalid JSON');
|
||||
return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public static function encodeXrayPayload(string $host, int $port, string $clientId, string $description = '', ?array $reality = null): string
|
||||
{
|
||||
$desc = $description !== '' ? $description : self::resolveServerDescription($host);
|
||||
$clientCfg = [
|
||||
'log' => ['loglevel' => 'error'],
|
||||
'inbounds' => [
|
||||
[
|
||||
'listen' => '127.0.0.1',
|
||||
'port' => 10808,
|
||||
'protocol' => 'socks',
|
||||
'settings' => ['udp' => true]
|
||||
]
|
||||
],
|
||||
'outbounds' => [
|
||||
[
|
||||
'protocol' => 'vless',
|
||||
'settings' => [
|
||||
'vnext' => [
|
||||
[
|
||||
'address' => $host,
|
||||
'port' => $port,
|
||||
'users' => [
|
||||
[
|
||||
'id' => $clientId,
|
||||
'flow' => ($reality && isset($reality['publicKey']) && $reality['publicKey'] !== '') ? 'xtls-rprx-vision' : null,
|
||||
'encryption' => 'none'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
'streamSettings' => [
|
||||
'network' => 'tcp',
|
||||
'security' => ($reality && isset($reality['publicKey']) && $reality['publicKey'] !== '') ? 'reality' : 'none',
|
||||
'realitySettings' => ($reality && isset($reality['publicKey']) && $reality['publicKey'] !== '') ? [
|
||||
'fingerprint' => 'chrome',
|
||||
'serverName' => (string) ($reality['serverName'] ?? $host),
|
||||
'publicKey' => (string) $reality['publicKey'],
|
||||
'shortId' => (string) ($reality['shortId'] ?? ''),
|
||||
'spiderX' => ''
|
||||
] : null
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$envelope = [
|
||||
'containers' => [
|
||||
[
|
||||
'container' => 'amnezia-xray',
|
||||
'xray' => [
|
||||
'last_config' => json_encode($clientCfg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT),
|
||||
'port' => (string) $port,
|
||||
'transport_proto' => 'tcp'
|
||||
]
|
||||
]
|
||||
],
|
||||
'defaultContainer' => 'amnezia-xray',
|
||||
'description' => $desc,
|
||||
'dns1' => '1.1.1.1',
|
||||
'dns2' => '1.0.0.1',
|
||||
'hostName' => $host,
|
||||
];
|
||||
|
||||
return self::encodeOldPayloadFromJson(json_encode($envelope, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user