Release
This commit is contained in:
207
web/lib/torctl.php
Normal file
207
web/lib/torctl.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?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;
|
||||
}
|
||||
Reference in New Issue
Block a user