mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-12 20:47:39 +02:00
Update to v7.39.0
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# aapanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2014-2099 aapanel(http://www.aapanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
|
||||
|
||||
def run_cmd(cmd):
|
||||
try:
|
||||
subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def load_iptables():
|
||||
"""
|
||||
恢复 iptables 规则
|
||||
"""
|
||||
if run_cmd("iptables -C INPUT -j IN_BT"):
|
||||
print("iptables existed")
|
||||
else:
|
||||
if run_cmd("iptables-restore --noflush < /www/server/panel/data/iptablesdata"):
|
||||
print("iptables restored")
|
||||
|
||||
|
||||
def load_ipset():
|
||||
"""
|
||||
恢复 ipset 规则
|
||||
"""
|
||||
if run_cmd("ipset restore < /www/server/panel/data/ipsetdata"):
|
||||
print("ipset restored")
|
||||
else:
|
||||
print("ipset existed")
|
||||
|
||||
|
||||
def save_iptables():
|
||||
"""
|
||||
保存 iptables 规则
|
||||
"""
|
||||
if run_cmd("iptables -C INPUT -j IN_BT"):
|
||||
if run_cmd(
|
||||
"iptables-save | grep -E 'IN_BT|OUT_BT|FORWARD_BT|^\*|^COMMIT' | sed 's/^-A INPUT/-I INPUT/; s/^-A OUTPUT/-I OUTPUT/; s/^-A PREROUTING/-I PREROUTING/' > /www/server/panel/data/iptablesdata"):
|
||||
print("iptables saved")
|
||||
|
||||
|
||||
def save_ipset():
|
||||
"""
|
||||
保存 ipset 规则
|
||||
"""
|
||||
if run_cmd("ipset save | grep -E '_bt_' > /www/server/panel/data/ipsetdata"):
|
||||
print("ipset saved")
|
||||
|
||||
|
||||
def dbus_listener():
|
||||
if not os.path.exists("/sbin/firewalld"):
|
||||
print("is not Firewalld")
|
||||
return
|
||||
|
||||
cmd = [
|
||||
"dbus-monitor",
|
||||
"--system",
|
||||
"type='signal',path='/org/fedoraproject/FirewallD1',interface='org.fedoraproject.FirewallD1',member='Reloaded'",
|
||||
"type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.fedoraproject.FirewallD1',arg1=''"
|
||||
]
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
while True:
|
||||
line = process.stdout.readline().strip()
|
||||
|
||||
if not line:
|
||||
break
|
||||
if "signal" in line:
|
||||
if "member=Reloaded" in line:
|
||||
print("firewalld reload...")
|
||||
load_iptables()
|
||||
elif "member=NameOwnerChanged" in line:
|
||||
print("firewalld restart...")
|
||||
threading.Timer(3, load_iptables).start()
|
||||
|
||||
|
||||
def main():
|
||||
import time
|
||||
if len(sys.argv) < 2:
|
||||
print("command:start|reload|stop|save")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
if command == "start":
|
||||
load_ipset()
|
||||
load_iptables()
|
||||
listener_thread = threading.Thread(target=dbus_listener)
|
||||
listener_thread.daemon = True
|
||||
listener_thread.start()
|
||||
while True:
|
||||
time.sleep(1)
|
||||
elif command == "reload":
|
||||
save_ipset()
|
||||
save_iptables()
|
||||
load_ipset()
|
||||
load_iptables()
|
||||
elif command == "stop":
|
||||
save_ipset()
|
||||
save_iptables()
|
||||
elif command == "save":
|
||||
save_ipset()
|
||||
save_iptables()
|
||||
elif command == "saveiptables":
|
||||
save_iptables()
|
||||
elif command == "saveipset":
|
||||
save_ipset()
|
||||
elif command == "loadiptables":
|
||||
load_iptables()
|
||||
elif command == "loadipset":
|
||||
load_ipset()
|
||||
elif command == "reloadiptables":
|
||||
save_iptables()
|
||||
load_iptables()
|
||||
elif command == "reloadipset":
|
||||
save_ipset()
|
||||
load_ipset()
|
||||
else:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
create_chain() {
|
||||
# 创建iptables链
|
||||
# params:表名 链名
|
||||
local table=$1
|
||||
local chain=$2
|
||||
if ! iptables -t "$table" -n -L "$chain" > /dev/null 2>&1; then
|
||||
iptables -t "$table" -N "$chain"
|
||||
echo "Created chain $chain in table $table"
|
||||
else
|
||||
echo "Chain $chain already exists in table $table"
|
||||
fi
|
||||
}
|
||||
|
||||
insert_input_output_rules() {
|
||||
# 在指定的表的链中插入子链
|
||||
# params:"表名:目标链名:需要插入的链"
|
||||
local rules=("$@")
|
||||
for rule in "${rules[@]}"; do
|
||||
IFS=':' read -r table chain target <<< "$rule"
|
||||
if ! iptables -t "$table" -C "$chain" -j "$target" > /dev/null 2>&1; then
|
||||
iptables -t "$table" -I "$chain" 1 -j "$target"
|
||||
echo "Inserted $target to $chain in table $table"
|
||||
else
|
||||
echo "$target already in $chain in table $table"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
add_jump_rules() {
|
||||
# 在指定的表的链中添加跳转规则
|
||||
# params:表名 目标链名 需要跳转的链
|
||||
local table=$1
|
||||
local target_chain=$2
|
||||
shift 2
|
||||
local chains=("$@")
|
||||
for chain in "${chains[@]}"; do
|
||||
if ! iptables -t "$table" -C "$target_chain" -j "$chain" > /dev/null 2>&1; then
|
||||
iptables -t "$table" -A "$target_chain" -j "$chain"
|
||||
echo "Added $chain to $target_chain in table $table"
|
||||
else
|
||||
echo "$chain already in $target_chain in table $table"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
create_ipset() {
|
||||
local ipset_name=$1
|
||||
if ! ipset list "$ipset_name" > /dev/null 2>&1; then
|
||||
ipset create "$ipset_name" hash:net maxelem 100000 timeout 0
|
||||
echo "Created ipset $ipset_name"
|
||||
else
|
||||
echo "ipset $ipset_name already exists"
|
||||
fi
|
||||
}
|
||||
|
||||
add_ipset_rules() {
|
||||
local rules=("$@")
|
||||
for rule in "${rules[@]}"; do
|
||||
IFS=':' read -r chain action direction ipset_name <<< "$rule"
|
||||
if ! iptables -C "$chain" -m set --match-set "$ipset_name" "$direction" -j "$action" > /dev/null 2>&1; then
|
||||
iptables -I "$chain" 1 -m set --match-set "$ipset_name" "$direction" -j "$action"
|
||||
echo "Added $action rule for $ipset_name ($direction) in $chain"
|
||||
else
|
||||
echo "$action rule for $ipset_name ($direction) already in $chain"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 函数:创建systemd服务
|
||||
create_systemd_service() {
|
||||
local exec_path="/www/server/panel/pyenv/bin/python3 /www/server/panel/script/BT-FirewallServices.py"
|
||||
local service_file="/etc/systemd/system/BT-FirewallServices.service"
|
||||
if [ ! -f "$service_file" ]; then
|
||||
/www/server/panel/pyenv/bin/python3 -c "import os,sys; os.chdir('/www/server/panel/'); sys.path.insert(0, 'class/'); sys.path.insert(0, '/www/server/panel/'); import public; public.stop_syssafe();"
|
||||
cat << EOF > "$service_file"
|
||||
[Unit]
|
||||
Description=Firewall and System Event Listener Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=$exec_path start
|
||||
ExecReload=$exec_path reload
|
||||
ExecStop=$exec_path stop
|
||||
User=root
|
||||
Type=simple
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl enable BT-FirewallServices.service
|
||||
${exec_path} save
|
||||
systemctl start BT-FirewallServices.service
|
||||
echo "Systemd service created and started"
|
||||
/www/server/panel/pyenv/bin/python3 -c "import os,sys; os.chdir('/www/server/panel/'); sys.path.insert(0, 'class/'); sys.path.insert(0, '/www/server/panel/'); import public; public.start_syssafe();"
|
||||
else
|
||||
echo "Systemd service already exists"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
# 所有需要创建接管的子链
|
||||
local chains=(
|
||||
"filter:IN_BT"
|
||||
"filter:IN_BT_log"
|
||||
"filter:IN_BT_user_ip"
|
||||
"filter:IN_BT_ip"
|
||||
"filter:IN_BT_user_port"
|
||||
"filter:OUT_BT"
|
||||
"filter:OUT_BT_user_ip"
|
||||
"filter:OUT_BT_user_port"
|
||||
"filter:IN_BT_Country"
|
||||
"nat:FORWARD_BT"
|
||||
)
|
||||
for chain in "${chains[@]}"; do
|
||||
IFS=':' read -r table chain_name <<< "$chain"
|
||||
create_chain "$table" "$chain_name"
|
||||
done
|
||||
|
||||
# 插入接管的子链
|
||||
local rules=(
|
||||
"filter:INPUT:IN_BT"
|
||||
"filter:IN_BT:IN_BT_log"
|
||||
"filter:IN_BT:IN_BT_user_ip"
|
||||
"filter:IN_BT:IN_BT_ip"
|
||||
"filter:IN_BT:IN_BT_user_port"
|
||||
"filter:IN_BT_ip:IN_BT_Country"
|
||||
"filter:OUTPUT:OUT_BT"
|
||||
"filter:OUT_BT:OUT_BT_user_ip"
|
||||
"filter:OUT_BT:OUT_BT_user_port"
|
||||
"nat:PREROUTING:FORWARD_BT"
|
||||
)
|
||||
insert_input_output_rules "${rules[@]}"
|
||||
|
||||
# ipset集合
|
||||
local ipsets=(
|
||||
"in_bt_user_accept_ipset"
|
||||
"in_bt_user_drop_ipset"
|
||||
"out_bt_user_accept_ipset"
|
||||
"out_bt_user_drop_ipset"
|
||||
)
|
||||
for ipset_name in "${ipsets[@]}"; do
|
||||
create_ipset "$ipset_name"
|
||||
done
|
||||
|
||||
local ipset_rules=(
|
||||
"IN_BT_user_ip:ACCEPT:src:in_bt_user_accept_ipset"
|
||||
"IN_BT_user_ip:DROP:src:in_bt_user_drop_ipset"
|
||||
"OUT_BT_user_ip:ACCEPT:dst:out_bt_user_accept_ipset"
|
||||
"OUT_BT_user_ip:DROP:dst:out_bt_user_drop_ipset"
|
||||
)
|
||||
add_ipset_rules "${ipset_rules[@]}"
|
||||
create_systemd_service
|
||||
systemctl reload BT-FirewallServices
|
||||
echo "aapanel firewall init finish..."
|
||||
}
|
||||
|
||||
main
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
configure_logging() {
|
||||
sudo tee /etc/rsyslog.d/ip-daily-log.conf <<EOF
|
||||
:msg, contains, "DAILY-IP: " -/var/log/IP-DAILY-LOG.log
|
||||
& stop
|
||||
EOF
|
||||
|
||||
sudo tee /etc/logrotate.d/ip-daily-log <<EOF
|
||||
/var/log/IP-DAILY-LOG.log {
|
||||
daily
|
||||
rotate 3 # 仅保留3天历史
|
||||
missingok
|
||||
nocompress # 无需压缩
|
||||
notifempty
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload rsyslog >/dev/null 2>&1
|
||||
endscript
|
||||
}
|
||||
EOF
|
||||
sudo systemctl restart rsyslog
|
||||
}
|
||||
|
||||
|
||||
sudo iptables -C IN_BT_log -j IN_BT_log_DAILY > /dev/null 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "rules 'iptables -I IN_BT_log -j IN_BT_log_DAILY' Exiting..."
|
||||
exit 0
|
||||
else
|
||||
echo "add rule..."
|
||||
sudo iptables -N IN_BT_log_DAILY
|
||||
sudo iptables -I IN_BT_log -j IN_BT_log_DAILY
|
||||
sudo iptables -A IN_BT_log_DAILY -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN
|
||||
sudo iptables -A IN_BT_log_DAILY -m recent --name DAILY_IPS --set -j LOG --log-prefix "DAILY-IP: " --log-level 4
|
||||
|
||||
ipset create in_bt_malicious_ipset hash:ip maxelem 1000000 timeout 0;
|
||||
iptables -A IN_BT_ip -m set --match-set in_bt_malicious_ipset src -j DROP
|
||||
systemctl reload BT-FirewallServices
|
||||
configure_logging
|
||||
echo "add rule finish..."
|
||||
fi
|
||||
+210
-111
@@ -4,29 +4,22 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from functools import wraps
|
||||
from typing import Optional, Dict
|
||||
# import asyncio
|
||||
import fcntl
|
||||
|
||||
os.chdir("/www/server/panel")
|
||||
sys.path.insert(0, "class/")
|
||||
sys.path.insert(0, "/www/server/panel/")
|
||||
|
||||
sys.path.insert(0, "class_v2/")
|
||||
import public
|
||||
|
||||
if not "class_v2" in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class_v2")
|
||||
from panel_site_v2 import panelSite
|
||||
|
||||
SETUP_PATH = public.get_setup_path()
|
||||
DATA_PATH = os.path.join(SETUP_PATH, "panel/data")
|
||||
|
||||
DAEMON_SERVICE = os.path.join(DATA_PATH, "daemon_service.pl")
|
||||
if not os.path.exists(DAEMON_SERVICE):
|
||||
public.writeFile(DAEMON_SERVICE, json.dumps([]))
|
||||
|
||||
DAEMON_SERVICE_LOCK = os.path.join(DATA_PATH, "daemon_service_lock.pl")
|
||||
MANUAL_FLAG = os.path.join(public.get_panel_path(), "data/mod_push_data", "manual_flag.pl")
|
||||
if not os.path.exists(MANUAL_FLAG):
|
||||
public.writeFile(MANUAL_FLAG, json.dumps({}))
|
||||
|
||||
SERVICES_MAP = {
|
||||
"apache": (
|
||||
@@ -56,74 +49,197 @@ SERVICES_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def add_daemon(service_name: str) -> bool:
|
||||
if not service_name or service_name not in SERVICES_MAP.keys():
|
||||
return False
|
||||
daemon_list = json.loads(public.readFile(DAEMON_SERVICE))
|
||||
if service_name in daemon_list:
|
||||
return True
|
||||
daemon_list.append(service_name)
|
||||
public.writeFile(DAEMON_SERVICE, json.dumps(list(set(daemon_list))))
|
||||
return True
|
||||
def manual_flag(server_name: str = None, open_: str = None) -> Optional[dict]:
|
||||
if not server_name: # only read
|
||||
return DaemonManager.safe_read()
|
||||
# 人为干预
|
||||
if open_ in ["start", "restart"]: # 激活服务检查
|
||||
return DaemonManager.active_daemon(server_name)
|
||||
elif open_ == "stop": # 跳过服务检查
|
||||
return DaemonManager.skip_daemon(server_name)
|
||||
return DaemonManager.safe_read()
|
||||
|
||||
|
||||
def del_daemon(service_name: str) -> bool:
|
||||
if not service_name:
|
||||
return False
|
||||
try:
|
||||
public.writeFile(DAEMON_SERVICE, json.dumps([
|
||||
x for x in json.loads(public.readFile(DAEMON_SERVICE)) if x != service_name
|
||||
]))
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
class DaemonManager:
|
||||
@classmethod
|
||||
def __ensure(cls):
|
||||
if not os.path.exists(DAEMON_SERVICE_LOCK):
|
||||
with open(DAEMON_SERVICE_LOCK, "w") as _:
|
||||
pass
|
||||
if not os.path.exists(MANUAL_FLAG):
|
||||
public.writeFile(MANUAL_FLAG, json.dumps({}))
|
||||
if not os.path.exists(DAEMON_SERVICE):
|
||||
public.writeFile(DAEMON_SERVICE, json.dumps([]))
|
||||
|
||||
@staticmethod
|
||||
def read_lock(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
DaemonManager.__ensure()
|
||||
with open(DAEMON_SERVICE_LOCK, "r") as lock_file:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_SH)
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
finally:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_UN)
|
||||
return result
|
||||
|
||||
def save_file(path: str, body: str) -> None:
|
||||
try:
|
||||
with open(path, "w+") as f:
|
||||
fcntl.flock(f, fcntl.LOCK_EX)
|
||||
f.write(body)
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
except (IOError, OSError) as _:
|
||||
pass
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
def write_lock(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
DaemonManager.__ensure()
|
||||
with open(DAEMON_SERVICE_LOCK, "r+") as lock_file:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_EX)
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
finally:
|
||||
fcntl.flock(lock_file, fcntl.LOCK_UN)
|
||||
return result
|
||||
|
||||
def read_file(path: str) -> dict:
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
content = f.read()
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
return json.loads(content) if content else {}
|
||||
except json.JSONDecodeError as _:
|
||||
return {}
|
||||
except (IOError, OSError) as _:
|
||||
return {}
|
||||
return wrapper
|
||||
|
||||
@staticmethod
|
||||
@write_lock
|
||||
def operate_daemon(service_name: str, flag: int = 0) -> list:
|
||||
"""
|
||||
flag: 0 add, 1 del
|
||||
"""
|
||||
with open(DAEMON_SERVICE, "r+") as f:
|
||||
try:
|
||||
service = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
service = []
|
||||
if flag == 0:
|
||||
service.append(service_name)
|
||||
elif flag == 1:
|
||||
service = [x for x in service if x != service_name]
|
||||
service = list(set(service))
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(service, f)
|
||||
f.truncate()
|
||||
return service
|
||||
|
||||
def manual_flag(server_name: str = None, open_: str = None) -> dict:
|
||||
"""人为关闭标记"""
|
||||
manual = read_file(MANUAL_FLAG)
|
||||
if server_name and open_ == "stop":
|
||||
manual[server_name] = 1
|
||||
save_file(MANUAL_FLAG, json.dumps(manual))
|
||||
return manual
|
||||
elif server_name and open_ in ["start", "restart"] and manual.get(server_name) == 1:
|
||||
manual[server_name] = 0
|
||||
save_file(MANUAL_FLAG, json.dumps(manual))
|
||||
return manual
|
||||
else:
|
||||
return manual
|
||||
@staticmethod
|
||||
@write_lock
|
||||
def operate_manual_flag(service_name: str, flag: int = 0) -> dict:
|
||||
"""
|
||||
flag: 0 normal, 1 manual closed
|
||||
"""
|
||||
with open(MANUAL_FLAG, "r+") as f:
|
||||
try:
|
||||
service = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
service = {}
|
||||
service[service_name] = flag
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(service, f)
|
||||
f.truncate()
|
||||
return service
|
||||
|
||||
@staticmethod
|
||||
def remove_daemon(service_name: str) -> list:
|
||||
"""移除守护进程服务"""
|
||||
return DaemonManager.operate_daemon(service_name, 1)
|
||||
|
||||
@staticmethod
|
||||
def add_daemon(service_name: str) -> list:
|
||||
"""添加守护进程服务"""
|
||||
return DaemonManager.operate_daemon(service_name, 0)
|
||||
|
||||
@staticmethod
|
||||
def skip_daemon(service_name: str) -> dict:
|
||||
"""跳过服务检查"""
|
||||
return DaemonManager.operate_manual_flag(service_name, 1)
|
||||
|
||||
@staticmethod
|
||||
def active_daemon(service_name: str) -> dict:
|
||||
"""激活服务检查"""
|
||||
return DaemonManager.operate_manual_flag(service_name, 0)
|
||||
|
||||
@staticmethod
|
||||
@read_lock
|
||||
def safe_read():
|
||||
try:
|
||||
res = public.readFile(DAEMON_SERVICE)
|
||||
return json.loads(res) if res else []
|
||||
except:
|
||||
return []
|
||||
|
||||
|
||||
class RestartServices:
|
||||
COUNT = 30
|
||||
RECORD: Dict[str, int] = {}
|
||||
|
||||
def __init__(self):
|
||||
self.nick_name = None
|
||||
self.serviced = None
|
||||
self.pid_file = None
|
||||
self.bash = None
|
||||
|
||||
def __keep_flag_right(self, manual_info: dict) -> None:
|
||||
try:
|
||||
with open(MANUAL_FLAG, "r+") as f:
|
||||
try:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
|
||||
f.seek(0)
|
||||
# noinspection PyTypeChecker
|
||||
json.dump(manual_info, f)
|
||||
f.truncate()
|
||||
except:
|
||||
print("Error writing manual flag file")
|
||||
finally:
|
||||
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
|
||||
except Exception as e:
|
||||
print("Error keep_flag_right:", e)
|
||||
|
||||
def _overhead(self) -> bool:
|
||||
if self.nick_name not in self.RECORD:
|
||||
self.RECORD[self.nick_name] = 0
|
||||
return True
|
||||
|
||||
if self.RECORD[self.nick_name] >= self.COUNT:
|
||||
return False
|
||||
|
||||
self.RECORD[self.nick_name] += 1
|
||||
return True
|
||||
|
||||
def _script(self, act: str) -> None:
|
||||
try:
|
||||
if act not in ["start", "stop", "restart", "status"]:
|
||||
return
|
||||
# "try to {act} [{self.nick_name}]..."
|
||||
bash_path = self.bash if self.bash else f"/etc/init.d/{self.serviced}"
|
||||
if self.pid_file.endswith(".sock"):
|
||||
try:
|
||||
os.remove(self.pid_file)
|
||||
except:
|
||||
pass
|
||||
result = subprocess.run(
|
||||
[bash_path, act],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
public.WriteLog(
|
||||
"Service Daemon", f"Failed to {act} {self.nick_name}, error: {result.stderr.strip()}"
|
||||
)
|
||||
except subprocess.TimeoutExpired as t:
|
||||
public.WriteLog(
|
||||
"Service Daemon", f"Failed to {act} {self.nick_name}, error: time out, {t}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
public.WriteLog(
|
||||
"Service Daemon", f"Failed to {act} {self.nick_name}, error: {e}"
|
||||
)
|
||||
|
||||
def is_support(self) -> bool:
|
||||
try:
|
||||
map_info = SERVICES_MAP.get(self.nick_name)
|
||||
@@ -186,57 +302,43 @@ class RestartServices:
|
||||
except:
|
||||
return False
|
||||
|
||||
def _script(self, act: str) -> None:
|
||||
try:
|
||||
if act not in ["start", "stop", "restart", "status"]:
|
||||
return
|
||||
# "try to {act} [{self.nick_name}]..."
|
||||
bash_path = self.bash if self.bash else f"/etc/init.d/{self.serviced}"
|
||||
if self.pid_file.endswith(".sock"):
|
||||
try:
|
||||
subprocess.run([f"rm -f {self.pid_file}"])
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
subprocess.run(
|
||||
[bash_path, act],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
@DaemonManager.read_lock
|
||||
def main(self):
|
||||
with open(MANUAL_FLAG, 'rb') as fp:
|
||||
fcntl.flock(fp, fcntl.LOCK_SH)
|
||||
check_list = json.loads(public.readFile(DAEMON_SERVICE))
|
||||
manual_info = manual_flag()
|
||||
manaul = public.readFile(MANUAL_FLAG)
|
||||
services = public.readFile(DAEMON_SERVICE)
|
||||
try:
|
||||
manual_info = json.loads(manaul) if manaul else {}
|
||||
check_list = json.loads(services) if services else []
|
||||
except Exception as e:
|
||||
public.print_log(f"error, {e}")
|
||||
return
|
||||
|
||||
for service in check_list:
|
||||
self.nick_name = service
|
||||
if not self.is_support() or not self.is_install_service():
|
||||
for service in [
|
||||
x for x in check_list if self.RECORD.get(x, 0) < self.COUNT
|
||||
]:
|
||||
self.nick_name = service
|
||||
if not self.is_support() or not self.is_install_service():
|
||||
continue
|
||||
|
||||
if not self.is_process_running():
|
||||
if manual_info.get(self.nick_name) == 1:
|
||||
# service closed maually, skip
|
||||
continue
|
||||
|
||||
public.WriteLog(
|
||||
"Service Daemon", f"Service [ {self.nick_name} ] is Not Running, Try to start it..."
|
||||
)
|
||||
self._overhead()
|
||||
self._script("start")
|
||||
time.sleep(3)
|
||||
if not self.is_process_running():
|
||||
if manual_info.get(self.nick_name) == 1:
|
||||
# service closed maually, skip
|
||||
continue
|
||||
public.WriteLog(
|
||||
"Service Daemon", f"Service [ {self.nick_name} ] is Not Running, Try to start it..."
|
||||
)
|
||||
self._script("start")
|
||||
time.sleep(5)
|
||||
if not self.is_process_running():
|
||||
self._script("restart")
|
||||
self._overhead()
|
||||
self._script("restart")
|
||||
|
||||
if manual_info.get(self.nick_name) == 1:
|
||||
# service is running, remove manual flag
|
||||
# service is running, fix the wrong flag
|
||||
manual_info[self.nick_name] = 0
|
||||
save_file(MANUAL_FLAG, json.dumps(manual_info))
|
||||
return
|
||||
return
|
||||
# under lock file read lock
|
||||
self.__keep_flag_right(manual_info)
|
||||
|
||||
|
||||
def first_time_installed(data: dict) -> None:
|
||||
@@ -253,10 +355,7 @@ def first_time_installed(data: dict) -> None:
|
||||
if setup is False and os.path.exists(pl_name):
|
||||
os.remove(pl_name)
|
||||
elif setup is True and not os.path.exists(pl_name):
|
||||
get = public.dict_obj()
|
||||
get.name = service
|
||||
get.status = 1
|
||||
panelSite().set_restart_task(get)
|
||||
DaemonManager.add_daemon(service)
|
||||
public.writeFile(pl_name, "1", mode="w")
|
||||
else:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# aapanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2014-2099 aapanel(http://www.aapanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
os.chdir('/www/server/panel/')
|
||||
sys.path.insert(0, "class/")
|
||||
sys.path.insert(0, "class_v2/")
|
||||
sys.path.insert(0, "/www/server/panel/")
|
||||
import public
|
||||
|
||||
__isFirewalld = False
|
||||
__isUfw = False
|
||||
|
||||
if os.path.exists('/usr/sbin/firewalld') and os.path.exists('/usr/bin/yum'):
|
||||
__isFirewalld = True
|
||||
if os.path.exists('/usr/sbin/ufw') and os.path.exists('/usr/bin/apt-get'):
|
||||
__isUfw = True
|
||||
|
||||
|
||||
def check_ipset_exist(ipset_name):
|
||||
cmd = "ipset list {}|grep Name".format(ipset_name)
|
||||
res, err = public.ExecShell(cmd)
|
||||
if err != "":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def firewalld_process_zone_file(zone_path, rule_dict, zone_name):
|
||||
"""
|
||||
处理zone文件(public.xml或trusted.xml),删除匹配的规则。
|
||||
|
||||
参数:
|
||||
zone_path (str): zone文件路径
|
||||
rule_dict (dict): 规则字典
|
||||
zone_name (str): Zone名称('public'或'trusted')
|
||||
"""
|
||||
if not os.path.exists(zone_path):
|
||||
return
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(zone_path)
|
||||
root = tree.getroot()
|
||||
# 处理<rule>标签
|
||||
for rule in root.findall('rule'):
|
||||
source = rule.find('source')
|
||||
if source is not None:
|
||||
ip = source.get('address')
|
||||
action = None
|
||||
if rule.find('accept') is not None:
|
||||
action = 'ACCEPT'
|
||||
elif rule.find('drop') is not None:
|
||||
action = 'DROP'
|
||||
if (ip in rule_dict and
|
||||
rule_dict[ip]['Chain'].upper() == 'INPUT' and
|
||||
rule_dict[ip]['Zone'] == zone_name and
|
||||
rule_dict[ip]['Strategy'].upper() == action.upper()):
|
||||
root.remove(rule)
|
||||
if zone_name == 'trusted':
|
||||
for source in root.findall('source'):
|
||||
ip = source.get('address')
|
||||
if (ip in rule_dict and
|
||||
rule_dict[ip]['Chain'].upper() == 'INPUT' and
|
||||
rule_dict[ip]['Zone'] == 'trusted' and
|
||||
rule_dict[ip]['Strategy'].upper() == 'ACCEPT'):
|
||||
root.remove(source)
|
||||
tree.write(zone_path)
|
||||
|
||||
|
||||
def firewalld_process_direct_file(direct_path, rule_dict):
|
||||
"""
|
||||
处理direct.xml文件,删除匹配的OUTPUT规则。
|
||||
参数:
|
||||
direct_path (str): direct.xml文件路径
|
||||
rule_dict (dict): 规则字典
|
||||
"""
|
||||
if not os.path.exists(direct_path):
|
||||
return
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(direct_path)
|
||||
root = tree.getroot()
|
||||
for rule in root.findall('rule'):
|
||||
if rule.get('chain') == 'OUTPUT':
|
||||
rule_text = rule.text.strip()
|
||||
if rule_text.startswith('-d '):
|
||||
parts = rule_text.split()
|
||||
ip = parts[1]
|
||||
action = parts[-1]
|
||||
if (ip in rule_dict and
|
||||
rule_dict[ip]['Chain'].upper() == 'OUTPUT' and
|
||||
rule_dict[ip]['Strategy'].upper() == action.upper()):
|
||||
root.remove(rule)
|
||||
tree.write(direct_path)
|
||||
|
||||
|
||||
def firewalld_batch_remove_ip_rule(rule_dict):
|
||||
direct_path = '/etc/firewalld/direct.xml'
|
||||
public_path = '/etc/firewalld/zones/public.xml'
|
||||
trusted_path = '/etc/firewalld/zones/trusted.xml'
|
||||
import shutil
|
||||
|
||||
if os.path.exists(public_path):
|
||||
shutil.copyfile(public_path, public_path + '.bak')
|
||||
firewalld_process_zone_file(public_path, rule_dict, 'public')
|
||||
if os.path.exists(trusted_path):
|
||||
shutil.copyfile(trusted_path, trusted_path + '.bak')
|
||||
firewalld_process_zone_file(trusted_path, rule_dict, 'trusted')
|
||||
if os.path.exists(direct_path):
|
||||
shutil.copyfile(direct_path, direct_path + '.bak')
|
||||
firewalld_process_direct_file(direct_path, rule_dict)
|
||||
|
||||
|
||||
def ufw_batch_remove_ip_rule(rule_dict):
|
||||
"""
|
||||
UFW批量删除规则
|
||||
"""
|
||||
ufw_config = '/etc/ufw/user.rules'
|
||||
import shutil
|
||||
shutil.copy(ufw_config, ufw_config + ".bak" + public.format_date())
|
||||
|
||||
lines = public.readFile(ufw_config)
|
||||
lines = lines.splitlines()
|
||||
new_lines = []
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if line.startswith('### tuple ###'):
|
||||
parts = line.split()
|
||||
action = parts[3] # deny 或 allow
|
||||
direction = parts[-1] # in 或 out
|
||||
ip = parts[-2] # IP 地址
|
||||
|
||||
if ip == '0.0.0.0/0':
|
||||
new_lines.extend(lines[i:i + 3]) # 保留这三行
|
||||
i += 3
|
||||
continue
|
||||
|
||||
chain = 'INPUT' if direction == 'in' else 'OUTPUT' if direction == 'out' else None
|
||||
strategy = 'DROP' if action == 'deny' else 'ACCEPT' if action == 'allow' else None
|
||||
|
||||
if (ip in rule_dict and
|
||||
chain.upper() == rule_dict[ip]['Chain'].upper() and
|
||||
strategy.upper() == rule_dict[ip]['Strategy'].upper()):
|
||||
# 如果匹配,跳过这三行(即删除)
|
||||
i += 3
|
||||
else:
|
||||
# 如果不匹配,保留这三行
|
||||
new_lines.extend(lines[i:i + 3])
|
||||
i += 3
|
||||
else:
|
||||
# 如果不是规则的开始行,直接保留
|
||||
new_lines.append(lines[i])
|
||||
i += 1
|
||||
with open(ufw_config, 'w') as file:
|
||||
file.writelines(line if line.endswith('\n') else line + '\n' for line in new_lines)
|
||||
|
||||
|
||||
# 9.5.0-9.6.0
|
||||
def upgrade_iprule(commodel):
|
||||
list_address = commodel.firewall.list_address()
|
||||
if len(list_address) == 0:
|
||||
return
|
||||
|
||||
rule_dict = {}
|
||||
print("-* IP rule data being migrated...")
|
||||
for item in list_address:
|
||||
print("Migration IP rules:{}".format(item))
|
||||
commodel.iptables.set_chain_rich_ip(item, 'add', item['Chain'])
|
||||
rule_dict[item['Address']] = {
|
||||
"Address": item['Address'],
|
||||
"Family": item['Family'],
|
||||
"Strategy": item['Strategy'],
|
||||
"Zone": item.get('Zone'),
|
||||
"Chain": item['Chain']
|
||||
}
|
||||
|
||||
if __isFirewalld:
|
||||
firewalld_batch_remove_ip_rule(rule_dict)
|
||||
elif __isUfw:
|
||||
ufw_batch_remove_ip_rule(rule_dict)
|
||||
else:
|
||||
return
|
||||
commodel.firewall.reload()
|
||||
public.ExecShell("systemctl reload BT-FirewallServices")
|
||||
print("-* IP rule data migration completed...")
|
||||
|
||||
|
||||
# 9.5.0-9.6.0
|
||||
def upgrade_countrys(commodel):
|
||||
country_list = public.M('firewall_country').select()
|
||||
if len(country_list) == 0:
|
||||
return
|
||||
print("-* Area rule data being migrated...")
|
||||
for country in country_list:
|
||||
ports = country['ports']
|
||||
brief = country['brief']
|
||||
types = country['types']
|
||||
|
||||
print("Rules for relocation areas:{}".format(brief))
|
||||
if __isUfw or not __isFirewalld:
|
||||
if not ports:
|
||||
public.ExecShell('iptables -D INPUT -m set --match-set ' + brief + ' src -j ' + types.upper())
|
||||
else:
|
||||
public.ExecShell(
|
||||
'iptables -D INPUT -m set --match-set ' + brief + ' src -p tcp --destination-port ' + ports + ' -j ' + types.upper()
|
||||
)
|
||||
else:
|
||||
if not ports:
|
||||
public.ExecShell(
|
||||
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -j {}".format(
|
||||
brief, types.upper())
|
||||
)
|
||||
else:
|
||||
public.ExecShell(
|
||||
'firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set {} src -p tcp --dport {} -j {}'.format(
|
||||
brief, ports, types.upper())
|
||||
)
|
||||
|
||||
commodel.firewall.reload()
|
||||
for country in country_list:
|
||||
ports = country['ports']
|
||||
brief = country['brief']
|
||||
types = country['types']
|
||||
|
||||
print("Rules for relocation areas:{}".format(brief))
|
||||
public.ExecShell("ipset destroy " + brief)
|
||||
|
||||
tmp_file = "/tmp/firewall_{}.txt".format(brief)
|
||||
command = '''grep -q "in_bt_country" {filename} || awk '{{print "add in_bt_country_" $2, $3}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
|
||||
filename=tmp_file)
|
||||
public.ExecShell(command)
|
||||
|
||||
_ipset = "in_bt_country_" + brief
|
||||
public.ExecShell('ipset create {} hash:net maxelem 1000000; ipset restore -f {}'.format(_ipset, tmp_file))
|
||||
|
||||
if ports:
|
||||
public.ExecShell(
|
||||
'iptables -I IN_BT_Country -m set --match-set {} src -p tcp --destination-port {} -j {}'.format(_ipset,
|
||||
ports,
|
||||
types.upper()))
|
||||
else:
|
||||
public.ExecShell('iptables -I IN_BT_Country -m set --match-set {} src -j {}'.format(_ipset, types.upper()))
|
||||
public.ExecShell("systemctl reload BT-FirewallServices")
|
||||
print("-* Area rule data migration completed...")
|
||||
|
||||
|
||||
# 9.5.0-9.6.0
|
||||
def upgrade_malicious_ip():
|
||||
pl_path = "/www/server/panel/config/firewalld_malicious_ip.pl"
|
||||
if not os.path.exists(pl_path):
|
||||
return
|
||||
|
||||
if not check_ipset_exist("malicious_ipset"):
|
||||
return
|
||||
|
||||
print("-* Migration of malicious IP blocking data in progress...")
|
||||
# 移除旧规则
|
||||
if __isUfw or not __isFirewalld:
|
||||
public.ExecShell('rm -rf /var/log/FIREWALL-ACCESS-LOG*')
|
||||
public.ExecShell('rm -rf /etc/rsyslog.d/firewall-access-log.conf')
|
||||
public.ExecShell('rm -rf /etc/logrotate.d/firewall-access-log')
|
||||
public.ExecShell(
|
||||
'iptables -D INPUT -m conntrack --ctstate NEW -j LOG --log-prefix "FIREWALL-ACCESS: " --log-level 4')
|
||||
public.ExecShell('iptables -D INPUT -j IP-DAILY-LOG')
|
||||
public.ExecShell('iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN')
|
||||
public.ExecShell(
|
||||
'iptables -D IP-DAILY-LOG -m recent --name DAILY_IPS --set -j LOG --log-prefix "DAILY-IP: " --log-level 4')
|
||||
public.ExecShell('iptables -D IP-DAILY-LOG -j RETURN')
|
||||
public.ExecShell('iptables -X IP-DAILY-LOG')
|
||||
public.ExecShell("iptables -D INPUT -m set --match-set malicious_ipset src -j DROP")
|
||||
else:
|
||||
public.ExecShell(
|
||||
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 1 -m conntrack --ctstate NEW -j LOG --log-prefix 'FIREWALL-ACCESS: ' --log-level 4")
|
||||
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 2 -j IP-DAILY-LOG")
|
||||
public.ExecShell(
|
||||
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 0 -m recent --name DAILY_IPS --rcheck --seconds 86400 -j RETURN")
|
||||
public.ExecShell(
|
||||
"firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 1 -m recent --name DAILY_IPS --set -j LOG --log-prefix 'DAILY-IP: ' --log-level 4")
|
||||
public.ExecShell("firewall-cmd --permanent --direct --remove-rule ipv4 filter IP-DAILY-LOG 2 -j RETURN")
|
||||
public.ExecShell("firewall-cmd --permanent --direct --remove-chain ipv4 filter IP-DAILY-LOG")
|
||||
public.ExecShell(
|
||||
"firewall-cmd --permanent --direct --remove-rule ipv4 filter INPUT 0 -m set --match-set malicious_ipset src -j DROP")
|
||||
commodel.firewall.reload()
|
||||
public.ExecShell("ipset destroy malicious_ipset")
|
||||
|
||||
read = public.readFile(pl_path)
|
||||
if read.strip() == "open":
|
||||
tmp_file = "/tmp/firewall_malicious_ip.txt"
|
||||
command = '''grep -q "in_bt" {filename} || awk '{{print "add in_bt_" $2, $3 ,"timeout", 86400}}' {filename} > {filename}.tmp && mv {filename}.tmp {filename}'''.format(
|
||||
filename=tmp_file)
|
||||
public.ExecShell(command)
|
||||
public.ExecShell("sh /www/server/panel/script/open_malicious_ip.sh")
|
||||
public.ExecShell("ipset restore -f {}".format(tmp_file))
|
||||
public.ExecShell("systemctl reload BT-FirewallServices")
|
||||
print("-* Malicious IP blocking data migration completed...")
|
||||
|
||||
|
||||
def upgrade_port_forward(commodel):
|
||||
"""
|
||||
升级端口转发规则
|
||||
"""
|
||||
port_forward_list = commodel.firewall.list_port_forward()
|
||||
print("-* Migrating port forwarding rule data...")
|
||||
for port_forward in port_forward_list:
|
||||
print("Migrating port forwarding rules:{}".format(port_forward))
|
||||
info = {
|
||||
"Family": "ipv4",
|
||||
"Protocol": port_forward["Protocol"],
|
||||
"S_Address": port_forward['S_Address'],
|
||||
"S_Port": port_forward['S_Port'],
|
||||
"T_Address": port_forward['T_Address'],
|
||||
"T_Port": port_forward['T_Port'],
|
||||
}
|
||||
commodel.firewall.port_forward(info, "remove")
|
||||
commodel.iptables.port_forward(info, "add")
|
||||
public.ExecShell("systemctl reload BT-FirewallServices")
|
||||
commodel.firewall.reload()
|
||||
print("-* Port forwarding rule data migration complete...")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
from firewallModelV2.comModel import main as comModel
|
||||
import time
|
||||
|
||||
commodel = comModel()
|
||||
upgrade_iprule(commodel)
|
||||
upgrade_countrys(commodel)
|
||||
upgrade_malicious_ip()
|
||||
upgrade_port_forward(commodel)
|
||||
print("aapanel: FireWall Migrate Service Finish...")
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
print("-" * 50)
|
||||
print(traceback.format_exc())
|
||||
print("-" * 50)
|
||||
print(f"Error: FireWall Migrate Error: {e}")
|
||||
Reference in New Issue
Block a user