Compare commits

..

20 Commits

Author SHA1 Message Date
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
mercury c56fe4ff78 blocklist improve
add https://github.com/legiz-ru/sb-rule-sets/raw/main/ru-bundle.lst button
2024-11-15 11:49:20 +04:00
mercury b0e6835ac9 improve scanner 2024-11-15 01:04:06 +04:00
mercury f29d366aef scaner: fix regex 2024-11-14 16:47:55 +04:00
mercury 9eb15b9b45 update version 2024-11-14 15:37:06 +04:00
mercury 954cfe0346 improve ip ban 2024-11-14 14:32:49 +04:00
mercury 0d21eadcaa fix input timer autoscan 2024-11-14 02:35:51 +04:00
mercury f0b43e791d update version 2024-11-14 01:13:59 +04:00
mercury 0297417a63 xray log file 2024-11-14 00:40:54 +04:00
mercury 7449e8bba3 scanner: sort ips 2024-11-14 00:35:15 +04:00
mercury ad7351dc9a improve logs 2024-11-14 00:24:15 +04:00
mercury 5c5c537d62 scanner: allow/deny lists 2024-11-14 00:01:23 +04:00
mercury edc877f149 limit message for scanner 2024-11-13 01:08:25 +04:00
mercury ea07b0da42 scanner init 2024-11-13 00:59:34 +04:00
12 changed files with 779 additions and 75 deletions
+737 -51
View File
@@ -11,6 +11,8 @@ class Bot
public $file;
public $dns;
public $mtu;
public $logs;
public $reg;
public function __construct($key, $i18n)
{
@@ -28,6 +30,28 @@ class Bot
$this->limit = $this->getPacConf()['limitpage'] ?: 5;
$this->adguard = '/config/AdGuardHome.yaml';
$this->update = '/update/json';
$this->logs = [
'nginx_default_access',
'nginx_domain_access',
'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()
@@ -139,12 +163,60 @@ class Bot
case preg_match('~^/mirror$~', $this->input['message'], $m):
$this->menu('mirror');
break;
case preg_match('~^/switchBanIp$~', $this->input['callback'], $m):
$this->switchBanIp();
break;
case preg_match('~^/searchLogs (.+)$~', $this->input['message'], $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;
case preg_match('~^/switchScanIp$~', $this->input['callback'], $m):
$this->switchScanIp();
break;
case preg_match('~^/autoScanTimeout$~', $this->input['callback'], $m):
$this->autoScanTimeout();
break;
case preg_match('~^/autoupdate$~', $this->input['message'], $m):
$this->autoupdate();
break;
case preg_match('~^/ports$~', $this->input['callback'], $m):
$this->ports();
break;
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();
break;
case preg_match('~^/cleanDeny(?:\s(\d))?$~', $this->input['callback'], $m):
$this->cleanDeny($m[1]);
break;
case preg_match('~^/denyList (.+?)(?:\s(\d))?$~', $this->input['callback'], $m):
$this->denyList($m[1], $m[2] ?: 0);
break;
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):
$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('~^/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(.+?)\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):
$this->adgFillAllowedClients($m[1] ?: false);
break;
@@ -193,6 +265,9 @@ class Bot
case preg_match('~^/addCommunityFilter$~', $this->input['callback'], $m):
$this->addCommunityFilter();
break;
case preg_match('~^/addLegizFilter$~', $this->input['callback'], $m):
$this->addLegizFilter();
break;
case preg_match('~^/pacMenu (\d+)$~', $this->input['callback'], $m):
$this->pacMenu($m[1]);
break;
@@ -220,6 +295,9 @@ class Bot
case preg_match('~^/clearLog (?P<arg>\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
$this->clearLog(...explode('_', $m['arg']));
break;
case preg_match('~^/cleanLog$~', $this->input['callback'], $m):
$this->cleanLog();
break;
case preg_match('~^/delLog (?P<arg>\d+(?:_(?:-)?\d+)?)$~', $this->input['callback'], $m):
$this->delLog(...explode('_', $m['arg']));
break;
@@ -522,7 +600,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();
@@ -591,6 +669,7 @@ class Bot
public function restartXray($c)
{
$c['inbounds'][0]['settings']['clients'] = array_values($c['inbounds'][0]['settings']['clients']);
$c['log']['access'] = '/logs/xray';
$this->ssh('pkill xray', 'xr');
file_put_contents('/config/xray.json', json_encode($c, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$this->ssh('xray run -config /xray.json > /dev/null 2>&1 &', 'xr');
@@ -1097,13 +1176,59 @@ class Bot
$this->checkVersion();
$this->checkBackup($period);
$this->checkCert();
$this->autoAnalyzeLogs();
sleep($period);
}
}
public function autoAnalyzeLogs()
{
try {
$pac = $this->getPacConf();
if (!empty($pac['autoscan'])) {
$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) {
foreach ($v as $i) {
$t[$i['title']][$k] = 1;
}
}
foreach ($t as $k => $v) {
$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' => $v,
'callback_data' => "/searchLogs $v",
]];
}
}
if ($pac['silence'] == 0 || $pac['silence'] == 1) {
foreach ($c['admin'] as $k => $v) {
$this->send($v, "suspicious ips found: $text" . ($ban ? "\nbanned:$ban" : ''), button: $ips ?: [[
[
'text' => $this->i18n('analyze'),
'callback_data' => '/analysisIp',
],
]], disable_notification: $pac['silence'] ? true : false);
}
}
}
}
}
} catch (Exception $e) {
}
}
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']);
@@ -1116,9 +1241,6 @@ class Bot
&& $now - $start >= 0
&& (($now - $start) % $period < $delta)
) {
if (!empty($c['pinbackup'])) {
$this->pinAdmin($c['pinbackup'], 1);
}
$this->pinBackup();
}
}
@@ -1140,12 +1262,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']);
}
@@ -1168,9 +1297,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",
],
]
]);
}
@@ -1329,16 +1462,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(
@@ -2459,9 +2582,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;
}
@@ -2483,7 +2606,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);
@@ -2522,6 +2649,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;
}
}
@@ -3378,6 +3510,19 @@ DNS-over-HTTPS with IP:
$this->pacUpdate();
}
public function addLegizFilter()
{
$pac = $this->getPacConf();
$l = array_filter(array_map(fn($e) => trim($e), explode("\n", file_get_contents('https://github.com/legiz-ru/sb-rule-sets/raw/main/ru-bundle.lst'))));
if (!empty($l)) {
foreach ($l as $k => $v) {
$pac['includelist'][$v] = true;
}
}
$this->setPacConf($pac);
$this->pacUpdate();
}
public function pacMenu($page = 0)
{
unset($_SESSION['proxylistentry']);
@@ -3461,6 +3606,12 @@ DNS-over-HTTPS with IP:
'callback_data' => "/addCommunityFilter",
],
];
$data[] = [
[
'text' => $this->i18n('add') . ' ru-bundle',
'callback_data' => "/addLegizFilter",
],
];
$data = array_merge($data, $this->listPac('includelist', $page, 'pacMenu')[0]);
$data[] = [
[
@@ -3945,7 +4096,7 @@ DNS-over-HTTPS with IP:
public function menu($type = false, $arg = false, $return = false)
{
$domain = $this->getPacConf()['domain'] ?: $this->ip;
$cron = exec('pgrep -f cron.php');
$cron = $this->ssh('pgrep -f cron.php', 'service');
$menu = [
'main' => [
'text' => 'v' . getenv('VER') . ($this->dontshowcron ? '' : "\ncron: " . $this->i18n($cron ? 'on' : 'off') . ($cron ? '' : ' show <code>logs/php_error</code>')),
@@ -4000,6 +4151,12 @@ DNS-over-HTTPS with IP:
'callback_data' => "/pacMenu 0",
],
],
[
[
'text' => $this->i18n('IP ban & Logs'),
'callback_data' => "/ipMenu",
],
],
[
[
'text' => $this->i18n('config'),
@@ -4020,18 +4177,18 @@ DNS-over-HTTPS with IP:
],
],
],
'wg' => $type == 'wg' ? $this->statusWg($arg) : false,
'wg' => $type == 'wg' ? $this->statusWg($arg) : false,
'client' => $type == 'client' ? $this->getClient(...explode('_', $arg)) : false,
'addpeer' => $type == 'addpeer' ? $this->addWg(...explode('_', $arg)) : false,
'pac' => $type == 'pac' ? $this->pacMenu($arg) : false,
'adguard' => $type == 'adguard' ? $this->adguardMenu() : false,
'config' => $type == 'config' ? $this->configMenu() : false,
'ss' => $type == 'ss' ? $this->menuSS() : false,
'lang' => $type == 'lang' ? $this->menuLang() : false,
'oc' => $type == 'oc' ? $this->ocMenu() : false,
'naive' => $type == 'naive' ? $this->naiveMenu() : false,
'mirror' => $type == 'mirror' ? $this->mirrorMenu() : false,
'update' => $type == 'update' ? $this->updatebot() : false,
'addpeer' => $type == 'addpeer' ? $this->addWg(...explode('_', $arg)) : false,
'pac' => $type == 'pac' ? $this->pacMenu($arg) : false,
'adguard' => $type == 'adguard' ? $this->adguardMenu() : false,
'config' => $type == 'config' ? $this->configMenu() : false,
'ss' => $type == 'ss' ? $this->menuSS() : false,
'lang' => $type == 'lang' ? $this->menuLang() : false,
'oc' => $type == 'oc' ? $this->ocMenu() : false,
'naive' => $type == 'naive' ? $this->naiveMenu() : false,
'mirror' => $type == 'mirror' ? $this->mirrorMenu() : false,
'update' => $type == 'update' ? $this->updatebot() : false,
];
$text = $menu[$type ?: 'main' ]['text'];
@@ -4058,6 +4215,497 @@ DNS-over-HTTPS with IP:
}
}
public function switchScanIp()
{
$c = $this->getPacConf();
$c['autoscan'] = $c['autoscan'] ? 0 : 1;
$this->setPacConf($c);
$this->ipMenu();
}
public function switchBanIp()
{
$c = $this->getPacConf();
$c['autodeny'] = $c['autodeny'] ? 0 : 1;
$this->setPacConf($c);
$this->ipMenu();
}
public function switchSilence()
{
$c = $this->getPacConf();
$c['silence'] = (($c['silence'] ?: 0) + 1) % 3;
$this->setPacConf($c);
$this->ipMenu();
}
public function ipMenu()
{
$text = 'Menu -> IP';
$pac = $this->getPacConf();
$d = count($pac['deny'] ?: []);
$w = count($pac['white'] ?: []);
$data[] = [
[
'text' => $this->i18n('logs'),
'callback_data' => "/logs",
],
];
$data[] = [
[
'text' => $this->i18n('autoscan') . ': ' . ($pac['autoscan'] ? $this->getTime(strtotime(($pac['autoscan_timeout'] ?: 3600) . ' seconds')) : $this->i18n('off')),
'callback_data' => '/autoScanTimeout',
],
];
if (!empty($pac['autoscan'])) {
$data[] = [
[
'text' => $this->i18n('autoblock') . ': ' . $this->i18n($pac['autodeny'] ? 'on' : 'off'),
'callback_data' => '/switchBanIp',
],
[
'text' => $this->i18n('silence') . ': ' . ((function ($pac) {
switch ($pac['silence']) {
case 0:
return $this->i18n('off');
case 1:
return '🟡';
case 2:
return $this->i18n('on');
}
})($pac)),
'callback_data' => '/switchSilence',
],
];
}
$data[] = [
[
'text' => $this->i18n('ignorelist') . ": $w",
'callback_data' => '/denyList 0 1',
],
[
'text' => $this->i18n('blocklist') . ": $d",
'callback_data' => '/denyList 0 0',
],
];
$data[] = [
[
'text' => $this->i18n('analyze'),
'callback_data' => '/analysisIp',
],
];
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/menu",
],
];
$this->update(
$this->input['chat'],
$this->input['message_id'],
$text,
$data ?: 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;
}
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 ($r = fopen('/logs/xray', 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (preg_match('~(\d+\.\d+\.\d+\.\d+)(?=.+accepted)~', $l, $m)) {
$xr[$m[1]] = true;
}
}
fclose($r);
}
$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),
];
$ip = [];
foreach ($t as $r) {
foreach ($r as $k => $v) {
$ip[$k] = $v;
}
}
if (!empty($return)) {
return $ip;
}
if (!empty($ip)) {
foreach ($ip as $k => $v) {
$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),
]
];
}
}
$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, $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]]));
$text = "$search\n<pre>$info</pre>";
$data[] = [
[
'text' => $this->i18n('block'),
'callback_data' => "/denyIp $search" . ($fun ? " $fun $page $white" : ''),
],
[
'text' => $this->i18n('ignore'),
'callback_data' => "/whiteIp $search" . ($fun ? " $fun $page $white" : ''),
],
];
$data[] = [
[
'text' => $this->i18n('all logs'),
'callback_data' => "/searchIp $search",
],
[
'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);
}
}
}
public function searchIp($ip)
{
foreach ($this->logs as $v) {
if ($r = fopen("/logs/$v", 'r')) {
while (feof($r) === false) {
$l = fgets($r);
if (preg_match('~' . preg_quote($ip) . '~', $l)) {
$res[$v][] = $l;
}
}
fclose($r);
}
}
if (!empty($res)) {
foreach ($res 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 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 denyList($page = 0, $white = 0)
{
$text = 'Menu -> IP -> ' . ($white ? 'white' : 'deny') . ' 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('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 denyList $page $white",
],
[
'text' => $this->i18n('delete'),
'callback_data' => "/allowIp $v $page" . ($white ? " 1" : ''),
],
];
}
if ($all > 1) {
$data[] = [
[
'text' => '<<',
'callback_data' => "/denyList " . ($page - 1 >= 0 ? $page - 1 : $all - 1) . ($white ? " 1" : ' 0'),
],
[
'text' => '>>',
'callback_data' => "/denyList " . ($page < $all - 1 ? $page + 1 : 0) . ($white ? " 1" : ' 0'),
]
];
}
$data[] = [
[
'text' => $this->i18n('delete all'),
'callback_data' => "/cleanDeny" . ($white ? " 1" : ''),
],
];
}
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/ipMenu",
],
];
$this->update(
$this->input['chat'],
$this->input['message_id'],
$text,
$data ?: false,
);
}
public function cleanDeny($white = 0)
{
$pac = $this->getPacConf();
unset($pac[$white ? 'white' : 'deny']);
$this->setPacConf($pac);
$this->syncDeny();
$this->ipMenu();
}
public function denyIp($ip, $fun = false, $page = 0, $white = 0)
{
$pac = $this->getPacConf();
if (is_array($ip)) {
foreach ($ip as $v) {
$pac['deny'][] = $v;
if (($t = array_search($v, $pac['white'] ?: [])) !== false) {
unset($pac['white'][$t]);
}
}
} else {
$pac['deny'][] = $ip;
if (($t = array_search($ip, $pac['white'] ?: [])) !== false) {
unset($pac['white'][$t]);
}
}
$this->setPacConf($pac);
if (empty($fun)) {
$this->delete($this->input['from'], $this->input['message_id']);
}
$this->syncDeny();
if (!empty($fun)) {
$this->{$fun}($page, $white);
}
}
public function whiteIp($ip, $fun = false, $page = 0, $white = 0)
{
$pac = $this->getPacConf();
if (is_array($ip)) {
foreach ($ip as $v) {
$pac['white'][] = $v;
if (($t = array_search($v, $pac['deny'] ?: [])) !== false) {
unset($pac['deny'][$t]);
}
}
} else {
$pac['white'][] = $ip;
if (($t = array_search($ip, $pac['deny'] ?: [])) !== false) {
unset($pac['deny'][$t]);
}
}
$this->setPacConf($pac);
if (empty($fun)) {
$this->delete($this->input['from'], $this->input['message_id']);
}
$this->syncDeny();
if (!empty($fun)) {
$this->{$fun}($page, $white);
}
}
public function allowIp($ip, $page, $white = 0)
{
$pac = $this->getPacConf();
unset($pac[$white ? 'white' : 'deny'][array_search($ip, $pac[$white ? 'white' : 'deny'])]);
$this->setPacConf($pac);
$this->syncDeny();
$this->denyList($page, $white);
}
public function cleanLogs($ip, $nodelete = false)
{
foreach ($this->logs as $v) {
exec("sed -i '/$ip/d' /logs/$v");
}
if (empty($nodelete)) {
$this->delete($this->input['from'], $this->input['message_id']);
}
}
public function syncDeny()
{
$pac = $this->getPacConf();
if (!empty($pac['white'])) {
$pac['white'] = array_unique($pac['white']);
sort($pac['white']);
foreach ($pac['white'] as $v) {
$text .= "allow $v;\n";
}
}
if (!empty($pac['deny'])) {
$pac['deny'] = array_unique($pac['deny']);
sort($pac['deny']);
foreach ($pac['deny'] as $v) {
$text .= "deny $v;\n";
}
}
$this->setPacConf($pac);
file_put_contents('/config/deny', $text ?: '');
$this->ssh('nginx -s reload', 'up');
}
public function linkXray($i, $s = false)
{
$c = $this->getXray();
@@ -4428,6 +5076,38 @@ DNS-over-HTTPS with IP:
];
}
public function autoScanTimeout()
{
$r = $this->send(
$this->input['chat'],
"@{$this->input['username']} send time like 1 hour or 1 day etc",
$this->input['message_id'],
reply: 'send time like 1 hour or 1 day etc',
);
$_SESSION['reply'][$r['result']['message_id']] = [
'start_message' => $this->input['message_id'],
'start_callback' => $this->input['callback_id'],
'callback' => 'setAutoScanTimeout',
'args' => [],
];
}
public function setAutoScanTimeout($time)
{
$pac = $this->getPacConf();
if (empty($time)) {
unset($pac['autoscan_timeout']);
unset($pac['autoscan']);
} elseif ($t = strtotime($time, 0)) {
$pac['autoscan_timeout'] = $t;
$pac['autoscan'] = 1;
} else {
$this->send($this->input['from'], "$time - wrong format", $this->input['message_id']);
}
$this->setPacConf($pac);
$this->ipMenu();
}
public function templateCopy($type)
{
$r = $this->send(
@@ -5686,7 +6366,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);
@@ -5846,10 +6526,6 @@ DNS-over-HTTPS with IP:
],
];
$data[] = [
[
'text' => $this->i18n('logs'),
'callback_data' => "/logs",
],
[
'text' => $this->i18n('debug') . ': ' . $this->i18n($c['debug'] ? 'on' : 'off'),
'callback_data' => "/debug",
@@ -5980,24 +6656,26 @@ DNS-over-HTTPS with IP:
$size = filesize("/logs/$v");
$data[] = [
[
'text' => "$v ($size)",
'text' => "$size $v",
'callback_data' => "/getLog $k",
],
[
'text' => $this->i18n('clear'),
'text' => $this->i18n('clean'),
'callback_data' => "/clearLog $k",
],
[
'text' => $this->i18n('delete'),
'callback_data' => "/delLog $k",
],
];
}
}
$data[] = [
[
'text' => $this->i18n('clean all'),
'callback_data' => "/cleanLog",
],
];
$data[] = [
[
'text' => $this->i18n('back'),
'callback_data' => "/menu config",
'callback_data' => "/ipMenu",
],
];
$this->update(
@@ -6032,6 +6710,14 @@ DNS-over-HTTPS with IP:
$this->logs();
}
public function cleanLog()
{
foreach (scandir('/logs/') as $k => $v) {
file_put_contents("/logs/$v", '');
}
$this->logs();
}
public function delLog($i)
{
foreach (scandir('/logs/') as $k => $v) {
@@ -6601,7 +7287,7 @@ DNS-over-HTTPS with IP:
var_dump($this->request('setMyCommands', json_encode($data), 1));
}
public function send($chat, $text, ?int $to = 0, $button = false, $reply = false, $mode = 'HTML')
public function send($chat, $text, ?int $to = 0, $button = false, $reply = false, $mode = 'HTML', $disable_notification = false)
{
if ($button) {
$extra = ['inline_keyboard' => $button];
@@ -6622,7 +7308,7 @@ DNS-over-HTTPS with IP:
'text' => "$v\n",
'parse_mode' => $mode,
// 'disable_web_page_preview' => true,
// 'disable_notification' => !empty($to) && 0 == $k,
'disable_notification' => $disable_notification,
'reply_to_message_id' => 0 == $k && $to > 0 ? $to : false,
];
if ($k == array_key_last($tails)) {
@@ -6638,7 +7324,7 @@ DNS-over-HTTPS with IP:
'text' => $text,
'parse_mode' => $mode,
// 'disable_web_page_preview' => true,
// 'disable_notification' => !empty($to),
'disable_notification' => $disable_notification,
'reply_to_message_id' => $to,
];
if (!empty($extra)) {
+1
View File
@@ -20,4 +20,5 @@ if (!empty($bot->selfupdate)) {
$bot->dontshowcron = 1;
$bot->adguardSync();
$bot->sslip();
$bot->syncDeny();
$bot->cleanDocker();
View File
+1
View File
@@ -11,6 +11,7 @@ events {
}
stream {
include /etc/nginx/deny;
log_format basic '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received';
access_log /logs/upstream_access basic;
+5 -2
View File
@@ -29,6 +29,7 @@ services:
volumes:
- ./config/.profile:/root/.ashrc:ro
- ./config/upstream.conf:/etc/nginx/nginx.conf
- ./config/deny:/etc/nginx/deny
- ./scripts/start_upstream.sh:/start_upstream.sh
- ./ssh:/ssh
- ./config/sshd_config:/etc/ssh/sshd_config
@@ -95,7 +96,7 @@ services:
ipv4_address: 10.10.1.2
logging: *default-logging
php:
image: mercurykd/vpnbot-php:1.5
image: mercurykd/vpnbot-php:1.6
build:
dockerfile: dockerfile/php.dockerfile
args:
@@ -146,7 +147,7 @@ services:
timeout: 5s
retries: 5
service:
image: mercurykd/vpnbot-php:1.5
image: mercurykd/vpnbot-php:1.6
build:
dockerfile: dockerfile/php.dockerfile
args:
@@ -159,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:
+3 -1
View File
@@ -32,4 +32,6 @@ RUN apk add --no-cache --update php81 \
&& mv sing-box-1.8.11-linux-amd64/sing-box /usr/bin \
&& rm sing-box-1.8.11-linux-amd64.tar.gz \
&& rm -rf /sing-box-1.8.11-linux-amd64
ENV ENV="/root/.ashrc"
RUN apk add openssh \
&& mkdir /root/.ssh
ENV ENV="/root/.ashrc"
-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
-1
View File
@@ -3,7 +3,6 @@ ssh-keygen -m PEM -t rsa -f /ssh/key -N ''
openssl req -newkey rsa:2048 -sha256 -nodes -x509 -days 365 -keyout /certs/self_private -out /certs/self_public -subj "/C=NN/ST=N/L=N/O=N/CN=$IP"
php init.php
if [[ -f "/start" && -f "/ssh/key.pub" && -s "/ssh/key.pub" ]]; then
php cron.php &
unitd --log /logs/unit_error
curl -X PUT --data-binary @/config/unit.json --unix-socket /var/run/control.unit.sock http://localhost/config
pkill unitd
+5 -1
View File
@@ -1 +1,5 @@
php service.php
cat /ssh/key.pub > /root/.ssh/authorized_keys
ssh-keygen -A
exec /usr/sbin/sshd -D -e "$@" &
php service.php
php cron.php
+6 -6
View File
@@ -10,23 +10,23 @@ do
if [[ -n "$cmd" ]]
then
key=$(cat $pwd/update/key)
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "останавливаю бота"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "stopping the bot"/')"
docker compose down --remove-orphans
if [[ "$cmd" == "1" ]]
then
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "очищаю директорию"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "clearing the directory"/')"
git reset --hard && git clean -fd
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "скачиваю обновление"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "downloading the update"/')"
git fetch
if [[ -n "$branch" ]]
then
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "меняю ветку"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "changing branch"/')"
git checkout -t origin/$branch || git checkout $branch
fi
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "применяю обновления"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "applying updates"/')"
git pull > ./update/message
fi
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "запускаю бота"/')"
curl -H "Content-Type: application/json" -X POST https://api.telegram.org/bot$key/editMessageText -d "$(cat $pwd/update/curl | sed 's/"text":"~t~"/"text": "launching the bot"/')"
IP=$(curl ipinfo.io/ip) VER=$(git describe --tags) docker compose --env-file ./.env --env-file ./override.env up -d --force-recreate
bash $pwd/update/update.sh &
> $pwd/update/key
+21
View File
@@ -1,3 +1,24 @@
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 для читаемости
15.11.2024 v1.100
- несколько режимов silence для сканера
- анализ сканера скидывает файл всех найденных айпи
- команда /searchLogs (пока только для айпи)
- логи переехали в меню сканера
- обновление текстов и подсказок
14.11.2024 v1.99.1
- анализ логов и бан айпишников, фиксы
14.11.2024 v1.99
- анализ логов и бан айпишников
11.11.2024 v1.98
- фикс заполнения clientId при наполнении белого списка клиентов adg
09.11.2024 v1.97