Files
rpi-tor-relay-panel/web/lib/torctl.php
2025-11-07 09:47:03 +01:00

208 lines
7.1 KiB
PHP

<?php
function torctl(array $commands): array {
$sock = @stream_socket_client("unix:///run/tor/control", $errno, $errstr, 2.0);
if (!$sock) { throw new Exception("control connect failed: $errstr"); }
stream_set_timeout($sock, 2);
$cookieRaw = @file_get_contents("/run/tor/control.authcookie");
if ($cookieRaw === false) { fclose($sock); throw new Exception("cookie read failed"); }
$cookie = bin2hex($cookieRaw);
fwrite($sock, "AUTHENTICATE $cookie\r\n");
$resp = fgets($sock);
if ($resp === false || strpos($resp, "250") !== 0) { fclose($sock); throw new Exception("auth failed: $resp"); }
$out = [];
foreach ($commands as $c) {
fwrite($sock, rtrim($c)."\r\n");
$buf = "";
while (($line = fgets($sock)) !== false) {
$buf .= $line;
if (rtrim($line) === "250 OK") break;
}
foreach (explode("\n", trim($buf)) as $ln) {
$ln = trim($ln);
if (strpos($ln, "250-") === 0) $ln = substr($ln, 4);
else if (strpos($ln, "250 ") === 0) $ln = substr($ln, 4);
else continue;
if (strpos($ln, "=") !== false) {
[$k,$v] = explode("=", $ln, 2);
$out[trim($k)] = trim($v);
} else {
$out[] = $ln;
}
}
}
fclose($sock);
return $out;
}
function torpanel_conf_path(): string { return "/etc/tor/torrc.d/99-torpanel.conf"; }
function _sanitize_nickname(string $nick): string {
$nick = preg_replace('/[^A-Za-z0-9_-]/', '', $nick);
if ($nick === '') $nick = 'RaspberryRelay';
return substr($nick, 0, 19);
}
function _mbps_to_kb(int $mbps): int {
$mbps = max(1, min($mbps, 1000000));
return (int)max(1, round($mbps * 125));
}
function _kb_to_mbps(float $kb): int {
return (int)max(1, round($kb / 125));
}
function _rate_to_kb(string $val): int {
if (preg_match('/^\s*(\d+(?:\.\d+)?)\s*(KB|MB)?\s*$/i', $val, $m)) {
$n = (float)$m[1];
$u = isset($m[2]) ? strtoupper($m[2]) : 'KB';
if ($u === 'MB') return (int)max(1, round($n * 1000));
return (int)max(1, round($n));
}
return 625; // ~5 Mbps
}
function read_torpanel_conf(): array {
$path = torpanel_conf_path();
$cfg = [
"Nickname" => "RaspberryRelay",
"ContactInfo" => "contact@admin.com",
"ORPort" => "9001",
"BandwidthRate" => "625 KB",
"BandwidthBurst" => "1250 KB",
"AccountingMax" => "100 GB",
"AccountingStart" => "month 1 00:00",
"SocksPort" => "0",
"TransPort" => "0",
"ExitRelay" => "0",
"ControlPort" => "0",
"ControlSocket" => "/run/tor/control",
"CookieAuthentication" => "1",
"CookieAuthFileGroupReadable" => "1",
];
if (!is_readable($path)) return $cfg;
foreach (@file($path) ?: [] as $line) {
if (preg_match('/^\s*(Nickname|ContactInfo|ORPort|BandwidthRate|BandwidthBurst|AccountingMax|AccountingStart|SocksPort|TransPort|ExitRelay|ControlPort|ControlSocket|CookieAuthentication|CookieAuthFileGroupReadable)\s+(.+?)\s*$/', $line, $m)) {
$cfg[$m[1]] = trim($m[2]);
}
}
return $cfg;
}
function torrc_to_ui(array $parsed): array {
$rate_kb = _rate_to_kb((string)($parsed['BandwidthRate'] ?? '625 KB'));
$burst_kb = _rate_to_kb((string)($parsed['BandwidthBurst'] ?? max(1, $rate_kb * 2)));
$cap_gb = 100;
if (preg_match('/^\s*(\d+(?:\.\d+)?)\s*GB\b/i', (string)($parsed['AccountingMax'] ?? ''), $m)) {
$cap_gb = (int)max(1, round((float)$m[1]));
}
$acc_day = 1;
if (preg_match('/month\s+(\d+)\s+\d+:\d+/i', (string)($parsed['AccountingStart'] ?? ''), $m)) {
$acc_day = max(1, min(28, (int)$m[1]));
}
return [
'nickname' => (string)($parsed['Nickname'] ?? 'RaspberryRelay'),
'contact' => (string)($parsed['ContactInfo'] ?? 'contact@admin.com'),
'orport' => (int) ($parsed['ORPort'] ?? 9001),
'rate_mbps' => _kb_to_mbps($rate_kb),
'burst_mbps' => _kb_to_mbps($burst_kb),
'cap_gb' => (int)$cap_gb,
'acc_day' => (int)$acc_day,
];
}
function write_torpanel_conf(array $in): bool {
$nick = isset($in['nickname']) ? (string)$in['nickname'] :
(isset($in['Nickname']) ? (string)$in['Nickname'] : 'RaspberryRelay');
$nick = _sanitize_nickname($nick);
$contact = isset($in['contact']) ? (string)$in['contact'] :
(isset($in['ContactInfo']) ? (string)$in['ContactInfo'] : 'contact@admin.com');
$contact = substr(preg_replace('/[\x00-\x1F\x7F]/', '', trim($contact)), 0, 200);
$orport = (int)($in['orport'] ?? ($in['ORPort'] ?? 9001));
if ($orport < 1 || $orport > 65535) $orport = 9001;
if (isset($in['rate_mbps'])) { $rate_kb = _mbps_to_kb((int)$in['rate_mbps']); }
else { $rate_kb = _rate_to_kb((string)($in['BandwidthRate'] ?? '625 KB')); }
if (isset($in['burst_mbps'])) { $burst_kb = _mbps_to_kb((int)$in['burst_mbps']); }
else { $burst_kb = _rate_to_kb((string)($in['BandwidthBurst'] ?? max(1, $rate_kb*2) . ' KB')); }
if ($burst_kb < $rate_kb) $burst_kb = $rate_kb;
$cap_gb = (int)($in['cap_gb'] ?? 0);
if ($cap_gb <= 0 && isset($in['AccountingMax']) && preg_match('/^\s*(\d+(?:\.\d+)?)\s*GB\b/i', (string)$in['AccountingMax'], $m)) {
$cap_gb = (int)max(1, round((float)$m[1]));
}
if ($cap_gb < 1) $cap_gb = 100;
$acc_day = (int)($in['acc_day'] ?? 0);
if ($acc_day <= 0 && isset($in['AccountingStart']) && preg_match('/month\s+(\d+)\s+\d+:\d+/i', (string)$in['AccountingStart'], $m)) {
$acc_day = (int)$m[1];
}
if ($acc_day < 1 || $acc_day > 28) $acc_day = 1;
$out = <<<EOC
## --- Managed by TorPanel ---
SocksPort 0
TransPort 0
ORPort {$orport}
ExitRelay 0
ExitPolicy reject *:*
Nickname {$nick}
ContactInfo {$contact}
BandwidthRate {$rate_kb} KB
BandwidthBurst {$burst_kb} KB
AccountingMax {$cap_gb} GB
AccountingStart month {$acc_day} 00:00
ControlPort 0
ControlSocket /run/tor/control
CookieAuthentication 1
CookieAuthFileGroupReadable 1
# --- End TorPanel block ---
EOC;
$path = torpanel_conf_path();
$dir = dirname($path);
if (file_exists($path) && is_writable($path)) {
if (file_put_contents($path, $out, LOCK_EX) !== false) {
@chgrp($path, 'www-data'); @chmod($path, 0664);
return true;
}
return false;
}
if (is_dir($dir) && is_writable($dir)) {
$tmp = $path . '.tmp';
if (file_put_contents($tmp, $out) !== false) {
@chgrp($tmp, 'www-data'); @chmod($tmp, 0664);
@rename($tmp, $path);
@chgrp($path, 'www-data'); @chmod($path, 0664);
return true;
}
@is_file($tmp) && @unlink($tmp);
}
if (file_put_contents($path, $out) !== false) {
@chgrp($path, 'www-data'); @chmod($path, 0664);
return true;
}
return false;
}
function config_apply(array $in): bool {
$ok = write_torpanel_conf($in);
if (!$ok) return false;
@exec('sudo /bin/systemctl reload tor 2>/dev/null');
return true;
}