Compare commits

..

19 Commits

Author SHA1 Message Date
mercury e2f84b0ee7 update version 2024-11-21 00:39:07 +04:00
mercury b394e769c6 роутинг для openconnect 2024-11-21 00:24:50 +04:00
mercury 7d8dc1dd2d duplicating the list of subnets for openconnect 2024-11-20 20:59:25 +04:00
mercury 34b1de5132 import telegram, gcore, cloudflare ip 2024-11-20 20:58:17 +04:00
mercury 12b5d84afc IP analyzer takes into account subnets 2024-11-20 17:12:08 +04:00
mercury c3d90ada83 addruleset and additional tags 2024-11-20 15:55:10 +04:00
mercury dbe3f290ea commands reset and backup 2024-11-20 15:46:03 +04:00
mercury b56b48e080 override ignore 2024-11-20 15:45:02 +04:00
mercury 21b7a4816f fix bot dump when telegram logs are empty 2024-11-18 00:31:53 +04:00
mercury 51ecb815ba fix domain installation on first launch 2024-11-17 19:32:54 +04:00
mercury 66cee61d41 allow telegram ip 2024-11-17 15:08:01 +04:00
mercury 66225917fe fix ip menu 2024-11-16 16:45:36 +04:00
mercury be227f6a33 update version 2024-11-16 01:13:53 +04:00
mercury 37d392a240 update backup 2024-11-16 01:10:47 +04:00
mercury 565573cfc2 fix notify update 2024-11-16 00:51:09 +04:00
mercury 3813f43b92 update bot from notify 2024-11-16 00:45:30 +04:00
mercury f34427c13c fix notification about new version 2024-11-16 00:42:22 +04:00
mercury c2219e3657 ability to add your IPs to blocklist/whitelist 2024-11-16 00:34:22 +04:00
mercury 0d7426e785 improve analyze ip 2024-11-16 00:11:19 +04:00
11 changed files with 491 additions and 191 deletions
+4 -1
View File
@@ -22,4 +22,7 @@ update/*
override.env
override.html
override.php
docker-compose.override.yml
docker-compose.override.yml
backup.json
app/webapp/override/
.rest
+13
View File
@@ -0,0 +1,13 @@
<?php
require __DIR__ . '/timezone.php';
require __DIR__ . '/bot.php';
require __DIR__ . '/config.php';
require __DIR__ . '/i18n.php';
if ($c['debug']) {
require __DIR__ . '/debug.php';
}
$bot = new Bot($c['key'], $i);
echo $bot->export();
+434 -174
View File
@@ -12,6 +12,7 @@ class Bot
public $dns;
public $mtu;
public $logs;
public $reg;
public function __construct($key, $i18n)
{
@@ -35,6 +36,22 @@ class Bot
'upstream_access',
'xray',
];
$this->reg = '~' . implode('|', [
'GET /ws(?:.+)? HTTP',
'GET /adguard/(?:.+)? HTTP',
'GET /webapp(?:.+)? HTTP',
'GET /pac(?:.+)? HTTP',
'GET \.well-known(?:.+)? HTTP',
'GET /v2ray(?:.+)? HTTP',
'GET /dns-query(?:.+)? HTTP',
'GET / HTTP',
'GET /tlgrm(?:.+)? HTTP',
'GET /jsoneditor.min.css HTTP',
'GET /jsoneditor.min.js HTTP',
'GET /jquery-3.7.1.min.js HTTP',
'GET /img/jsoneditor-icons.svg HTTP',
'GET /favicon.ico HTTP',
]) . '~';
}
public function input()
@@ -146,13 +163,21 @@ class Bot
case preg_match('~^/mirror$~', $this->input['message'], $m):
$this->menu('mirror');
break;
case preg_match('~^/mainOutbound$~', $this->input['callback'], $m):
$this->mainOutbound();
break;
case preg_match('~^/importIps (.+)$~', $this->input['callback'], $m):
$this->importIps($m[1]);
break;
case preg_match('~^/switchBanIp$~', $this->input['callback'], $m):
$this->switchBanIp();
break;
case preg_match('~^/searchLogs (.+)$~', $this->input['message'], $m):
case preg_match('~^/searchLogs (.+)$~', $this->input['callback'], $m):
$this->searchLogs($m[1]);
break;
case preg_match('~^/searchLogs (.+?)(?:\s(.+?))?(?:\s(.+?))?(?:\s(.+?))?$~', $this->input['callback'], $m):
$this->searchLogs($m[1], $m[2], $m[3], $m[4]);
break;
case preg_match('~^/switchSilence$~', $this->input['callback'], $m):
$this->switchSilence();
break;
@@ -168,9 +193,8 @@ class Bot
case preg_match('~^/ports$~', $this->input['callback'], $m):
$this->ports();
break;
case preg_match('~^/ip$~', $this->input['message'], $m):
case preg_match('~^/analysisIp$~', $this->input['callback'], $m):
$this->analysisIp();
case preg_match('~^/analysisIp(?:\s(\d+))?$~', $this->input['callback'], $m):
$this->analysisIp($m[1] ?: 0);
break;
case preg_match('~^/ipMenu$~', $this->input['callback'], $m):
$this->ipMenu();
@@ -184,16 +208,19 @@ class Bot
case preg_match('~^/cleanLogs (.+?)(?:\s(1))?$~', $this->input['callback'], $m):
$this->cleanLogs($m[1], $m[2]);
break;
case preg_match('~^/allowIp (\d+\.\d+\.\d+\.\d+) (\d+)(?:\s(\d+))?$~', $this->input['callback'], $m):
case preg_match('~^/allowIp (.+?) (\d+)(?:\s(\d+))?$~', $this->input['callback'], $m):
$this->allowIp($m[1], $m[2], $m[3]);
break;
case preg_match('~^/searchIp (.+)$~', $this->input['callback'], $m):
$this->searchIp($m[1]);
break;
case preg_match('~^/denyIp (.+?)(?:\s(\d)\s(\d+?)\s(\d))?$~', $this->input['callback'], $m):
case preg_match('~^/searchSuspiciousIp (.+)$~', $this->input['callback'], $m):
$this->searchSuspiciousIp($m[1]);
break;
case preg_match('~^/denyIp (.+?)(?:\s(.+?)\s(\d+?)\s(\d))?$~', $this->input['callback'], $m):
$this->denyIp($m[1], $m[2], $m[3], $m[4]);
break;
case preg_match('~^/whiteIp (.+?)(?:\s(\d)\s(\d+?)\s(\d))?$~', $this->input['callback'], $m):
case preg_match('~^/whiteIp (.+?)(?:\s(.+?)\s(\d+?)\s(\d))?$~', $this->input['callback'], $m):
$this->whiteIp($m[1], $m[2], $m[3], $m[4]);
break;
case preg_match('~^/adgFillAllowedClients(?: (\d+))?$~', $this->input['callback'], $m):
@@ -298,13 +325,13 @@ class Bot
case preg_match('~^/defaultMTU (?P<arg>\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
$this->defaultMTU(...explode('_', $m['arg']));
break;
case preg_match('~^/subnet (?P<arg>-?\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
case preg_match('~^/subnet (?P<arg>-?\d+(?:_-?\d+)?(?:_\d)?)$~', $this->input['callback'], $m):
$this->subnet(...explode('_', $m['arg']));
break;
case preg_match('~^/subnetAdd (?P<arg>-?\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
case preg_match('~^/subnetAdd (?P<arg>-?\d+(?:_-?\d+)?(?:_-?\d+)?)$~', $this->input['callback'], $m):
$this->subnetAdd(...explode('_', $m['arg']));
break;
case preg_match('~^/subnetDelete (?P<arg>-?\d+(?:_-?\d+)?(?:_-?\d+)?)$~', $this->input['callback'], $m):
case preg_match('~^/subnetDelete (?P<arg>-?\d+(?:_-?\d+)?(?:_-?\d+)?(?:_-?\d+)?)$~', $this->input['callback'], $m):
$this->subnetDelete(...explode('_', $m['arg']));
break;
case preg_match('~^/addSubnets (?P<arg>-?\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
@@ -579,7 +606,7 @@ class Bot
$this->addOverrideHtml();
break;
case preg_match('~^/export$~', $this->input['callback'], $m):
$this->exportManual();
$this->pinBackup();
break;
case preg_match('~^/import$~', $this->input['callback'], $m):
$this->import();
@@ -1165,27 +1192,26 @@ class Bot
try {
$pac = $this->getPacConf();
if (!empty($pac['autoscan'])) {
$r = $this->analysisIp(1);
$r = $this->analysisIp(return: 1);
require __DIR__ . '/config.php';
if (!empty($c['admin']) && (empty($this->time3) || ((time() - $this->time3) > $pac['autoscan_timeout']))) {
$this->time3 = time();
if (!empty($r)) {
foreach ($r as $k => $v) {
$tmp = array_unique($v);
foreach ($tmp as $i) {
$t[$i]++;
foreach ($v as $i) {
$t[$i['title']][$k] = 1;
}
}
foreach ($t as $k => $v) {
$text .= "\n$v $k";
$text .= "\n" . count($v) . " $k";
}
if (!empty($pac['autodeny'])) {
$this->denyIp(array_keys($r));
$ban = count(array_keys($r));
foreach (array_keys($r) as $v) {
$ips[] = [[
'text' => "logs $v",
'callback_data' => "/searchIp $v",
'text' => $v,
'callback_data' => "/searchLogs $v",
]];
}
}
@@ -1203,12 +1229,13 @@ class Bot
}
}
} catch (Exception $e) {
file_put_contents('/logs/php_error', $e->getMessage());
}
}
public function checkBackup($delta)
{
$c = $this->getPacConf();
$c = $this->getPacConf();
if (!empty($c['backup'])) {
$now = strtotime(date('Y-m-d H:i:s'));
[$start, $period] = explode('/', $c['backup']);
@@ -1221,9 +1248,6 @@ class Bot
&& $now - $start >= 0
&& (($now - $start) % $period < $delta)
) {
if (!empty($c['pinbackup'])) {
$this->pinAdmin($c['pinbackup'], 1);
}
$this->pinBackup();
}
}
@@ -1245,12 +1269,19 @@ class Bot
}
}
public function pinBackup()
public function pinBackup($file = false)
{
require __DIR__ . '/config.php';
$conf = $this->getPacConf();
$bot = preg_replace('~[\W]~iu', '_', $this->request('getMyName', [])['result']['name']);
$conf['pinbackup'] = $this->upload("{$bot}_export_" . date('d_m_Y_H_i') . '.json', $this->export(), $c['admin'][0])['result']['message_id'];
$conf = $this->getPacConf();
$bot = preg_replace('~[\W]~iu', '_', $this->request('getMyName', [])['result']['name']);
$json = $this->export();
if (!empty($file)) {
file_put_contents($file, $json);
}
if (!empty($conf['pinbackup'])) {
$this->pinAdmin($conf['pinbackup'], 1);
}
$conf['pinbackup'] = $this->upload("{$bot}_export_" . date('d_m_Y_H_i') . '.json', $json, $c['admin'][0])['result']['message_id'];
$this->setPacConf($conf);
$this->pinAdmin($conf['pinbackup']);
}
@@ -1273,9 +1304,13 @@ class Bot
$this->send($v, implode("\n", $diff), 0, [
[
[
'text' => 'changelog',
'text' => 'changelog',
'web_app' => ['url' => "https://raw.githubusercontent.com/mercurykd/vpnbot/$b/version"],
]
],
[
'text' => $this->i18n('update bot'),
'callback_data' => "/applyupdatebot",
],
]
]);
}
@@ -1434,16 +1469,6 @@ class Bot
return json_encode($conf, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
public function exportManual($file = false)
{
$json = $this->export();
if (!empty($file)) {
file_put_contents($file, $json);
}
$bot = preg_replace('~[\W]~iu', '_', $this->request('getMyName', [])['result']['name']);
return $this->upload("{$bot}_export_" . date('d_m_Y_H_i') . '.json', $json);
}
public function import()
{
$r = $this->send(
@@ -2564,9 +2589,9 @@ DNS-over-HTTPS with IP:
default:
$r = $this->send(
$this->input['chat'],
"@{$this->input['username']} list domains separated by commas",
"@{$this->input['username']} list separated by commas",
$this->input['message_id'],
reply: 'list domains separated by commas',
reply: 'list separated by commas',
);
break;
}
@@ -2588,7 +2613,11 @@ DNS-over-HTTPS with IP:
if (!empty($domains)) {
$conf = $this->getPacConf();
foreach ($domains as $k => $v) {
$conf[$type][in_array($type, ['rulessetlist', 'packagelist', 'processlist']) ? trim($v) : idn_to_ascii(trim($v))] = true;
if (in_array($type, ['white', 'deny'])) {
$conf[$type][] = $v;
} else {
$conf[$type][in_array($type, ['rulessetlist', 'packagelist', 'processlist']) ? trim($v) : idn_to_ascii(trim($v))] = true;
}
}
ksort($conf[$type]);
$this->setPacConf($conf);
@@ -2627,6 +2656,11 @@ DNS-over-HTTPS with IP:
$this->xrayUpdateRules();
$this->xtlsrulesset();
break;
case 'white':
case 'deny':
$this->syncDeny();
$this->denyList(0, $type == 'white' ? 1 : 0);
break;
}
}
@@ -3078,7 +3112,7 @@ DNS-over-HTTPS with IP:
$this->menu('client', "{$client}_$page");
}
public function subnetAdd($wgpage, $page)
public function subnetAdd($wgpage, $page, $openconnect)
{
$r = $this->send(
$this->input['chat'],
@@ -3090,11 +3124,11 @@ DNS-over-HTTPS with IP:
'start_message' => $this->input['message_id'],
'start_callback' => $this->input['callback_id'],
'callback' => 'subnetSave',
'args' => [$wgpage, $page],
'args' => [$wgpage, $page, $openconnect],
];
}
public function subnetSave($text, $wgpage, $page)
public function subnetSave($text, $wgpage, $page, $openconnect)
{
$c = $this->getPacConf();
$subnets = explode(',', $text);
@@ -3103,15 +3137,42 @@ DNS-over-HTTPS with IP:
$this->setPacConf($c);
$page = floor(count($c['subnets']) / $this->limit);
}
$this->subnet($wgpage, $page);
if (!empty($openconnect)) {
$this->ocservRoute();
}
$this->subnet($wgpage, $page, $openconnect);
}
public function subnetDelete($wgpage, $k, $page = 0)
public function subnetDelete($wgpage, $k, $page = 0, $openconnect = 0)
{
$c = $this->getPacConf();
unset($c['subnets'][$k]);
$this->setPacConf($c);
$this->subnet($wgpage, $page);
if (!empty($openconnect)) {
$this->ocservRoute();
}
$this->subnet($wgpage, $page, $openconnect);
}
public function ocservRoute()
{
$p = $this->getPacConf();
$c = file_get_contents('/config/ocserv.conf');
$t = preg_replace('~^route[^\n]+~sm', '', $c);
if (!empty($p['subnets'])) {
foreach ($p['subnets'] as $v) {
if (preg_match('~^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}~', $v)) {
$t .= "route = $v";
$flag = true;
}
}
if (empty($flag)) {
$t .= 'route = default';
}
} else {
$t .= 'route = default';
}
$this->restartOcserv($t);
}
public function calc()
@@ -3167,10 +3228,10 @@ DNS-over-HTTPS with IP:
}
}
public function subnet($wgpage = 0, $page = 0, $count = 5)
public function subnet($wgpage = 0, $page = 0, $openconnect = 0)
{
$count = $this->limit;
$text = "Menu -> Wireguard -> " . $this->i18n('listSubnet') . "\n";
$text = 'Menu -> ' . ($openconnect ? 'Openconnect' : 'Wireguard') . ' -> ' . $this->i18n('listSubnet') . "\n";
$data[] = [
[
'text' => $this->i18n('calc'),
@@ -3180,7 +3241,7 @@ DNS-over-HTTPS with IP:
$data[] = [
[
'text' => $this->i18n('add'),
'callback_data' => "/subnetAdd {$wgpage}_$page",
'callback_data' => "/subnetAdd {$wgpage}_{$page}_$openconnect",
],
];
$subnets = $this->getPacConf()['subnets'];
@@ -3193,7 +3254,7 @@ DNS-over-HTTPS with IP:
$data[] = [
[
'text' => $this->i18n('delete') . " $v",
'callback_data' => "/subnetDelete {$wgpage}_{$k}_$page",
'callback_data' => "/subnetDelete {$wgpage}_{$k}_{$page}_$openconnect",
],
];
}
@@ -3201,11 +3262,11 @@ DNS-over-HTTPS with IP:
$data[] = [
[
'text' => '<<',
'callback_data' => "/subnet {$wgpage}_" . ($page - 1 >= 0 ? $page - 1 : $all - 1),
'callback_data' => "/subnet {$wgpage}_" . ($page - 1 >= 0 ? $page - 1 : $all - 1) . ($openconnect ? '_1' : ''),
],
[
'text' => '>>',
'callback_data' => "/subnet {$wgpage}_" . ($page < $all - 1 ? $page + 1 : 0),
'callback_data' => "/subnet {$wgpage}_" . ($page < $all - 1 ? $page + 1 : 0) . ($openconnect ? '_1' : ''),
]
];
}
@@ -3213,7 +3274,7 @@ DNS-over-HTTPS with IP:
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/menu wg $wgpage",
'callback_data' => $openconnect ? '/menu oc' : "/menu wg $wgpage",
],
];
$this->update(
@@ -3766,7 +3827,7 @@ DNS-over-HTTPS with IP:
[$data] = $this->listPac('includelist', $page, 'xtlsproxy');
$data[] = [
[
'text' => 'set to ' . ($p['domains_outbound'] ? 'proxy' : 'direct'),
'text' => 'set to ' . ($p['domains_outbound'] ? ($p['outbound'] ?: 'proxy') : 'direct'),
'callback_data' => "/domainsOutbound",
],
];
@@ -3824,7 +3885,7 @@ DNS-over-HTTPS with IP:
$p = $this->getPacConf();
$data[] = [
[
'text' => 'set to ' . ($p['app_outbound'] ? 'proxy' : 'direct'),
'text' => 'set to ' . ($p['app_outbound'] ? ($p['outbound'] ?: 'proxy') : 'direct'),
'callback_data' => "/appOutbound",
],
];
@@ -3850,7 +3911,7 @@ DNS-over-HTTPS with IP:
$p = $this->getPacConf();
$data[] = [
[
'text' => 'set to ' . ($p['process_outbound'] ? 'proxy' : 'direct'),
'text' => 'set to ' . ($p['process_outbound'] ? ($p['outbound'] ?: 'proxy') : 'direct'),
'callback_data' => "/processOutbound",
],
];
@@ -4281,27 +4342,47 @@ DNS-over-HTTPS with IP:
);
}
public function analysisIp($return = false)
public function suspicious($regexp, $file, $ip, $title, $reverse = false)
{
if ($r = fopen($file, 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (preg_match('~(\d+\.\d+\.\d+\.\d+)~', $l, $m)) {
if ($reverse xor preg_match($regexp, $l)) {
if (is_array($ip)) {
if (empty($ip[$m[1]])) {
$ret[$m[1]][] = [
'title' => $title,
'log' => $l,
];
}
} else {
if ($ip == $m[1]) {
$ret[$m[1]][] = [
'title' => $title,
'log' => $l,
];
}
}
}
}
}
fclose($r);
}
return $ret ?: [];
}
public function analysisIp(int $page = 0, $return = false)
{
$pac = $this->getPacConf();
foreach (array_merge($pac['white'] ?: [], $pac['deny'] ?: [], [
'10.10.0.1' ,'10.10.1.1' ,
'10.10.0.2' ,'10.10.1.2' ,
'10.10.0.3' ,'10.10.1.3' ,
'10.10.0.4' ,'10.10.1.4' ,
'10.10.0.5' ,'10.10.1.5' ,
'10.10.0.6' ,'10.10.1.6' ,
'10.10.0.7' ,'10.10.1.7' ,
'10.10.0.8' ,'10.10.1.8' ,
'10.10.0.9' ,'10.10.1.9' ,
'10.10.0.10','10.10.1.10',
'10.10.0.11','10.10.1.11',
'10.10.0.12','10.10.1.12',
'10.10.0.13','10.10.1.13',
'10.10.0.14','10.10.1.14',
'10.10.0.15','10.10.1.15',
]) as $v) {
$xr[$v] = true;
foreach (array_merge($pac['white'] ?: [], $pac['deny'] ?: [], ['10.10.0.0/23']) as $v) {
if (!empty(preg_match('~^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/(\d{1,2})~', $v, $m))) {
for ($i = ip2long($m[1]); $i < ip2long($m[1]) + pow(2, 32 - $m[2]) - 1; $i++) {
$xr[long2ip($i)] = true;
}
} else {
$xr[$v] = true;
}
}
if ($r = fopen('/logs/nginx_tlgrm_access', 'r')) {
while (feof($r) === false) {
@@ -4322,98 +4403,102 @@ DNS-over-HTTPS with IP:
fclose($r);
}
if ($r = fopen('/logs/upstream_access', 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (preg_match('~(\d+\.\d+\.\d+\.\d+).+200\s\d+\s0$~', $l, $m)) {
if (empty($xr[$m[1]])) {
$ip[$m[1]][] = 'possibly a Reality Degenerate';
}
}
}
fclose($r);
}
$reg = [
'GET /ws(?:.+)? HTTP',
'GET /adguard/(?:.+)? HTTP',
'GET /webapp(?:.+)? HTTP',
'GET /pac(?:.+)? HTTP',
'GET \.well-known(?:.+)? HTTP',
'GET /v2ray(?:.+)? HTTP',
'GET /dns-query(?:.+)? HTTP',
'GET / HTTP',
'GET /tlgrm(?:.+)? HTTP',
'GET /jsoneditor.min.css HTTP',
'GET /jsoneditor.min.js HTTP',
'GET /jquery-3.7.1.min.js HTTP',
'GET /img/jsoneditor-icons.svg HTTP',
'GET /favicon.ico HTTP',
$t = [
$this->suspicious('~\d+\.\d+\.\d+\.\d+.+200\s\d+\s0$~', '/logs/upstream_access', $xr, 'possibly a Reality Degenerate'),
$this->suspicious($this->reg, '/logs/nginx_default_access', $xr, 'possibly a scanner', true),
$this->suspicious($this->reg, '/logs/nginx_domain_access', $xr, 'possibly a scanner', true),
];
if ($r = fopen('/logs/nginx_default_access', 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (!preg_match('~' . implode('|', $reg) . '~', $l)) {
if (preg_match('~(\d+\.\d+\.\d+\.\d+)~', $l, $m)) {
if (empty($xr[$m[1]])) {
$ip[$m[1]][] = 'possibly a scanner';
}
}
}
$ip = [];
foreach ($t as $r) {
foreach ($r as $k => $v) {
$ip[$k] = $v;
}
fclose($r);
}
if ($r = fopen('/logs/nginx_domain_access', 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (!preg_match('~' . implode('|', $reg) . '~', $l)) {
if (preg_match('~(\d+\.\d+\.\d+\.\d+)~', $l, $m)) {
if (empty($xr[$m[1]])) {
$ip[$m[1]][] = 'possibly a scanner';
}
}
}
}
fclose($r);
if (!empty($return)) {
return $ip;
}
if (!empty($ip)) {
if (!empty($return)) {
return $ip;
}
foreach ($ip as $k => $v) {
$file .= "/searchLogs $k\n";
$data[] = [
[
'text' => $k,
'callback_data' => "/searchLogs $k analysisIp $page 0",
]
];
}
$all = (int) ceil(count($data) / $this->limit);
$page = min($page, $all - 1);
$page = $page < 0 ? $all - 1 : $page;
$data = array_slice($data ?: [], $page * $this->limit, $this->limit);
if ($all > 1) {
$data[] = [
[
'text' => '<<',
'callback_data' => "/analysisIp " . ($page - 1 >= 0 ? $page - 1 : $all - 1),
],
[
'text' => '>>',
'callback_data' => "/analysisIp " . ($page < $all - 1 ? $page + 1 : 0),
]
];
}
$this->sendFile($this->input['from'], new CURLStringFile($file, 'analyze_ip_' . date('Y_m_d_H_i_s')));
} else {
$this->answer($this->input['callback_id'], 'empty');
}
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/ipMenu",
],
];
$this->update($this->input['from'], $this->input['message_id'], count($ip) ?: 'empty', $data);
}
public function searchLogs($search)
public function searchLogs($search, $fun = false, $page = 0, $white = 0)
{
if (preg_match('~^\d+\.\d+\.\d+\.\d+$~', $search)) {
$info = file_get_contents("https://ipinfo.io/$search/json", context: stream_context_create(['http' => ['timeout' => 2]]));
$this->send($this->input['from'], "$search\n<pre>$info</pre>", button: [[
$text = "$search\n<pre>$info</pre>";
$data[] = [
[
'text' => $this->i18n('block'),
'callback_data' => "/denyIp $search",
'callback_data' => "/denyIp $search" . ($fun ? " $fun $page $white" : ''),
],
[
'text' => $this->i18n('ignore'),
'callback_data' => "/whiteIp $search",
'callback_data' => "/whiteIp $search" . ($fun ? " $fun $page $white" : ''),
],
];
$data[] = [
[
'text' => $this->i18n('logs'),
'text' => $this->i18n('all logs'),
'callback_data' => "/searchIp $search",
],
[
'text' => $this->i18n('clean logs'),
'text' => $this->i18n('suspicious log'),
'callback_data' => "/searchSuspiciousIp $search",
],
];
$data[] = [
[
'text' => $this->i18n("clean logs $search"),
'callback_data' => "/cleanLogs $search",
],
]]);
];
if (!empty($fun)) {
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/$fun $page" . ($white ? " $white" : ''),
],
];
$this->update($this->input['from'], $this->input['message_id'], $text, button: $data);
} else {
if (empty($this->input['callback_id'])) {
$this->delete($this->input['from'], $this->input['message_id']);
}
$this->send($this->input['from'], $text, button: $data);
}
}
}
@@ -4448,20 +4533,105 @@ DNS-over-HTTPS with IP:
}
}
public function searchSuspiciousIp($ip)
{
$t = [
$this->suspicious('~\d+\.\d+\.\d+\.\d+.+200\s\d+\s0$~', '/logs/upstream_access', $ip, 'possibly a Reality Degenerate'),
$this->suspicious($this->reg, '/logs/nginx_default_access', $ip, 'possibly a scanner', true),
$this->suspicious($this->reg, '/logs/nginx_domain_access', $ip, 'possibly a scanner', true),
];
foreach ($t as $r) {
if (!empty($r)) {
foreach ($r as $v) {
foreach ($v as $k) {
$logs[$k['title']][] = $k['log'];
}
}
}
}
if (!empty($logs)) {
foreach ($logs as $k => $v) {
$head= "$k:\n";
$t = array_chunk($v, 10);
foreach ($t as $j) {
$text = "$head<pre>";
foreach ($j as $i) {
$text .= htmlspecialchars($i, ENT_HTML5, 'UTF-8');
}
$text .= '</pre>';
$this->send($this->input['from'], $text, $this->input['message_id']);
}
}
} else {
$this->answer($this->input['callback_id'], 'empty');
}
}
public function importIps($type)
{
switch ($type) {
case 'telegram':
$r = file_get_contents('https://core.telegram.org/resources/cidr.txt');
if (!empty($r)) {
$domains = explode("\n", $r);
}
break;
case 'gcore':
$r = json_decode(file_get_contents('https://api.gcore.com/cdn/public-ip-list'), true);
if (!empty($r['addresses'])) {
$domains = $r['addresses'];
}
break;
case 'cloudflare':
$r = json_decode(file_get_contents('https://api.cloudflare.com/client/v4/ips'), true);
if (!empty($r['result']['ipv4_cidrs'])) {
$domains = $r['result']['ipv4_cidrs'];
}
break;
}
if (!empty($domains = array_filter($domains ?: [], fn($e) => preg_match('~^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}~', $e)))) {
$this->addInclude(implode(',', $domains), 'white');
}
}
public function denyList($page = 0, $white = 0)
{
$text = 'Menu -> IP -> ' . ($white ? 'white' : 'deny') . ' list';
$text = 'Menu -> IP -> ' . ($white ? 'ignore' : 'block') . 'list';
$domains = $this->getPacConf()[$white ? 'white' : 'deny'] ?: [];
$all = (int) ceil(count($domains) / $this->limit);
$page = min($page, $all - 1);
$page = $page < 0 ? $all - 1 : $page;
$data[] = [
[
'text' => $this->i18n('telegram IPs'),
'callback_data' => "/importIps telegram",
],
];
$data[] = [
[
'text' => $this->i18n('gcore IPs'),
'callback_data' => "/importIps gcore",
],
];
$data[] = [
[
'text' => $this->i18n('cloudflare IPs'),
'callback_data' => "/importIps cloudflare",
],
];
$data[] = [
[
'text' => $this->i18n('add'),
'callback_data' => "/include " . ($white ? 'white' : 'deny'),
],
];
if (!empty($domains)) {
foreach (array_slice($domains, $page * $this->limit, $this->limit) as $v) {
$data[] = [
[
'text' => $v,
'callback_data' => "/searchLogs $v",
'callback_data' => "/searchLogs $v denyList $page $white",
],
[
'text' => $this->i18n('delete'),
@@ -4512,7 +4682,7 @@ DNS-over-HTTPS with IP:
$this->ipMenu();
}
public function denyIp($ip, $nodelete = false, $page = 0, $white = 0)
public function denyIp($ip, $fun = false, $page = 0, $white = 0)
{
$pac = $this->getPacConf();
if (is_array($ip)) {
@@ -4529,16 +4699,16 @@ DNS-over-HTTPS with IP:
}
}
$this->setPacConf($pac);
if (empty($nodelete)) {
if (empty($fun)) {
$this->delete($this->input['from'], $this->input['message_id']);
}
$this->syncDeny();
if (!empty($nodelete)) {
$this->denyList($page, $white);
if (!empty($fun)) {
$this->{$fun}($page, $white);
}
}
public function whiteIp($ip, $nodelete = false, $page = 0, $white = 0)
public function whiteIp($ip, $fun = false, $page = 0, $white = 0)
{
$pac = $this->getPacConf();
if (is_array($ip)) {
@@ -4555,12 +4725,12 @@ DNS-over-HTTPS with IP:
}
}
$this->setPacConf($pac);
if (empty($nodelete)) {
if (empty($fun)) {
$this->delete($this->input['from'], $this->input['message_id']);
}
$this->syncDeny();
if (!empty($nodelete)) {
$this->denyList($page, $white);
if (!empty($fun)) {
$this->{$fun}($page, $white);
}
}
@@ -4586,6 +4756,20 @@ DNS-over-HTTPS with IP:
public function syncDeny()
{
$pac = $this->getPacConf();
if ($r = fopen('/logs/nginx_tlgrm_access', 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (preg_match('~(\d+\.\d+\.\d+\.\d+)~', $l, $m)) {
$xr[$m[1]] = true;
}
}
fclose($r);
}
if (!empty($xr)) {
foreach (array_keys($xr) as $v) {
$text .= "allow $v;\n";
}
}
if (!empty($pac['white'])) {
$pac['white'] = array_unique($pac['white']);
sort($pac['white']);
@@ -4596,8 +4780,12 @@ DNS-over-HTTPS with IP:
if (!empty($pac['deny'])) {
$pac['deny'] = array_unique($pac['deny']);
sort($pac['deny']);
foreach ($pac['deny'] as $v) {
$text .= "deny $v;\n";
foreach ($pac['deny'] as $k => $v) {
if (!in_array($v, $pac['white'] ?: []) && !in_array($v, array_keys($xr ?: []))) {
$text .= "deny $v;\n";
} else {
unset($pac['deny'][$k]);
}
}
}
$this->setPacConf($pac);
@@ -4800,11 +4988,19 @@ DNS-over-HTTPS with IP:
'text' => $this->i18n('change password'),
'callback_data' => "/changeOcPass",
],
];
$data[] = [
[
'text' => $this->i18n('dns') . ": $dns",
'callback_data' => "/changeOcDns",
],
];
$data[] = [
[
'text' => $this->i18n('listSubnet'),
'callback_data' => "/subnet 0_0_1",
],
];
$data[] = [
[
'text' => $this->i18n('expose-iroutes') . ' ' . $this->i18n($expose ? 'on' : 'off'),
@@ -5119,6 +5315,23 @@ DNS-over-HTTPS with IP:
$domain = $this->getDomain();
$hash = substr(md5($this->key), 0, 8);
$text[] = "Menu -> " . $this->i18n('xray') . " -> $type templates";
$text[] = <<<TEXT
<code>~outbound~</code>
<code>"~pac~"</code>
<code>~dns~</code>
<code>~uid~</code>
<code>~domain~</code>
<code>~directdomain~</code>
<code>~cdndomain~</code>
<code>~short_id~</code>
<code>~public_key~</code>
<code>~server_name~</code>
<code>~app_outbound~</code>
<code>~process_outbound~</code>
<code>~domains_outbound~</code>
<code>~final_outbound~</code>
<code>~ip~</code>
TEXT;
$templates = $pac["{$type}templates"];
$data[] = [
@@ -5180,6 +5393,34 @@ DNS-over-HTTPS with IP:
);
}
public function mainOutbound()
{
$r = $this->send(
$this->input['chat'],
"@{$this->input['username']} send name",
$this->input['message_id'],
reply: 'send name',
);
$_SESSION['reply'][$r['result']['message_id']] = [
'start_message' => $this->input['message_id'],
'start_callback' => $this->input['callback_id'],
'callback' => 'setMainOutbound',
'args' => [],
];
}
public function setMainOutbound($text)
{
$pac = $this->getPacConf();
if (!empty($text)) {
$pac['outbound'] = $text;
} else {
unset($pac['outbound']);
}
$this->setPacConf($pac);
$this->xray();
}
public function xray($page = 0)
{
if (!$this->ssh('pgrep xray', 'xr')) {
@@ -5192,6 +5433,12 @@ DNS-over-HTTPS with IP:
$text[] = "fake domain: <code>$fake</code>";
}
$text[] = 'transport: ' . ($p['transport'] ?: 'Reality');
$data[] = [
[
'text' => $this->i18n('main outbound name: ') . ($p['outbound'] ?: 'proxy'),
'callback_data' => '/mainOutbound',
],
];
$data[] = [
[
'text' => $p['linkdomain'] ?: $this->i18n('cdn'),
@@ -5772,7 +6019,6 @@ DNS-over-HTTPS with IP:
$c['outbounds'][$index]['tls']['reality']['short_id'] = '~short_id~';
}
$c['route'] = $this->addRuleSet($c['route']);
$c['route'] = $this->createRuleSet($c['route'], $uid, $domain);
break;
}
@@ -5782,6 +6028,8 @@ DNS-over-HTTPS with IP:
'~dns~' => "https://$domain/dns-query/$uid",
'~uid~' => $uid,
'~domain~' => $domain,
'~directdomain~' => $pac['domain'],
'~cdndomain~' => $pac['linkdomain'],
'~short_id~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['shortIds'][0],
'~public_key~' => $pac['xray'],
'~server_name~' => $xr['inbounds'][0]['streamSettings']['realitySettings']['serverNames'][0],
@@ -5791,6 +6039,7 @@ DNS-over-HTTPS with IP:
'~final_outbound~' => $pac['final_outbound'] ? $outbound : 'direct',
'~ip~' => $this->ip,
]);
$json = $this->addRuleSet($json);
$json = $this->clearEmptyRules($json);
header('Content-type: application/json');
@@ -5824,29 +6073,40 @@ DNS-over-HTTPS with IP:
return json_encode($json);
}
public function addRuleSet($route)
public function addRuleSet($json)
{
foreach ($route['rules'] as $k => $v) {
$t[$v['outbound']] = $k;
}
$p = $this->getPacConf();
if (!empty($p['rulessetlist'])) {
foreach ($p['rulessetlist'] as $k => $v) {
if (!empty($v)) {
[$type, $time, $url] = explode(':', $k, 3);
$route['rule_set'][] = [
"tag" => $k,
"type" => "remote",
"format" => "binary",
"url" => $url,
"download_detour" => "direct",
"update_interval" => $time
];
$route['rules'][$t[$type]]['rule_set'][] = $k;
$json = json_decode($json, 1);
if (!empty($json['route']['rules'])) {
foreach ($json['route']['rules'] as $k => $v) {
if (!empty($v['addruleset'])) {
$t[$v['outbound']] = $k;
}
}
$this->sd($t, 1);
$p = $this->getPacConf();
if (!empty($p['rulessetlist'])) {
foreach ($p['rulessetlist'] as $k => $v) {
if (!empty($v)) {
[$type, $time, $url] = explode(':', $k, 3);
if (!empty($json['route']['rules'][$t[$type]])) {
$json['route']['rule_set'][] = [
"tag" => $k,
"type" => "remote",
"format" => "binary",
"url" => $url,
"download_detour" => "direct",
"update_interval" => $time
];
$json['route']['rules'][$t[$type]]['rule_set'][] = $k;
}
}
}
}
foreach ($json['route']['rules'] as $k => $v) {
unset($json['route']['rules'][$k]['addruleset']);
}
}
return $route;
return json_encode($json);
}
public function createRuleSet($route, $uid, $domain)
@@ -6265,7 +6525,7 @@ DNS-over-HTTPS with IP:
public function applyupdatebot()
{
$this->exportManual($this->update);
$this->pinBackup($this->update);
$r = $this->send($this->input['from'], 'update...');
file_put_contents('/update/reload_message', "{$this->input['from']}:{$r['result']['message_id']}");
file_put_contents('/update/key', $this->key);
+1 -1
View File
@@ -18,7 +18,7 @@ if (!empty($bot->selfupdate)) {
$bot->offWarp();
}
$bot->dontshowcron = 1;
$bot->adguardSync();
$bot->sslip();
$bot->adguardSync();
$bot->syncDeny();
$bot->cleanDocker();
+1 -1
View File
@@ -505,7 +505,7 @@ ipv4-netmask = 255.255.255.0
# Whether to tunnel all DNS queries via the VPN. This is the default
# when a default route is set.
#tunnel-all-dns = true
tunnel-all-dns = true
# The advertised DNS server. Use multiple lines for
# multiple servers.
+5
View File
@@ -111,6 +111,7 @@
"outbound": "dns-out"
},
{
"addruleset": true,
"createruleset": [
{
"name": "pac",
@@ -125,6 +126,7 @@
"outbound": "~domains_outbound~"
},
{
"addruleset": true,
"createruleset": [
{
"name": "package",
@@ -139,6 +141,7 @@
"outbound": "~app_outbound~"
},
{
"addruleset": true,
"createruleset": [
{
"name": "process",
@@ -153,6 +156,7 @@
"outbound": "~process_outbound~"
},
{
"addruleset": true,
"createruleset": [
{
"name": "block",
@@ -167,6 +171,7 @@
"outbound": "block"
},
{
"addruleset": true,
"createruleset": [
{
"name": "warp",
+2
View File
@@ -160,7 +160,9 @@ services:
- ./ssh:/ssh
- ./app:/app
- ./logs/:/logs/
- ./version:/version
- ./update:/update
- ./.git:/.git
- ./scripts/start_service.sh:/start_service.sh
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
+9 -1
View File
@@ -59,4 +59,12 @@ c:
git checkout .
git reset
webhook:
docker compose exec php php checkwebhook.php
docker compose exec php php checkwebhook.php
reset:
make d
git reset --hard
git clean -fd
docker volume rm vpnbot_adguard vpnbot_warp
make u
backup:
docker compose exec php php backup.php > backup.json
-5
View File
@@ -1,5 +0,0 @@
make d
rm /etc/systemd/resolved.conf.d/adguardhome.conf
mv /etc/resolv.conf.backup /etc/resolv.conf
systemctl reload-or-restart systemd-resolved
make u
-8
View File
@@ -1,8 +0,0 @@
mkdir /etc/systemd/resolved.conf.d
echo "[Resolve]
DNS=127.0.0.1
DNSStubListener=no" > /etc/systemd/resolved.conf.d/adguardhome.conf
mv /etc/resolv.conf /etc/resolv.conf.backup
ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
systemctl reload-or-restart systemd-resolved
make d u
+22
View File
@@ -1,3 +1,25 @@
21.11.2024 v1.107
- xray: добавлены теги ~cdndomain~, ~directdomain~
- xray: возможность менять имя главного аутбаунда(тот аутбаунд который заполняет бот, в шаблонах идет как ~outbound~) для клиентских конфигов
- xray: в шаблонах теперь надо явно указывать ключ addruleset в route -> rules, чтобы бот заполнил их правилами из списка ruleset (см origin шаблон)
- openconnect: добавлена маршрутизация (список подсетей общий с wireguard)
- ip ban: фикс обработки подсетей в белом/черном списках
- ip ban: в белый список можно импортировать адреса от telegram, gcore, cloudflare
- добавлена в игнор папка app/webapp/override. она не будет перезатираться после обновления, туда можно положить ваши ресурсы к override.html
- <code>make backup</code> - сохранит в корень backup.json
- <code>make reset</code> - обнуляет все настройки
18.11.2024 v1.106
- фикс отвала бота при пустых логах телеги
17.11.2024 v1.105
- фикс установки домена при первом запуске
16.11.2024 v1.104
- единая механика закрепления бэкапа
- фикс текста уведомления о новой версии
16.11.2024 v1.103
- фикс уведомления о новой версии
16.11.2024 v1.102
- переделан раздел списка ip под управление кнопками
- возможность добавить свои айпи в blocklist/whitelist
15.11.2024 v1.101
- кнопка добавления https://github.com/legiz-ru/sb-rule-sets/raw/main/ru-bundle.lst
- переделан раздел списка ip для читаемости