Initial YakPanel commit
This commit is contained in:
66
mod/base/web_conf/__init__.py
Normal file
66
mod/base/web_conf/__init__.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import json
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
|
||||
from .ip_restrict import IpRestrict, RealIpRestrict
|
||||
from .redirect import RealRedirect, Redirect
|
||||
from .access_restriction import AccessRestriction, RealAccessRestriction
|
||||
from .domain_tool import domain_to_puny_code, check_domain, normalize_domain, NginxDomainTool, ApacheDomainTool, \
|
||||
is_domain
|
||||
from .dir_tool import DirTool
|
||||
from .referer import Referer, RealReferer
|
||||
from .logmanager import LogMgr, RealLogMgr
|
||||
from .proxy import Proxy, RealProxy
|
||||
from .ssl import SSLManager, RealSSLManger
|
||||
from .config_mgr import ConfigMgr
|
||||
from .default_site import set_default_site, get_default_site, check_default
|
||||
from .server_extension import NginxExtension as ng_ext, ApacheExtension as ap_ext
|
||||
|
||||
|
||||
def remove_sites_service_config(site_name: str, config_prefix: str = ""):
|
||||
"""
|
||||
用于删除一个网站的nginx,apache的所有相关配置文件和配置项
|
||||
包含:
|
||||
配置文件,访问限制, 反向代理, 重定向, 防盗链,证书目录, IP黑白名单, 历史配置文件, 默认站点, 日志格式配置记录, 伪静态等
|
||||
"""
|
||||
# 配置文件
|
||||
ng_file = "/www/server/panel/vhost/nginx/{}{}.conf".format(config_prefix, site_name)
|
||||
if os.path.exists(ng_file):
|
||||
os.remove(ng_file)
|
||||
ap_file = "/www/server/panel/vhost/apache/{}{}.conf".format(config_prefix, site_name)
|
||||
if os.path.exists(ap_file):
|
||||
os.remove(ap_file)
|
||||
# 访问限制
|
||||
RealAccessRestriction(config_prefix=config_prefix).remove_site_access_restriction_info(site_name)
|
||||
# 反向代理
|
||||
RealProxy(config_prefix=config_prefix).remove_site_proxy_info(site_name)
|
||||
# 重定向
|
||||
RealRedirect(config_prefix=config_prefix).remove_site_redirect_info(site_name)
|
||||
# 防盗链
|
||||
RealReferer(config_prefix=config_prefix).remove_site_referer_info(site_name)
|
||||
# 证书目录
|
||||
cert_path = "/www/server/panel/vhost/cert/" + site_name
|
||||
if os.path.isdir(cert_path):
|
||||
shutil.rmtree(cert_path)
|
||||
# IP黑白名单
|
||||
RealIpRestrict(config_prefix=config_prefix).remove_site_ip_restrict_info(site_name)
|
||||
# 历史配置文件
|
||||
ConfigMgr(site_name=site_name, config_prefix=config_prefix).clear_history_file()
|
||||
# 默认站点
|
||||
d_site_name, d_prefix = get_default_site()
|
||||
if d_site_name == site_name and d_prefix == config_prefix:
|
||||
d_file = "/www/server/panel/data/mod_default_site.pl"
|
||||
f = open(d_file, mode="w+")
|
||||
json.dump({"name": None, "prefix": None}, f)
|
||||
|
||||
# 日志格式配置记录
|
||||
RealLogMgr(conf_prefix=config_prefix).remove_site_log_format_info(site_name)
|
||||
|
||||
# 伪静态
|
||||
rewrite_path = "/www/server/panel/vhost/rewrite/{}{}.conf".format(config_prefix, site_name)
|
||||
if os.path.isdir(rewrite_path):
|
||||
os.remove(rewrite_path)
|
||||
BIN
mod/base/web_conf/__pycache__/ssl.cpython-314.pyc
Normal file
BIN
mod/base/web_conf/__pycache__/ssl.cpython-314.pyc
Normal file
Binary file not shown.
608
mod/base/web_conf/access_restriction.py
Normal file
608
mod/base/web_conf/access_restriction.py
Normal file
@@ -0,0 +1,608 @@
|
||||
# 访问限制, 目前不兼容之前版本的访问限制
|
||||
# nginx 使用 if 和 正则实现,保障与反向代理、重定向的兼容性
|
||||
# apache 实现方案未变
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import shutil
|
||||
import warnings
|
||||
from typing import Optional, Union, List, Dict
|
||||
from itertools import chain
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key
|
||||
from mod.base import json_response
|
||||
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
||||
|
||||
class _ConfigObject:
|
||||
_config_file_path = ""
|
||||
panel_path = "/www/server/panel"
|
||||
|
||||
def __init__(self):
|
||||
self._config: Optional[dict] = None
|
||||
|
||||
@property
|
||||
def config(self) -> Dict[str, dict]:
|
||||
if self._config is None:
|
||||
try:
|
||||
self._config = json.loads(read_file(self._config_file_path))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
self._config = {}
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
if self._config:
|
||||
write_file(self._config_file_path, json.dumps(self._config))
|
||||
|
||||
|
||||
class ServerConfig:
|
||||
_vhost_path = "/www/server/panel/vhost"
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
self.config_prefix: str = config_prefix
|
||||
|
||||
@staticmethod
|
||||
def crypt_password(password) -> str:
|
||||
import crypt
|
||||
return crypt.crypt(password,password)
|
||||
|
||||
|
||||
# nginx配置文件相关操作
|
||||
class _NginxAccessConf(ServerConfig):
|
||||
|
||||
# 添加 include 导入配置项
|
||||
def set_nginx_access_include(self, site_name) -> Optional[str]:
|
||||
ng_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, site_name)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not ng_conf:
|
||||
return "配置文件丢失"
|
||||
access_dir = "{}/nginx/access/{}".format(self._vhost_path, site_name)
|
||||
if not os.path.isdir(os.path.dirname(access_dir)):
|
||||
os.makedirs(os.path.dirname(access_dir))
|
||||
|
||||
if not os.path.isdir(access_dir):
|
||||
os.makedirs(access_dir)
|
||||
|
||||
include_conf = (
|
||||
" #引用访问限制规则,注释后配置的访问限制将无效\n"
|
||||
" include /www/server/panel/vhost/nginx/access/%s/*.conf;\n"
|
||||
) % site_name
|
||||
|
||||
rep_include = re.compile(r"\s*include.*/access/.*/\*\.conf\s*;", re.M)
|
||||
if rep_include.search(ng_conf):
|
||||
return
|
||||
# 添加 引入
|
||||
rep_list = [
|
||||
(re.compile(r"#SSL-END"), False), # 匹配Referer配置, 加其下
|
||||
(re.compile(r"(\s*#.*)?\s*include\s+.*/redirect/.*\.conf;"), True), # 重定向
|
||||
(re.compile(r"(\s*#.*)?\s*include\s+.*/ip-restrict/.*\.conf;"), True), # Ip黑白名单
|
||||
]
|
||||
|
||||
# 使用正则匹配确定插入位置 use_start 在前面插入还是后面插入
|
||||
def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool:
|
||||
tmp_res = tmp_rep.search(ng_conf)
|
||||
if not tmp_res:
|
||||
return False
|
||||
if use_start:
|
||||
new_conf = ng_conf[:tmp_res.start()] + include_conf + tmp_res.group() + ng_conf[tmp_res.end():]
|
||||
else:
|
||||
new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + include_conf + ng_conf[tmp_res.end():]
|
||||
|
||||
write_file(ng_file, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return False
|
||||
return True
|
||||
for r, s in rep_list:
|
||||
if set_by_rep_idx(r, s):
|
||||
break
|
||||
else:
|
||||
return "无法在配置文件中定位到需要添加的项目"
|
||||
|
||||
# 写入配置文件
|
||||
def set_nginx_access_by_conf(self, site_name: str, configs: Dict[str, List[Dict[str, str]]]) -> Optional[str]:
|
||||
""" configs 示例结构
|
||||
configs = {
|
||||
"auth_dir": [
|
||||
{
|
||||
"name": "aaa",
|
||||
"dir_path": "/",
|
||||
"auth_file": "/www/server/pass/www.cache.com/aaa.pass",
|
||||
"username":"aaaa",
|
||||
"password":"aaaa",
|
||||
}
|
||||
],
|
||||
"file_deny": [
|
||||
{
|
||||
"name": "bbb",
|
||||
"dir_path": "/",
|
||||
"suffix": ["png", "jpg"]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
path_map = {}
|
||||
for c in chain(configs.get("auth_dir", []), configs.get("file_deny", [])):
|
||||
if c["dir_path"] not in path_map:
|
||||
path_map[c["dir_path"]] = {"path": c["dir_path"]}
|
||||
path_map[c["dir_path"]].update(c)
|
||||
|
||||
path_list = list(path_map.values())
|
||||
path_list.sort(key=lambda x: len(x["path"].split("/")), reverse=True)
|
||||
conf_template = r"""location ~ "^%s.*$" {
|
||||
auth_basic "Authorization";
|
||||
auth_basic_user_file %s;
|
||||
%s
|
||||
}
|
||||
"""
|
||||
suffix_template = r'{tmp_pre}if ( $uri ~ "\.({suffix})$" ) {{\n{tmp_pre} return 404;\n{tmp_pre}}}'
|
||||
suffix_template2 = r'if ( $uri ~ "^{path}.*\.({suffix})$" ) {{\n return 404;\n}}\n'
|
||||
tmp_conf_list = []
|
||||
for i in path_list:
|
||||
if "auth_file" in i and "suffix" in i:
|
||||
tmp_pre = " "
|
||||
tmp_conf = conf_template % (
|
||||
i["path"], i["auth_file"], suffix_template.format(tmp_pre=tmp_pre, suffix="|".join(i["suffix"]))
|
||||
)
|
||||
write_file(i["auth_file"], "{}:{}".format(i["username"], self.crypt_password(i["password"])))
|
||||
|
||||
elif "auth_file" in i:
|
||||
tmp_conf = conf_template % (i["path"], i["auth_file"], "")
|
||||
write_file(i["auth_file"], "{}:{}".format(i["username"], self.crypt_password(i["password"])))
|
||||
else:
|
||||
tmp_conf = suffix_template2.format(path=i["path"], suffix="|".join(i["suffix"]))
|
||||
|
||||
tmp_conf_list.append(tmp_conf)
|
||||
|
||||
config_data = "\n".join(tmp_conf_list)
|
||||
config_file = "{}/nginx/access/{}/{}{}.conf".format(self._vhost_path, site_name, self.config_prefix, site_name)
|
||||
old_config = read_file(config_file)
|
||||
write_file(config_file, config_data)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
if isinstance(old_config, str):
|
||||
write_file(config_file, old_config)
|
||||
else:
|
||||
write_file(config_file, "")
|
||||
return "配置失败"
|
||||
|
||||
|
||||
class _ApacheAccessConf(ServerConfig):
|
||||
|
||||
def set_apache_access_include(self, site_name) -> Optional[str]:
|
||||
ap_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, site_name)
|
||||
ap_conf = read_file(ap_file)
|
||||
if not ap_conf:
|
||||
return "配置文件丢失"
|
||||
access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name)
|
||||
if not os.path.isdir(os.path.dirname(access_dir)):
|
||||
os.makedirs(os.path.dirname(access_dir))
|
||||
|
||||
if not os.path.isdir(access_dir):
|
||||
os.makedirs(access_dir)
|
||||
|
||||
pass_dir = "/www/server/pass/" + site_name
|
||||
if not os.path.isdir(os.path.dirname(pass_dir)):
|
||||
os.makedirs(os.path.dirname(pass_dir))
|
||||
|
||||
if not os.path.isdir(pass_dir):
|
||||
os.makedirs(pass_dir)
|
||||
|
||||
include_conf = (
|
||||
"\n #引用访问限制规则,注释后配置的访问限制将无效\n"
|
||||
" IncludeOptional /www/server/panel/vhost/apache/access/%s/*.conf\n"
|
||||
) % site_name
|
||||
|
||||
rep_include = re.compile(r"\s*IncludeOptional.*/access/.*/\*\.conf", re.M)
|
||||
if rep_include.search(ap_conf):
|
||||
return
|
||||
# 添加 引入
|
||||
rep_vhost_r = re.compile(r"</VirtualHost>")
|
||||
new_conf = rep_vhost_r.sub(include_conf + "</VirtualHost>", ap_conf)
|
||||
if not rep_include.search(new_conf):
|
||||
return "配置添加失败"
|
||||
|
||||
write_file(ap_file, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return "配置添加失败"
|
||||
|
||||
def set_apache_access_by_conf(self, site_name: str, configs: Dict[str, List[Dict[str, str]]]) -> Optional[str]:
|
||||
""" configs 示例结构
|
||||
configs = {
|
||||
"auth_dir": [
|
||||
{
|
||||
"name": "aaa",
|
||||
"dir_path": "/",
|
||||
"auth_file": "/www/server/pass/www.cache.com/aaa.pass",
|
||||
"username":"aaaa",
|
||||
"password":"aaaa",
|
||||
}
|
||||
],
|
||||
"file_deny": [
|
||||
{
|
||||
"name": "bbb",
|
||||
"dir_path": "/",
|
||||
"suffix": ["png", "jpg"]
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
site_path = DB("sites").where("name=?", (site_name, )).find()["path"]
|
||||
names = []
|
||||
old_configs = []
|
||||
access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name)
|
||||
for i in os.listdir(access_dir):
|
||||
if not os.path.isfile(os.path.join(access_dir, i)):
|
||||
continue
|
||||
old_configs.append((i, read_file(os.path.join(access_dir, i))))
|
||||
|
||||
for c in chain(configs.get("auth_dir", []), configs.get("file_deny", [])):
|
||||
if "suffix" in c:
|
||||
self._set_apache_file_deny(c, site_name)
|
||||
names.append("deny_{}.conf".format(c["name"]))
|
||||
else:
|
||||
self._set_apache_auth_dir(c, site_name, site_path)
|
||||
names.append("auth_{}.conf".format(c["name"]))
|
||||
|
||||
for i in os.listdir(access_dir):
|
||||
if i not in names:
|
||||
os.remove(os.path.join(access_dir, i))
|
||||
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
for i in os.listdir(access_dir):
|
||||
os.remove(os.path.join(access_dir, i))
|
||||
for n, data in old_configs: # 还原之前的配置文件
|
||||
write_file(os.path.join(access_dir, n), data)
|
||||
return "配置保存失败"
|
||||
|
||||
def _set_apache_file_deny(self, data: dict, site_name: str):
|
||||
conf = r'''
|
||||
#BEGIN_DENY_{n}
|
||||
<Directory ~ "{d}.*\.({s})$">
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
</Directory>
|
||||
#END_DENY_{n}
|
||||
'''.format(n=data["name"], d=data["dir_path"], s="|".join(data["suffix"]))
|
||||
access_file = "{}/apache/access/{}/deny_{}.conf".format(self._vhost_path, site_name, data["name"])
|
||||
write_file(access_file, conf)
|
||||
|
||||
def _set_apache_auth_dir(self, data: dict, site_path: str, site_name: str):
|
||||
conf = '''
|
||||
<Directory "{site_path}{site_dir}">
|
||||
#AUTH_START
|
||||
AuthType basic
|
||||
AuthName "Authorization "
|
||||
AuthUserFile {auth_file}
|
||||
Require user {username}
|
||||
#AUTH_END
|
||||
SetOutputFilter DEFLATE
|
||||
Options FollowSymLinks
|
||||
AllowOverride All
|
||||
#Require all granted
|
||||
DirectoryIndex index.php index.html index.htm default.php default.html default.htm
|
||||
</Directory>'''.format(site_path=site_path, site_dir=data["dir_path"], auth_file=data["auth_file"],
|
||||
username=data["username"], site_name=site_name)
|
||||
write_file(data["auth_file"], "{}:{}".format(data["username"], self.crypt_password(data["password"])))
|
||||
access_file = "{}/apache/access/{}/auth_{}.conf".format(self._vhost_path, site_path, data["name"])
|
||||
write_file(access_file, conf)
|
||||
|
||||
|
||||
class RealAccessRestriction(_ConfigObject, _ApacheAccessConf, _NginxAccessConf):
|
||||
_config_file_path = "/www/server/panel/data/site_access.json"
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
super(RealAccessRestriction, self).__init__()
|
||||
super(_ApacheAccessConf, self).__init__(config_prefix)
|
||||
|
||||
# 把配置信息更新到服务配置文件中
|
||||
def _refresh_web_server_conf(self, site_name: str, site_access_conf: dict, web_server=None) -> Optional[str]:
|
||||
if web_server is None:
|
||||
web_server = webserver()
|
||||
error_msg = self.set_apache_access_by_conf(site_name, site_access_conf)
|
||||
if web_server == "apache" and error_msg is not None:
|
||||
return error_msg
|
||||
error_msg = self.set_nginx_access_by_conf(site_name, site_access_conf)
|
||||
if web_server == "nginx" and error_msg is not None:
|
||||
return error_msg
|
||||
|
||||
# 添加include配置到对应站点的配置文件中
|
||||
def _set_web_server_conf_include(self, site_name, web_server=None) -> Optional[str]:
|
||||
if web_server is None:
|
||||
web_server = webserver()
|
||||
error_msg = self.set_apache_access_include(site_name)
|
||||
if web_server == "apache" and error_msg is not None:
|
||||
return error_msg
|
||||
error_msg = self.set_nginx_access_include(site_name)
|
||||
if web_server == "nginx" and error_msg is not None:
|
||||
return error_msg
|
||||
|
||||
def check_auth_dir_args(self, get, is_modify=False) -> Union[str, dict]:
|
||||
values = {}
|
||||
try:
|
||||
values["site_name"] = get.site_name.strip()
|
||||
values["dir_path"] = get.dir_path.strip()
|
||||
except AttributeError:
|
||||
return "parameter error"
|
||||
|
||||
if hasattr(get, "password"):
|
||||
password = get.password.strip()
|
||||
if len(password) < 3:
|
||||
return '密码不能少于3位'
|
||||
if re.search(r'\s', password):
|
||||
return '密码不能存在空格'
|
||||
values['password'] = password
|
||||
else:
|
||||
return '请输入密码!'
|
||||
|
||||
if hasattr(get, "username"):
|
||||
username = get.username.strip()
|
||||
if len(username) < 3:
|
||||
return '账号不能少于3位'
|
||||
if re.search(r'\s', username):
|
||||
return '账号不能存在空格'
|
||||
values['username'] = username
|
||||
else:
|
||||
return '请输入用户!'
|
||||
|
||||
if hasattr(get, "name"):
|
||||
name = get.name.strip()
|
||||
if len(name) < 3:
|
||||
return '名称不能少于3位'
|
||||
if re.search(r'\s', name):
|
||||
return '名称不能存在空格'
|
||||
if not re.search(r'^\w+$', name):
|
||||
return '名称格式错误,仅支持数字字母下划线,请参考格式:aaa_bbb'
|
||||
values['name'] = name
|
||||
else:
|
||||
return '请输入名称!'
|
||||
if not is_modify:
|
||||
data = self.config.get(values["site_name"], {}).get("auth_dir", [])
|
||||
for i in data:
|
||||
if i["dir_path"] == values["dir_path"]:
|
||||
return "此路径已存在"
|
||||
if i["name"] == values["name"]:
|
||||
return "此名称已存在"
|
||||
|
||||
values["auth_file"] = "/www/server/pass/{}/{}.pass".format(values["site_name"], values["name"])
|
||||
return values
|
||||
|
||||
def create_auth_dir(self, get) -> Optional[str]:
|
||||
conf = self.check_auth_dir_args(get, is_modify=False)
|
||||
if isinstance(conf, str):
|
||||
return conf
|
||||
|
||||
web_server = webserver()
|
||||
error_msg = self._set_web_server_conf_include(conf["site_name"], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
|
||||
if conf["site_name"] not in self.config:
|
||||
self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []}
|
||||
self.config[conf["site_name"]]["auth_dir"].append(conf)
|
||||
|
||||
error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def modify_auth_dir(self, get) -> Optional[str]:
|
||||
conf = self.check_auth_dir_args(get, is_modify=True)
|
||||
if isinstance(conf, str):
|
||||
return conf
|
||||
|
||||
data = self.config.get(conf["site_name"], {}).get("auth_dir", [])
|
||||
target_idx = None
|
||||
for idx, i in enumerate(data):
|
||||
if i["name"] == conf["name"]:
|
||||
target_idx = idx
|
||||
break
|
||||
if target_idx is None:
|
||||
return "没有指定的配置信息"
|
||||
web_server = webserver()
|
||||
error_msg = self._set_web_server_conf_include(conf["site_name"], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
if conf["site_name"] not in self.config:
|
||||
self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []}
|
||||
|
||||
self.config[conf["site_name"]]["auth_dir"][target_idx] = conf
|
||||
|
||||
error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def remove_auth_dir(self, site_name: str, name: str) -> Optional[str]:
|
||||
if site_name not in self.config:
|
||||
return "没有该网站的配置"
|
||||
|
||||
target = None
|
||||
for idx, i in enumerate(self.config[site_name].get("auth_dir", [])):
|
||||
if i.get("name", None) == name:
|
||||
target = idx
|
||||
|
||||
if target is None:
|
||||
return "没有该路径的配置"
|
||||
|
||||
del self.config[site_name]["auth_dir"][target]
|
||||
web_server = webserver()
|
||||
error_msg = self._refresh_web_server_conf(site_name, self.config[site_name], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return
|
||||
|
||||
def check_file_deny_args(self, get, is_modify=False) -> Union[str, dict]:
|
||||
values = {}
|
||||
try:
|
||||
values["site_name"] = get.site_name.strip()
|
||||
values["name"] = get.name.strip()
|
||||
values["dir_path"] = get.dir_path.strip()
|
||||
values["suffix"] = list(filter(lambda x: bool(x.strip()), json.loads(get.suffix.strip())))
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return "Parameter error"
|
||||
|
||||
if len(values["name"]) < 3:
|
||||
return '规则名最少需要输入3个字符串!'
|
||||
if not values["suffix"]:
|
||||
return '文件扩展名不可为空!'
|
||||
if not values["dir_path"]:
|
||||
return '目录不可为空!'
|
||||
|
||||
if not is_modify:
|
||||
data = self.config.get(values["site_name"], {}).get("file_deny", [])
|
||||
for i in data:
|
||||
if i["dir_path"] == values["dir_path"]:
|
||||
return "此路径已存在"
|
||||
if i["name"] == values["name"]:
|
||||
return "此名称已存在"
|
||||
return values
|
||||
|
||||
def create_file_deny(self, get) -> Optional[str]:
|
||||
conf = self.check_file_deny_args(get, is_modify=False)
|
||||
if isinstance(conf, str):
|
||||
return conf
|
||||
web_server = webserver()
|
||||
error_msg = self._set_web_server_conf_include(conf["site_name"], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
if conf["site_name"] not in self.config:
|
||||
self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []}
|
||||
|
||||
self.config[conf["site_name"]]["file_deny"].append(conf)
|
||||
error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def modify_file_deny(self, get) -> Optional[str]:
|
||||
conf = self.check_file_deny_args(get, is_modify=True)
|
||||
if isinstance(conf, str):
|
||||
return conf
|
||||
|
||||
data = self.config.get(conf["site_name"], {}).get("file_deny", [])
|
||||
target_idx = None
|
||||
for idx, i in enumerate(data):
|
||||
if i["name"] == conf["name"]:
|
||||
target_idx = idx
|
||||
break
|
||||
if target_idx is None:
|
||||
return "没有指定的配置信息"
|
||||
web_server = webserver()
|
||||
error_msg = self._set_web_server_conf_include(conf["site_name"], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
if conf["site_name"] not in self.config:
|
||||
self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []}
|
||||
|
||||
self.config[conf["site_name"]]["file_deny"][target_idx] = conf
|
||||
|
||||
error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def remove_file_deny(self, site_name: str, name: str) -> Optional[str]:
|
||||
if site_name not in self.config:
|
||||
return "没有该网站的配置"
|
||||
|
||||
target = None
|
||||
for idx, i in enumerate(self.config[site_name].get("file_deny", [])):
|
||||
if i.get("name", None) == name:
|
||||
target = idx
|
||||
|
||||
if target is None:
|
||||
return "没有该路径的配置"
|
||||
|
||||
del self.config[site_name]["file_deny"][target]
|
||||
web_server = webserver()
|
||||
error_msg = self._refresh_web_server_conf(site_name, self.config[site_name], web_server)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return
|
||||
|
||||
def site_access_restriction_info(self, site_name: str) -> dict:
|
||||
if site_name not in self.config:
|
||||
return {"auth_dir": [], "file_deny": []}
|
||||
else:
|
||||
return self.config[site_name]
|
||||
|
||||
def remove_site_access_restriction_info(self, site_name):
|
||||
if site_name in self.config:
|
||||
del self.config["site_name"]
|
||||
self.save_config()
|
||||
ng_access_dir = "{}/nginx/access/{}".format(self._vhost_path, site_name)
|
||||
ap_access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name)
|
||||
if os.path.isdir(ng_access_dir):
|
||||
shutil.rmtree(ng_access_dir)
|
||||
|
||||
if os.path.isdir(ap_access_dir):
|
||||
shutil.rmtree(ap_access_dir)
|
||||
|
||||
|
||||
class AccessRestriction:
|
||||
|
||||
def __init__(self, config_prefix: str = ""):
|
||||
self.config_prefix: str = config_prefix
|
||||
self._ar = RealAccessRestriction(config_prefix)
|
||||
|
||||
def create_auth_dir(self, get):
|
||||
res = self._ar.create_auth_dir(get)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="Successfully added")
|
||||
|
||||
def modify_auth_dir(self, get):
|
||||
res = self._ar.modify_auth_dir(get)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="修改成功")
|
||||
|
||||
def remove_auth_dir(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
name = get.name.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="请求参数错误")
|
||||
res = self._ar.remove_auth_dir(site_name, name)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="Successfully delete")
|
||||
|
||||
def create_file_deny(self, get):
|
||||
res = self._ar.create_file_deny(get)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="Successfully added")
|
||||
|
||||
def modify_file_deny(self, get):
|
||||
res = self._ar.modify_file_deny(get)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="修改成功")
|
||||
|
||||
def remove_file_deny(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
name = get.name.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="请求参数错误")
|
||||
res = self._ar.remove_file_deny(site_name, name)
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, msg=res)
|
||||
return json_response(status=True, msg="Successfully delete")
|
||||
|
||||
def site_access_restriction_info(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="请求参数错误")
|
||||
data = self._ar.site_access_restriction_info(site_name)
|
||||
return json_response(status=True, data=data)
|
||||
154
mod/base/web_conf/config_mgr.py
Normal file
154
mod/base/web_conf/config_mgr.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import time
|
||||
from hashlib import md5
|
||||
from typing import Optional
|
||||
from .util import service_reload, check_server_config, write_file, read_file
|
||||
|
||||
|
||||
# 支持读取配置文件
|
||||
# 保存并重启配置文件
|
||||
# 历史文件记录
|
||||
class ConfigMgr:
|
||||
_vhost_path = "/www/server/panel/vhost"
|
||||
|
||||
def __init__(self, site_name: str, config_prefix: str = ""):
|
||||
self.site_name = site_name
|
||||
self.config_prefix = config_prefix
|
||||
|
||||
def _read_config(self, web_server: str) -> Optional[str]:
|
||||
config_file = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name)
|
||||
res = read_file(config_file)
|
||||
if isinstance(res, str):
|
||||
return res
|
||||
return None
|
||||
|
||||
def nginx_config(self) -> Optional[str]:
|
||||
return self._read_config("nginx")
|
||||
|
||||
def apache_config(self) -> Optional[str]:
|
||||
return self._read_config("apache")
|
||||
|
||||
def save_config(self, conf_data: str, web_server: str):
|
||||
config_file = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name)
|
||||
old_config = self._read_config(web_server)
|
||||
write_file(config_file, conf_data)
|
||||
errmsg = check_server_config()
|
||||
if errmsg:
|
||||
write_file(config_file, old_config)
|
||||
return errmsg
|
||||
self._save_history(web_server)
|
||||
service_reload()
|
||||
|
||||
def save_nginx_config(self, conf_data: str) -> Optional[str]:
|
||||
return self.save_config(conf_data, "nginx")
|
||||
|
||||
def save_apache_config(self, conf_data: str) -> Optional[str]:
|
||||
return self.save_config(conf_data, "apache")
|
||||
|
||||
def history_list(self):
|
||||
his_path = '/www/backup/file_history'
|
||||
nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ng_save_path = "{}{}".format(his_path, nginx_config_file)
|
||||
apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ap_save_path = "{}{}".format(his_path, apache_config_file)
|
||||
return {
|
||||
"nginx": [] if not os.path.isdir(ng_save_path) else sorted(os.listdir(ng_save_path), reverse=True),
|
||||
"apache": [] if not os.path.isdir(ap_save_path) else sorted(os.listdir(ap_save_path), reverse=True)
|
||||
}
|
||||
|
||||
def history_conf(self, history_id: str) -> Optional[str]:
|
||||
his_path = '/www/backup/file_history'
|
||||
nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ng_save_path = "{}{}".format(his_path, nginx_config_file)
|
||||
if os.path.isdir(ng_save_path):
|
||||
for i in os.listdir(ng_save_path):
|
||||
if i == history_id:
|
||||
return read_file(os.path.join(ng_save_path, i))
|
||||
|
||||
apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ap_save_path = "{}{}".format(his_path, apache_config_file)
|
||||
if os.path.isdir(ap_save_path):
|
||||
for i in os.listdir(ap_save_path):
|
||||
if i == history_id:
|
||||
return read_file(os.path.join(ap_save_path, i))
|
||||
return None
|
||||
|
||||
def remove_history_file(self, history_id: str) -> None:
|
||||
his_path = '/www/backup/file_history'
|
||||
nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ng_save_path = "{}{}".format(his_path, nginx_config_file)
|
||||
if os.path.isdir(ng_save_path):
|
||||
for i in os.listdir(ng_save_path):
|
||||
if i == history_id:
|
||||
os.remove(os.path.join(ng_save_path, i))
|
||||
|
||||
apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ap_save_path = "{}{}".format(his_path, apache_config_file)
|
||||
if os.path.isdir(ap_save_path):
|
||||
for i in os.listdir(ap_save_path):
|
||||
if i == history_id:
|
||||
os.remove(os.path.join(ng_save_path, i))
|
||||
|
||||
def clear_history_file(self) -> None:
|
||||
"""
|
||||
清空所有的历史文件
|
||||
"""
|
||||
his_path = '/www/backup/file_history'
|
||||
nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ng_save_path = "{}{}".format(his_path, nginx_config_file)
|
||||
if os.path.isdir(ng_save_path):
|
||||
for i in os.listdir(ng_save_path):
|
||||
os.remove(os.path.join(ng_save_path, i))
|
||||
|
||||
apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name)
|
||||
ap_save_path = "{}{}".format(his_path, apache_config_file)
|
||||
if os.path.isdir(ap_save_path):
|
||||
for i in os.listdir(ap_save_path):
|
||||
os.remove(os.path.join(ng_save_path, i))
|
||||
|
||||
@staticmethod
|
||||
def _file_md5(filename):
|
||||
if not os.path.isfile(filename):
|
||||
return False
|
||||
md5_obj = md5()
|
||||
with open(filename, mode="rb") as f:
|
||||
while True:
|
||||
b = f.read(8096)
|
||||
if not b:
|
||||
break
|
||||
md5_obj.update(b)
|
||||
|
||||
return md5_obj.hexdigest()
|
||||
|
||||
def _save_history(self, web_server: str):
|
||||
if os.path.exists('/www/server/panel/data/not_file_history.pl'):
|
||||
return True
|
||||
|
||||
his_path = '/www/backup/file_history'
|
||||
filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name)
|
||||
save_path = "{}{}".format(his_path, filename)
|
||||
if not os.path.isdir(save_path):
|
||||
os.makedirs(save_path, 384)
|
||||
|
||||
his_list = sorted(os.listdir(save_path), reverse=True) # 倒序排列已有的历史文件
|
||||
try:
|
||||
num = int(read_file('data/history_num.pl'))
|
||||
except (ValueError, TypeError):
|
||||
num = 100
|
||||
|
||||
is_write = True
|
||||
if len(his_list) > 0:
|
||||
new_file_md5 = self._file_md5(filename)
|
||||
last_file_md5 = self._file_md5(os.path.join(save_path, his_list[0]))
|
||||
is_write = new_file_md5 != last_file_md5
|
||||
|
||||
if is_write:
|
||||
new_name = str(int(time.time()))
|
||||
write_file(os.path.join(save_path, new_name), read_file(filename, 'rb'), "wb")
|
||||
his_list.insert(0, new_name)
|
||||
|
||||
# 删除多余的副本
|
||||
for i in his_list[num:]:
|
||||
rm_file = save_path + '/' + i
|
||||
if os.path.exists(rm_file):
|
||||
os.remove(rm_file)
|
||||
136
mod/base/web_conf/default_site.py
Normal file
136
mod/base/web_conf/default_site.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
from .util import listen_ipv6, write_file, read_file, service_reload
|
||||
|
||||
|
||||
def check_default():
|
||||
vhost_path = "/www/server/panel/vhost"
|
||||
nginx = vhost_path + '/nginx'
|
||||
httpd = vhost_path + '/apache'
|
||||
httpd_default = '''<VirtualHost *:80>
|
||||
ServerAdmin webmaster@example.com
|
||||
DocumentRoot "/www/server/apache/htdocs"
|
||||
ServerName bt.default.com
|
||||
<Directory "/www/server/apache/htdocs">
|
||||
SetOutputFilter DEFLATE
|
||||
Options FollowSymLinks
|
||||
AllowOverride All
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
DirectoryIndex index.html
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
'''
|
||||
|
||||
listen_ipv6_str = ''
|
||||
if listen_ipv6():
|
||||
listen_ipv6_str = "\n listen [::]:80;"
|
||||
|
||||
nginx_default = '''server
|
||||
{
|
||||
listen 80;%s
|
||||
server_name _;
|
||||
index index.html;
|
||||
root /www/server/nginx/html;
|
||||
}''' % listen_ipv6_str
|
||||
|
||||
if not os.path.exists(httpd + '/0.default.conf') and not os.path.exists(httpd + '/default.conf'):
|
||||
write_file(httpd + '/0.default.conf', httpd_default)
|
||||
if not os.path.exists(nginx + '/0.default.conf') and not os.path.exists(nginx + '/default.conf'):
|
||||
write_file(nginx + '/0.default.conf', nginx_default)
|
||||
|
||||
|
||||
def get_default_site() -> Tuple[Optional[str], Optional[str]]:
|
||||
panel_path = "/www/server/panel"
|
||||
|
||||
old_ds_file = panel_path + "/data/defaultSite.pl"
|
||||
new_ds_file = panel_path + "/data/mod_default_site.pl"
|
||||
if os.path.exists(old_ds_file) and not os.path.exists(new_ds_file):
|
||||
write_file(new_ds_file, json.dumps({
|
||||
"name": read_file(old_ds_file).strip(),
|
||||
"prefix": ''
|
||||
}))
|
||||
|
||||
res = read_file(new_ds_file)
|
||||
if not isinstance(res, str):
|
||||
return None, None
|
||||
data = json.loads(res)
|
||||
return data["name"], data["prefix"]
|
||||
|
||||
|
||||
# site_name 传递None的时候,表示将默认站点设置给关闭
|
||||
# prefix 表示配置文件前缀, 如 "net_", 默认为空字符串
|
||||
# domain 站点的域名 如: "www.sss.com:8456"
|
||||
def set_default_site(site_name: Optional[str], prefix="", domain: str = None) -> Optional[str]:
|
||||
# 清理旧的
|
||||
old_default_name, old_prefix = get_default_site()
|
||||
panel_path = "/www/server/panel"
|
||||
default_site_save = panel_path + '/data/mod_default_site.pl'
|
||||
if old_default_name:
|
||||
ng_conf_file = os.path.join(panel_path, "vhost/nginx/{}{}.conf".format(old_prefix, old_default_name))
|
||||
old_conf = read_file(ng_conf_file)
|
||||
if isinstance(old_conf, str):
|
||||
rep_listen_ds = re.compile(r"listen\s+.*default_server.*;")
|
||||
new_conf_list = []
|
||||
start_idx = 0
|
||||
for tmp_res in rep_listen_ds.finditer(old_conf):
|
||||
new_conf_list.append(old_conf[start_idx: tmp_res.start()])
|
||||
new_conf_list.append(tmp_res.group().replace("default_server", ""))
|
||||
start_idx = tmp_res.end()
|
||||
|
||||
new_conf_list.append(old_conf[start_idx:])
|
||||
|
||||
write_file(ng_conf_file, "".join(new_conf_list))
|
||||
|
||||
path = '/www/server/apache/htdocs/.htaccess'
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
if site_name is None:
|
||||
write_file(default_site_save, json.dumps({
|
||||
"name": None,
|
||||
"prefix": None
|
||||
}))
|
||||
service_reload()
|
||||
return
|
||||
|
||||
# 处理新的
|
||||
ap_path = '/www/server/apache/htdocs'
|
||||
if os.path.exists(ap_path):
|
||||
conf = '''<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %{{HTTP_HOST}} !^127.0.0.1 [NC]
|
||||
RewriteRule (.*) http://{}/$1 [L]
|
||||
</IfModule>'''.format(domain)
|
||||
|
||||
write_file(ap_path + '/.htaccess', conf)
|
||||
|
||||
ng_conf_file = os.path.join(panel_path, "vhost/nginx/{}{}.conf".format(prefix, site_name))
|
||||
ng_conf = read_file(ng_conf_file)
|
||||
if isinstance(ng_conf, str):
|
||||
rep_listen = re.compile(r"listen[^;]*;")
|
||||
new_conf_list = []
|
||||
|
||||
start_idx = 0
|
||||
for tmp_res in rep_listen.finditer(ng_conf):
|
||||
new_conf_list.append(ng_conf[start_idx: tmp_res.start()])
|
||||
print(tmp_res.group())
|
||||
if tmp_res.group().find("default_server") == -1:
|
||||
new_conf_list.append(tmp_res.group()[:-1] + " default_server;")
|
||||
else:
|
||||
new_conf_list.append(tmp_res.group())
|
||||
start_idx = tmp_res.end()
|
||||
|
||||
new_conf_list.append(ng_conf[start_idx:])
|
||||
|
||||
write_file(ng_conf_file, "".join(new_conf_list))
|
||||
|
||||
write_file(default_site_save, json.dumps({
|
||||
"name": site_name,
|
||||
"prefix": prefix
|
||||
}))
|
||||
|
||||
service_reload()
|
||||
return
|
||||
252
mod/base/web_conf/dir_tool.py
Normal file
252
mod/base/web_conf/dir_tool.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# 网站文件相关操作
|
||||
|
||||
import os
|
||||
import re
|
||||
from typing import Optional, Union, List
|
||||
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, pre_re_key, ExecShell
|
||||
|
||||
|
||||
class DirTool:
|
||||
|
||||
def __init__(self, conf_prefix: str = ""):
|
||||
self.conf_prefix = conf_prefix
|
||||
self._vhost_path = "/www/server/panel/vhost"
|
||||
|
||||
# 修改站点路径
|
||||
def modify_site_path(self, site_name: str, old_site_path: str, new_site_path: str) -> Optional[str]:
|
||||
"""
|
||||
修改 站点root 路径
|
||||
site_name 站点名称
|
||||
old_site_path 旧的root 路径
|
||||
new_site_path 新的root 路径
|
||||
"""
|
||||
site_info = DB("sites").where("name=?", (site_name,)).find()
|
||||
if not isinstance(site_info, dict):
|
||||
return "站点信息查询错误"
|
||||
|
||||
error_msg = check_server_config()
|
||||
if error_msg:
|
||||
return "服务配置无法重载,请检查配置错误再操作。\n" + error_msg
|
||||
|
||||
if not self._check_site_path(new_site_path):
|
||||
return '请不要将网站根目录设置到以下关键目录中'
|
||||
|
||||
if not os.path.exists(new_site_path):
|
||||
return '指定的网站根目录不存在,无法设置,请检查输入信息.'
|
||||
if old_site_path[-1] == '/':
|
||||
old_site_path = old_site_path[:-1]
|
||||
|
||||
if new_site_path[-1] == '/':
|
||||
new_site_path = new_site_path[:-1]
|
||||
|
||||
old_run_path = self.get_site_run_path(site_name)
|
||||
if old_run_path is None:
|
||||
return '读取网站当前运行目录失败,请检查配置文件'
|
||||
old_run_path_sub = old_run_path.replace(old_site_path, "")
|
||||
new_run_path = new_site_path + old_run_path_sub
|
||||
if not os.path.exists(new_site_path):
|
||||
new_run_path = new_site_path
|
||||
nginx_file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
nginx_conf = read_file(nginx_file)
|
||||
if nginx_conf:
|
||||
rep_root = re.compile(r'\s*root\s+(.+);', re.M)
|
||||
new_conf = rep_root.sub(" root {};".format(new_run_path), nginx_conf)
|
||||
write_file(nginx_file, new_conf)
|
||||
|
||||
apache_file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
apache_conf = read_file(apache_file)
|
||||
if apache_conf:
|
||||
rep_doc = re.compile(r"DocumentRoot\s+.*\n")
|
||||
new_conf = rep_doc.sub('DocumentRoot "' + new_run_path + '"\n', apache_conf)
|
||||
|
||||
rep_dir = re.compile(r'''<Directory\s+['"]%s['"]'''% pre_re_key(old_site_path))
|
||||
new_conf = rep_dir.sub('<Directory "' + new_run_path + '">\n', new_conf)
|
||||
write_file(apache_file, new_conf)
|
||||
|
||||
# 创建basedir
|
||||
userIni = new_run_path + '/.user.ini'
|
||||
if os.path.exists(userIni):
|
||||
ExecShell("chattr -i " + userIni)
|
||||
write_file(userIni, 'open_basedir=' + new_run_path + '/:/tmp/')
|
||||
ExecShell('chmod 644 ' + userIni)
|
||||
ExecShell('chown root:root ' + userIni)
|
||||
ExecShell('chattr +i ' + userIni)
|
||||
service_reload()
|
||||
DB("sites").where("id=?", (site_info["id"],)).setField('path', new_site_path)
|
||||
return
|
||||
|
||||
# 修改站点的运行路径
|
||||
def modify_site_run_path(self, site_name, site_path, new_run_path_sub: str) -> Optional[str]:
|
||||
"""
|
||||
修改 站点运行路径
|
||||
site_name 站点名称
|
||||
site_path 站点路径
|
||||
new_run_path_sub root路径的子运行目录
|
||||
如 site_path -> /www/wwwroots/aaaa
|
||||
new_run_path_sub -> bbb/ccc
|
||||
new_run_path -> /www/wwwroots/aaaa/bbb/ccc
|
||||
"""
|
||||
# 处理Nginx
|
||||
old_run_path = self.get_site_run_path(site_name)
|
||||
if old_run_path is None:
|
||||
return '读取网站当前运行目录失败,请检查配置文件'
|
||||
if new_run_path_sub.startswith("/"):
|
||||
new_run_path_sub = new_run_path_sub[1:]
|
||||
new_run_path = os.path.join(site_path, new_run_path_sub)
|
||||
filename = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
nginx_conf = read_file(filename)
|
||||
if nginx_conf:
|
||||
tmp = re.search(r'\s*root\s+(.+);', nginx_conf)
|
||||
if tmp:
|
||||
o_path = tmp.groups()[0]
|
||||
new_conf = nginx_conf.replace(o_path, new_run_path)
|
||||
write_file(filename, new_conf)
|
||||
|
||||
# 处理Apache
|
||||
filename = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
ap_conf = read_file(filename)
|
||||
if ap_conf:
|
||||
tmp = re.search(r'\s*DocumentRoot\s*"(.+)"\s*\n', ap_conf)
|
||||
if tmp:
|
||||
o_path = tmp.groups()[0]
|
||||
new_conf = ap_conf.replace(o_path, new_run_path)
|
||||
write_file(filename, new_conf)
|
||||
|
||||
s_path = old_run_path + "/.user.ini"
|
||||
d_path = new_run_path + "/.user.ini"
|
||||
if s_path != d_path:
|
||||
ExecShell("chattr -i {}".format(s_path))
|
||||
ExecShell("mv {} {}".format(s_path, d_path))
|
||||
ExecShell("chattr +i {}".format(d_path))
|
||||
|
||||
service_reload()
|
||||
|
||||
# 获取站点的运行路径, 返回的路径是完整路径
|
||||
def get_site_run_path(self, site_name) -> Optional[str]:
|
||||
web_server = webserver()
|
||||
filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.conf_prefix, site_name)
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
run_path = None
|
||||
conf = read_file(filename)
|
||||
if web_server == 'nginx':
|
||||
tmp1 = re.search(r'\s*root\s+(?P<path>.+);', conf)
|
||||
if tmp1:
|
||||
run_path = tmp1.group("path").strip()
|
||||
elif web_server == 'apache':
|
||||
tmp1 = re.search(r'\s*DocumentRoot\s*"(?P<path>.+)"\s*\n', conf)
|
||||
if tmp1:
|
||||
run_path = tmp1.group("path")
|
||||
else:
|
||||
tmp1 = re.search(r"vhRoot\s*(?P<path>.*)", conf)
|
||||
if tmp1:
|
||||
run_path = tmp1.group("path").strip()
|
||||
|
||||
return run_path
|
||||
|
||||
# 获取index 文件
|
||||
def get_index_conf(self, site_name) -> Union[str, List[str]]:
|
||||
web_server = webserver()
|
||||
filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.conf_prefix, site_name)
|
||||
if not os.path.exists(filename):
|
||||
return "配置文件丢失"
|
||||
conf = read_file(filename)
|
||||
if not conf:
|
||||
return "配置文件丢失"
|
||||
split_char = " "
|
||||
if web_server == 'nginx':
|
||||
rep = re.compile(r"\s+index\s+(?P<target>.+);", re.M)
|
||||
elif web_server == 'apache':
|
||||
rep = re.compile(r"DirectoryIndex\s+(?P<target>.+)", re.M)
|
||||
else:
|
||||
rep = re.compile(r"indexFiles\s+(?P<target>.+)", re.M)
|
||||
split_char = ","
|
||||
res = rep.search(conf)
|
||||
if not res:
|
||||
return "获取失败,配置文件中不存在默认文档"
|
||||
|
||||
res_list = list(filter(None, map(lambda x: x.strip(), res.group("target").split(split_char))))
|
||||
|
||||
return res_list
|
||||
|
||||
# 获取设置index 文件 可以用 filenames 参数依次传入多个, 或 通过 file_list 参数传入index 列表
|
||||
def set_index_conf(self, site_name, *filenames: str, file_list: Optional[List[str]] = None):
|
||||
index_list = set()
|
||||
for i in filenames:
|
||||
f = i.strip()
|
||||
if not f:
|
||||
continue
|
||||
index_list.add(f)
|
||||
|
||||
if file_list is not None:
|
||||
for i in file_list:
|
||||
f = i.strip()
|
||||
if not f:
|
||||
continue
|
||||
index_list.add(f)
|
||||
|
||||
# nginx
|
||||
file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
conf = read_file(file)
|
||||
if conf:
|
||||
rep_index = re.compile(r"\s*index\s+.+;")
|
||||
new_conf = rep_index.sub(" index {};".format(" ".join(index_list)), conf)
|
||||
write_file(file, new_conf)
|
||||
|
||||
# apache
|
||||
file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
conf = read_file(file)
|
||||
if conf:
|
||||
rep_index = re.compile(r"\s*DirectoryIndex\s+.+\n")
|
||||
new_conf = rep_index.sub(" DirectoryIndex {}\n".format(" ".join(index_list)), conf)
|
||||
write_file(file, new_conf)
|
||||
|
||||
# openlitespeed
|
||||
file = '{}/openlitespeed/detail/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name)
|
||||
conf = read_file(file)
|
||||
if conf:
|
||||
rep_index = re.compile(r"indexFiles\s+.+\n")
|
||||
new_conf = rep_index.sub('indexFiles {}\n'.format(",".join(index_list)), conf)
|
||||
write_file(file, new_conf)
|
||||
|
||||
service_reload()
|
||||
return
|
||||
|
||||
def _check_site_path(self, site_path):
|
||||
try:
|
||||
if site_path.find('/usr/local/lighthouse/') >= 0:
|
||||
return True
|
||||
|
||||
if site_path in ['/', '/usr', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var']:
|
||||
return False
|
||||
whites = ['/www/server/tomcat', '/www/server/stop', '/www/server/phpmyadmin']
|
||||
for w in whites:
|
||||
if site_path.find(w) == 0:
|
||||
return True
|
||||
a, error_paths = self._get_sys_path()
|
||||
site_path = site_path.strip()
|
||||
if site_path[-1] == '/': site_path = site_path[:-1]
|
||||
if site_path in a:
|
||||
return False
|
||||
site_path += '/'
|
||||
for ep in error_paths:
|
||||
if site_path.find(ep) == 0:
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _get_sys_path():
|
||||
"""
|
||||
@name 关键目录
|
||||
@author hwliang<2021-06-11>
|
||||
@return tuple
|
||||
"""
|
||||
a = ['/www', '/usr', '/', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var']
|
||||
c = ['/www/.Recycle_bin/', '/www/backup/', '/www/php_session/', '/www/wwwlogs/', '/www/server/', '/etc/',
|
||||
'/usr/', '/var/', '/boot/', '/proc/', '/sys/', '/tmp/', '/root/', '/lib/', '/bin/', '/sbin/', '/run/',
|
||||
'/lib64/', '/lib32/', '/srv/']
|
||||
return a, c
|
||||
|
||||
1682
mod/base/web_conf/dns_api.py
Normal file
1682
mod/base/web_conf/dns_api.py
Normal file
File diff suppressed because it is too large
Load Diff
335
mod/base/web_conf/domain_tool.py
Normal file
335
mod/base/web_conf/domain_tool.py
Normal file
@@ -0,0 +1,335 @@
|
||||
import os
|
||||
import re
|
||||
from typing import Tuple, Optional, Union, List, Dict
|
||||
|
||||
from .util import webserver, check_server_config, write_file, read_file, service_reload, listen_ipv6, use_http2
|
||||
|
||||
|
||||
def domain_to_puny_code(domain: str) -> str:
|
||||
new_domain = ''
|
||||
for dkey in domain.split('.'):
|
||||
if dkey == '*' or dkey == "":
|
||||
continue
|
||||
# 匹配非ascii字符
|
||||
match = re.search(u"[\x80-\xff]+", dkey)
|
||||
if not match:
|
||||
match = re.search(u"[\u4e00-\u9fa5]+", dkey)
|
||||
if not match:
|
||||
new_domain += dkey + '.'
|
||||
else:
|
||||
new_domain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.'
|
||||
if domain.startswith('*.'):
|
||||
new_domain = "*." + new_domain
|
||||
return new_domain[:-1]
|
||||
|
||||
|
||||
def check_domain(domain: str) -> Optional[str]:
|
||||
domain = domain_to_puny_code(domain)
|
||||
# 判断通配符域名格式
|
||||
if domain.find('*') != -1 and domain.find('*.') == -1:
|
||||
return None
|
||||
|
||||
# 判断域名格式
|
||||
rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$")
|
||||
if not rep_domain.match(domain):
|
||||
return None
|
||||
return domain
|
||||
|
||||
|
||||
def is_domain(domain: str) -> bool:
|
||||
domain_regex = re.compile(
|
||||
r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
|
||||
re.IGNORECASE
|
||||
)
|
||||
return True if domain_regex.match(domain) else False
|
||||
|
||||
|
||||
# 检查原始的域名列表,返回[(domain, port)] 的格式,并返回其中有错误的项目
|
||||
def normalize_domain(*domains: str) -> Tuple[List[Tuple[str, str]], List[Dict]]:
|
||||
res, error = [], []
|
||||
for i in domains:
|
||||
if not i.strip():
|
||||
continue
|
||||
d_list = [i.strip() for i in i.split(":")]
|
||||
if len(d_list) > 1:
|
||||
try:
|
||||
p = int(d_list[1])
|
||||
if not (1 < p < 65535):
|
||||
error.append({
|
||||
"domain": i,
|
||||
"msg": "端口范围错误"
|
||||
})
|
||||
continue
|
||||
else:
|
||||
d_list[1] = str(p)
|
||||
except:
|
||||
error.append({
|
||||
"domain": i,
|
||||
"msg": "端口范围错误"
|
||||
})
|
||||
continue
|
||||
else:
|
||||
d_list.append("80")
|
||||
d, p = d_list
|
||||
d = check_domain(d)
|
||||
if isinstance(d, str):
|
||||
res.append((d, p)),
|
||||
continue
|
||||
error.append({
|
||||
"domain": i,
|
||||
"msg": "域名格式错误"
|
||||
})
|
||||
|
||||
res = list(set(res))
|
||||
return res, error
|
||||
|
||||
|
||||
class NginxDomainTool:
|
||||
ng_vhost = "/www/server/panel/vhost/nginx"
|
||||
|
||||
def __init__(self, conf_prefix: str = ""):
|
||||
self.conf_prefix = conf_prefix
|
||||
|
||||
# 在给定的配置文件中添加端口
|
||||
@staticmethod
|
||||
def nginx_add_port_by_config(conf, *port: str, is_http3=False) -> str:
|
||||
ports = set()
|
||||
for p in port:
|
||||
ports.add(p)
|
||||
|
||||
# 设置端口
|
||||
rep_port = re.compile(r"\s*listen\s+[\[\]:]*(?P<port>[0-9]+)(?P<ds>\s*default_server)?.*;[^\n]*\n", re.M)
|
||||
use_ipv6 = listen_ipv6()
|
||||
last_port_idx = None
|
||||
need_remove_port_idx = []
|
||||
had_ports = set()
|
||||
is_default_server = False
|
||||
for tmp_res in rep_port.finditer(conf):
|
||||
last_port_idx = tmp_res.end()
|
||||
if tmp_res.group("ds") and tmp_res.group("ds").strip():
|
||||
is_default_server = True
|
||||
if tmp_res.group("port") in ports:
|
||||
had_ports.add(tmp_res.group("port"))
|
||||
elif tmp_res.group("port") != "443":
|
||||
need_remove_port_idx.append((tmp_res.start(), tmp_res.end()))
|
||||
|
||||
if not last_port_idx:
|
||||
last_port_idx = re.search(r"server\s*\{\s*?\n", conf).end()
|
||||
|
||||
need_add_ports = ports - had_ports
|
||||
d_s = " default_server" if is_default_server else ""
|
||||
h2 = " http2" if use_http2() else ""
|
||||
if need_add_ports or is_http3:
|
||||
listen_add_list = []
|
||||
for p in need_add_ports:
|
||||
if p == "443":
|
||||
tmp = " listen 443 ssl{}{};\n".format(h2, d_s)
|
||||
if use_ipv6:
|
||||
tmp += " listen [::]:443 ssl{}{};\n".format(h2, d_s)
|
||||
listen_add_list.append(tmp)
|
||||
continue
|
||||
|
||||
tmp = " listen {}{};\n".format(p, d_s)
|
||||
if use_ipv6:
|
||||
tmp += " listen [::]:{}{};\n".format(p, d_s)
|
||||
listen_add_list.append(tmp)
|
||||
|
||||
if is_http3 and "443" in (had_ports | had_ports):
|
||||
listen_add_list.append(" listen 443 quic{};\n".format(d_s))
|
||||
if use_ipv6:
|
||||
listen_add_list.append(" listen [::]:443 quic{};\n".format(d_s))
|
||||
|
||||
new_conf = conf[:last_port_idx] + "".join(listen_add_list) + conf[last_port_idx:]
|
||||
return new_conf
|
||||
return conf
|
||||
|
||||
# 将站点配置的域名和端口,写到配置文件中
|
||||
def nginx_set_domain(self, site_name, *domain: Tuple[str, str]) -> Optional[str]:
|
||||
ng_file = '{}/{}{}.conf'.format(self.ng_vhost, self.conf_prefix, site_name)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not ng_conf:
|
||||
return "nginx配置文件丢失"
|
||||
|
||||
domains_set, ports = set(), set()
|
||||
for d, p in domain:
|
||||
domains_set.add(d)
|
||||
ports.add(p)
|
||||
|
||||
# 设置域名
|
||||
rep_server_name = re.compile(r"\s*server_name\s*(.*);", re.M)
|
||||
new_conf = rep_server_name.sub("\n server_name {};".format(" ".join(domains_set)), ng_conf, 1)
|
||||
|
||||
# 设置端口
|
||||
rep_port = re.compile(r"\s*listen\s+[\[\]:]*(?P<port>[0-9]+)(?P<ds>\s*default_server)?.*;[^\n]*\n", re.M)
|
||||
use_ipv6 = listen_ipv6()
|
||||
last_port_idx = None
|
||||
need_remove_port_idx = []
|
||||
had_ports = set()
|
||||
is_default_server = False
|
||||
for tmp_res in rep_port.finditer(new_conf):
|
||||
last_port_idx = tmp_res.end()
|
||||
if tmp_res.group("ds") is not None and tmp_res.group("ds").strip():
|
||||
is_default_server = True
|
||||
if tmp_res.group("port") in ports:
|
||||
had_ports.add(tmp_res.group("port"))
|
||||
elif tmp_res.group("port") != "443":
|
||||
need_remove_port_idx.append((tmp_res.start(), tmp_res.end()))
|
||||
|
||||
if not last_port_idx:
|
||||
last_port_idx = re.search(r"server\s*\{\s*?\n", new_conf).end()
|
||||
|
||||
ports = ports - had_ports
|
||||
if ports:
|
||||
d_s = " default_server" if is_default_server else ""
|
||||
listen_add_list = []
|
||||
for p in ports:
|
||||
tmp = " listen {}{};\n".format(p, d_s)
|
||||
if use_ipv6:
|
||||
tmp += " listen [::]:{}{};\n".format(p, d_s)
|
||||
listen_add_list.append(tmp)
|
||||
|
||||
new_conf = new_conf[:last_port_idx] + "".join(listen_add_list) + new_conf[last_port_idx:]
|
||||
|
||||
# 移除多余的port监听:
|
||||
# 所有遍历的索引都在 last_port_idx 之前,所有不会影响之前的修改 ↑
|
||||
if need_remove_port_idx:
|
||||
conf_list = []
|
||||
idx = 0
|
||||
for start, end in need_remove_port_idx:
|
||||
conf_list.append(new_conf[idx:start])
|
||||
idx = end
|
||||
conf_list.append(new_conf[idx:])
|
||||
new_conf = "".join(conf_list)
|
||||
|
||||
# 保存配置文件
|
||||
write_file(ng_file, new_conf)
|
||||
web_server = webserver()
|
||||
if web_server == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return "配置失败"
|
||||
if web_server == "nginx":
|
||||
service_reload()
|
||||
|
||||
|
||||
class ApacheDomainTool:
|
||||
ap_vhost = "/www/server/panel/vhost/apache"
|
||||
ap_path = "/www/server/apache"
|
||||
|
||||
def __init__(self, conf_prefix: str = ""):
|
||||
self.conf_prefix = conf_prefix
|
||||
|
||||
# 将站点配置的域名和端口,写到配置文件中
|
||||
def apache_set_domain(self,
|
||||
site_name, # 站点名称
|
||||
*domain: Tuple[str, str], # 域名列表,可以为多个
|
||||
template_path: Optional[str] = None, # 在新加端口时使用一个模板作为添加内容
|
||||
template_kwargs: Optional[dict] = None, # 在使用一个模板时的填充参数,
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
template_path: 在新加端口时使用一个模板作为添加内容
|
||||
template_kwargs: 在使用一个模板时的填充参数
|
||||
port domains server_admin server_name 四个参数会自动生成并填充
|
||||
没有传入 template_path 将会复制第一个虚拟机(VirtualHost)配置
|
||||
"""
|
||||
ap_file = '{}/{}{}.conf'.format(self.ap_vhost, self.conf_prefix, site_name)
|
||||
ap_conf: str = read_file(ap_file)
|
||||
if not ap_conf:
|
||||
return "nginx配置文件丢失"
|
||||
|
||||
domains, ports = set(), set()
|
||||
for i in domain:
|
||||
domains.add(str(i[0]))
|
||||
ports.add(str(i[1]))
|
||||
|
||||
domains_str = " ".join(domains)
|
||||
|
||||
# 设置域名
|
||||
rep_server_name = re.compile(r"\s*ServerAlias\s*(.*)\n", re.M)
|
||||
new_conf = rep_server_name.sub("\n ServerAlias {}\n".format(domains_str), ap_conf)
|
||||
|
||||
tmp_template_res = re.search(r"<VirtualHost(.|\n)*?</VirtualHost>", new_conf)
|
||||
if not tmp_template_res:
|
||||
tmp_template = None
|
||||
else:
|
||||
tmp_template = tmp_template_res.group()
|
||||
|
||||
rep_ports = re.compile(r"<VirtualHost +.*:(?P<port>\d+)+\s*>")
|
||||
need_remove_port = []
|
||||
for tmp in rep_ports.finditer(new_conf):
|
||||
if tmp.group("port") in ports:
|
||||
ports.remove(tmp.group("port"))
|
||||
elif tmp.group("port") != "443":
|
||||
need_remove_port.append(tmp.group("port"))
|
||||
|
||||
if need_remove_port:
|
||||
for i in need_remove_port:
|
||||
tmp_rep = re.compile(r"<VirtualHost.*" + i + r"(.|\n)*?</VirtualHost[^\n]*\n?")
|
||||
new_conf = tmp_rep.sub("", new_conf, 1)
|
||||
|
||||
if ports:
|
||||
other_config_body_list = []
|
||||
if template_path is not None:
|
||||
# 添加其他的port
|
||||
try:
|
||||
config_body = read_file(template_path)
|
||||
for p in ports:
|
||||
other_config_body_list.append(config_body.format(
|
||||
port=p,
|
||||
server_admin="admin@{}".format(site_name),
|
||||
server_name='{}.{}'.format(p, site_name),
|
||||
domains=domains_str,
|
||||
**template_kwargs
|
||||
))
|
||||
except:
|
||||
raise ValueError("参数与模板不匹配")
|
||||
else:
|
||||
if tmp_template is None:
|
||||
return "配置文件格式错误"
|
||||
|
||||
for p in ports:
|
||||
other_config_body_list.append(rep_ports.sub("<VirtualHost *:{}>".format(p), tmp_template, 1))
|
||||
|
||||
new_conf += "\n" + "\n".join(other_config_body_list)
|
||||
write_file(ap_file, new_conf)
|
||||
# 添加端口
|
||||
self.apache_add_ports(*ports)
|
||||
web_server = webserver()
|
||||
if web_server == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return "配置失败"
|
||||
|
||||
if web_server == "apache":
|
||||
service_reload()
|
||||
|
||||
# 添加apache主配置文件中的端口监听
|
||||
@classmethod
|
||||
def apache_add_ports(cls, *ports: Union[str, int]) -> None:
|
||||
real_ports = set()
|
||||
for p in ports:
|
||||
real_ports.add(str(p))
|
||||
|
||||
ssl_conf_file = '{}/conf/extra/httpd-ssl.conf'.format(cls.ap_path)
|
||||
if os.path.isfile(ssl_conf_file):
|
||||
ssl_conf = read_file(ssl_conf_file)
|
||||
if isinstance(ssl_conf, str) and ssl_conf.find('Listen 443') != -1:
|
||||
ssl_conf = ssl_conf.replace('Listen 443', '')
|
||||
write_file(ssl_conf_file, ssl_conf)
|
||||
|
||||
ap_conf_file = '{}/conf/httpd.conf'.format(cls.ap_path)
|
||||
if not os.path.isfile(ap_conf_file):
|
||||
return
|
||||
ap_conf = read_file(ap_conf_file)
|
||||
if ap_conf is None:
|
||||
return
|
||||
|
||||
rep_ports = re.compile(r"Listen\s+(?P<port>[0-9]+)\n", re.M)
|
||||
last_idx = None
|
||||
for key in rep_ports.finditer(ap_conf):
|
||||
last_idx = key.end()
|
||||
if key.group("port") in real_ports:
|
||||
real_ports.remove(key.group("port"))
|
||||
|
||||
if not last_idx:
|
||||
return
|
||||
new_conf = ap_conf[:last_idx] + "\n".join(["Listen %s" % i for i in real_ports]) + "\n" + ap_conf[last_idx:]
|
||||
write_file(ap_conf_file, new_conf)
|
||||
326
mod/base/web_conf/ip_restrict.py
Normal file
326
mod/base/web_conf/ip_restrict.py
Normal file
@@ -0,0 +1,326 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
from typing import Tuple, Optional, Union
|
||||
from ipaddress import ip_address
|
||||
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload
|
||||
from mod.base import json_response
|
||||
|
||||
|
||||
class _BaseRestrict:
|
||||
def __init__(self, config_file: str, site_name: str):
|
||||
self._conf_file = config_file
|
||||
self._conf = self._read_conf()
|
||||
self.site_name = site_name
|
||||
|
||||
def _read_conf(self):
|
||||
default_conf = {
|
||||
"restrict_type": "closed",
|
||||
"black_list": [],
|
||||
"white_list": []
|
||||
}
|
||||
|
||||
if not os.path.exists(self._conf_file):
|
||||
return default_conf
|
||||
try:
|
||||
conf = json.loads(read_file(self._conf_file))
|
||||
except:
|
||||
conf = default_conf
|
||||
return conf
|
||||
|
||||
def to_view(self):
|
||||
return self._conf
|
||||
|
||||
|
||||
class _IpRestrict(_BaseRestrict):
|
||||
def __init__(self, site_name: str, config_prefix: str):
|
||||
setup_path = "/www/server/panel"
|
||||
ip_restrict_conf_dir = "{}/data/ip_restrict_data".format(setup_path)
|
||||
if not os.path.exists(ip_restrict_conf_dir):
|
||||
os.makedirs(ip_restrict_conf_dir)
|
||||
super().__init__("{}/{}{}".format(ip_restrict_conf_dir, config_prefix, site_name), site_name)
|
||||
self.config_prefix = config_prefix
|
||||
self.nginx_sub_file = "{}/vhost/ip-restrict/{}{}.conf".format(setup_path, self.config_prefix, self.site_name)
|
||||
|
||||
@property
|
||||
def restrict_type(self):
|
||||
return self._conf.get("restrict_type", "black")
|
||||
|
||||
@restrict_type.setter
|
||||
def restrict_type(self, data: str):
|
||||
if data in ("black", "white", "closed"):
|
||||
self._conf["restrict_type"] = data
|
||||
|
||||
@property
|
||||
def black_list(self):
|
||||
return self._conf.get("black_list", [])
|
||||
|
||||
@black_list.setter
|
||||
def black_list(self, list_data: list):
|
||||
self._conf["black_list"] = list_data
|
||||
|
||||
@property
|
||||
def white_list(self):
|
||||
return self._conf.get("white_list", [])
|
||||
|
||||
@white_list.setter
|
||||
def white_list(self, list_data: list):
|
||||
self._conf["white_list"] = list_data
|
||||
|
||||
def save(self) -> Tuple[bool, str]:
|
||||
if not self._conf: # 没有的时候不操作
|
||||
return True, "operate successfully"
|
||||
write_file(self._conf_file, json.dumps(self._conf))
|
||||
|
||||
if self.restrict_type == "closed":
|
||||
write_file(self.nginx_sub_file, "")
|
||||
service_reload()
|
||||
return True, "operate successfully"
|
||||
|
||||
tmp_conf = []
|
||||
if self.restrict_type == "white":
|
||||
for i in self.white_list:
|
||||
tmp_conf.append("allow {};".format(i))
|
||||
|
||||
tmp_conf.append("deny all; # 除开上述IP外,其他IP全部禁止访问")
|
||||
elif self.restrict_type == "black":
|
||||
for i in self.black_list:
|
||||
tmp_conf.append("deny {};".format(i))
|
||||
else:
|
||||
raise ValueError("错误的类型,无法操作")
|
||||
|
||||
write_file(self.nginx_sub_file, "\n".join(tmp_conf))
|
||||
error_msg = check_server_config()
|
||||
if error_msg is not None:
|
||||
write_file(self.nginx_sub_file, "")
|
||||
return False, "操作失败"
|
||||
service_reload()
|
||||
return True, "operate successfully"
|
||||
|
||||
# 删除网站时调用,删除配置文件
|
||||
def remove_config_for_remove_site(self):
|
||||
if os.path.isfile(self.nginx_sub_file):
|
||||
os.remove(self.nginx_sub_file)
|
||||
|
||||
if os.path.isfile(self._conf_file):
|
||||
os.remove(self._conf_file)
|
||||
|
||||
|
||||
class RealIpRestrict:
|
||||
|
||||
def __init__(self, config_prefix: str = ""):
|
||||
self.config_prefix = config_prefix
|
||||
self.web_server = webserver()
|
||||
|
||||
# 获取某个站点的IP黑白名单详情
|
||||
def restrict_conf(self, site_name: str) -> Tuple[bool, Union[str, dict]]:
|
||||
if self.web_server != "nginx":
|
||||
return False, "不支持除nginx之外的服务器"
|
||||
ip_conf = _IpRestrict(site_name, self.config_prefix)
|
||||
if not self._get_status_in_nginx_conf(ip_conf):
|
||||
ip_conf.restrict_type = "closed"
|
||||
return True, ip_conf.to_view()
|
||||
|
||||
# 从配置文件中获取状态
|
||||
def _get_status_in_nginx_conf(self, ip_conf: _IpRestrict) -> bool:
|
||||
setup_path = "/www/server/panel"
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(setup_path, self.config_prefix, ip_conf.site_name)
|
||||
rep_include = re.compile(r"\sinclude +.*/ip-restrict/.*\.conf;", re.M)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not isinstance(ng_conf, str):
|
||||
return False
|
||||
if rep_include.search(ng_conf):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _set_nginx_include(self, ip_conf: _IpRestrict) -> Tuple[bool, str]:
|
||||
setup_path = "/www/server/panel"
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(setup_path, self.config_prefix, ip_conf.site_name)
|
||||
if not os.path.exists(os.path.dirname(ip_conf.nginx_sub_file)):
|
||||
os.makedirs(os.path.dirname(ip_conf.nginx_sub_file), 0o600)
|
||||
if not os.path.isfile(ip_conf.nginx_sub_file):
|
||||
write_file(ip_conf.nginx_sub_file, "")
|
||||
|
||||
ng_conf = read_file(ng_file)
|
||||
if not isinstance(ng_conf, str):
|
||||
return False, "nginx配置文件读取失败"
|
||||
|
||||
rep_include = re.compile(r"\s*include\s+.*/ip-restrict/.*\.conf;", re.M)
|
||||
if rep_include.search(ng_conf):
|
||||
return True, ""
|
||||
|
||||
_include_str = (
|
||||
"\n #引用IP黑白名单规则,注释后配置的IP黑白名单将无效\n"
|
||||
" include {};"
|
||||
).format(ip_conf.nginx_sub_file)
|
||||
|
||||
rep_redirect_include = re.compile(r"\s*include\s+.*/redirect/.*\.conf;", re.M) # 如果有重定向,添加到重定向之后
|
||||
redirect_include_res = rep_redirect_include.search(ng_conf)
|
||||
if redirect_include_res:
|
||||
new_conf = ng_conf[:redirect_include_res.end()] + _include_str + ng_conf[redirect_include_res.end():]
|
||||
else:
|
||||
if "#SSL-END" not in ng_conf:
|
||||
return False, "添加配置失败,无法定位SSL相关配置的位置"
|
||||
|
||||
new_conf = ng_conf.replace("#SSL-END", "#SSL-END" + _include_str)
|
||||
write_file(ng_file, new_conf)
|
||||
if self.web_server == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return False, "添加配置失败"
|
||||
|
||||
return True, ""
|
||||
|
||||
def set_ip_restrict(self, site_name: str, set_type: str) -> Tuple[bool, str]:
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
if set_type not in ("black", "white", "closed"):
|
||||
return False, "不支持的类型【{}】".format(set_type)
|
||||
ip_restrict.restrict_type = set_type
|
||||
f, msg = self._set_nginx_include(ip_restrict)
|
||||
if not f:
|
||||
return False, msg
|
||||
|
||||
return ip_restrict.save()
|
||||
|
||||
def add_black_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]:
|
||||
try:
|
||||
for ip in ips:
|
||||
_ = ip_address(ip) # 引发valueError
|
||||
except ValueError:
|
||||
return False, "ip参数解析错误"
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
black_list = ip_restrict.black_list
|
||||
for i in ips:
|
||||
if i not in black_list:
|
||||
black_list.append(i)
|
||||
|
||||
ip_restrict.black_list = black_list
|
||||
f, msg = self._set_nginx_include(ip_restrict)
|
||||
if not f:
|
||||
return False, msg
|
||||
|
||||
return ip_restrict.save()
|
||||
|
||||
def remove_black_ip_restrict(self, site_name: str, *ips: str):
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
black_list = ip_restrict.black_list
|
||||
for i in ips:
|
||||
if i in black_list:
|
||||
black_list.remove(i)
|
||||
|
||||
ip_restrict.black_list = black_list
|
||||
f, msg = self._set_nginx_include(ip_restrict)
|
||||
if not f:
|
||||
return False, msg
|
||||
|
||||
return ip_restrict.save()
|
||||
|
||||
def add_white_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]:
|
||||
try:
|
||||
for ip in ips:
|
||||
_ = ip_address(ip) # 引发valueError
|
||||
except ValueError:
|
||||
return False, "ip参数解析错误"
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
white_list = ip_restrict.white_list
|
||||
for i in ips:
|
||||
if i not in white_list:
|
||||
white_list.append(i)
|
||||
|
||||
ip_restrict.white_list = white_list
|
||||
f, msg = self._set_nginx_include(ip_restrict)
|
||||
if not f:
|
||||
return False, msg
|
||||
|
||||
return ip_restrict.save()
|
||||
|
||||
def remove_white_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]:
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
white_list = ip_restrict.white_list
|
||||
for i in ips:
|
||||
if i in white_list:
|
||||
white_list.remove(i)
|
||||
|
||||
ip_restrict.white_list = white_list
|
||||
|
||||
return ip_restrict.save()
|
||||
|
||||
def remove_site_ip_restrict_info(self, site_name: str):
|
||||
ip_restrict = _IpRestrict(site_name, self.config_prefix)
|
||||
ip_restrict.remove_config_for_remove_site()
|
||||
|
||||
|
||||
class IpRestrict:
|
||||
|
||||
def __init__(self, config_prefix: str = ""):
|
||||
self.config_prefix = config_prefix
|
||||
self._ri = RealIpRestrict(self.config_prefix)
|
||||
|
||||
# 获取ip控制信息
|
||||
def restrict_conf(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, d = self._ri.restrict_conf(site_name)
|
||||
if not f:
|
||||
return json_response(status=f, msg=d)
|
||||
return json_response(status=f, data=d)
|
||||
|
||||
# 设置ip黑白名单状态
|
||||
def set_ip_restrict(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
set_ip_restrict = get.set_type.strip()
|
||||
except (AttributeError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, m = self._ri.set_ip_restrict(site_name, set_ip_restrict)
|
||||
return json_response(status=f, msg=m)
|
||||
|
||||
# 添加黑名单
|
||||
def add_black_ip_restrict(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
value = get.value.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, m = self._ri.add_black_ip_restrict(site_name, value)
|
||||
return json_response(status=f, msg=m)
|
||||
|
||||
# 移除黑名单
|
||||
def remove_black_ip_restrict(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
value = get.value.strip()
|
||||
except (AttributeError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, m = self._ri.remove_black_ip_restrict(site_name, value)
|
||||
return json_response(status=f, msg=m)
|
||||
|
||||
# 添加白名单
|
||||
def add_white_ip_restrict(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
value = get.value.strip()
|
||||
except (AttributeError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, m = self._ri.add_white_ip_restrict(site_name, value)
|
||||
return json_response(status=f, msg=m)
|
||||
|
||||
# 移除白名单
|
||||
def remove_white_ip_restrict(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
value = get.value.strip()
|
||||
except (AttributeError, json.JSONDecodeError):
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
f, m = self._ri.remove_white_ip_restrict(site_name, value)
|
||||
return json_response(status=f, msg=m)
|
||||
|
||||
|
||||
238
mod/base/web_conf/limit_net.py
Normal file
238
mod/base/web_conf/limit_net.py
Normal file
@@ -0,0 +1,238 @@
|
||||
import os
|
||||
import re
|
||||
from typing import Tuple, Union
|
||||
|
||||
from .util import webserver
|
||||
|
||||
|
||||
class LimitNet(object):
|
||||
|
||||
def get_limit_net(self, get) -> Union[bool, str]:
|
||||
if webserver() != 'nginx':
|
||||
return False, ""
|
||||
try:
|
||||
site_id = int(get.site_id)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
return public.returnMsg(False, "Parameter error")
|
||||
|
||||
if self.config_prefix is None:
|
||||
return public.returnMsg(False, "不支持的网站类型")
|
||||
|
||||
# 取配置文件
|
||||
site_name = public.M('sites').where("id=?", (site_id,)).getField('name')
|
||||
filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name)
|
||||
conf = public.readFile(filename)
|
||||
if not isinstance(conf, str):
|
||||
return public.returnMsg(False, "配置文件读取错误")
|
||||
|
||||
# 站点总并发
|
||||
data = {
|
||||
'perserver': 0,
|
||||
'perip': 0,
|
||||
'limit_rate': 0,
|
||||
}
|
||||
|
||||
rep_per_server = re.compile(r"(?P<prefix>.*)limit_conn +perserver +(?P<target>\d+) *; *", re.M)
|
||||
tmp_res = rep_per_server.search(conf)
|
||||
if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释
|
||||
data['perserver'] = int(tmp_res.group("target"))
|
||||
|
||||
# IP并发限制
|
||||
rep_per_ip = re.compile(r"(?P<prefix>.*)limit_conn +perip +(?P<target>\d+) *; *", re.M)
|
||||
tmp_res = rep_per_ip.search(conf)
|
||||
if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释
|
||||
data['perip'] = int(tmp_res.group("target"))
|
||||
|
||||
# 请求并发限制
|
||||
rep_limit_rate = re.compile(r"(?P<prefix>.*)limit_rate +(?P<target>\d+)\w+ *; *", re.M)
|
||||
tmp_res = rep_limit_rate.search(conf)
|
||||
if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释
|
||||
data['limit_rate'] = int(tmp_res.group("target"))
|
||||
|
||||
self._show_limit_net(data)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _show_limit_net(data):
|
||||
values = [
|
||||
[300, 25, 512],
|
||||
[200, 10, 1024],
|
||||
[50, 3, 2048],
|
||||
[500, 10, 2048],
|
||||
[400, 15, 1024],
|
||||
[60, 10, 512],
|
||||
[150, 4, 1024],
|
||||
]
|
||||
for i, c in enumerate(values):
|
||||
if data["perserver"] == c[0] and data["perip"] == c[1] and data["limit_rate"] == c[2]:
|
||||
data["value"] = i + 1
|
||||
break
|
||||
else:
|
||||
data["value"] = 0
|
||||
|
||||
@staticmethod
|
||||
def _set_nginx_conf_limit() -> Tuple[bool, str]:
|
||||
# 设置共享内存
|
||||
nginx_conf_file = "/www/server/nginx/conf/nginx.conf"
|
||||
if not os.path.exists(nginx_conf_file):
|
||||
return False, "nginx配置文件丢失"
|
||||
nginx_conf = public.readFile(nginx_conf_file)
|
||||
rep_perip = re.compile(r"\s+limit_conn_zone +\$binary_remote_addr +zone=perip:10m;", re.M)
|
||||
rep_per_server = re.compile(r"\s+limit_conn_zone +\$server_name +zone=perserver:10m;", re.M)
|
||||
perip_res = rep_perip.search(nginx_conf)
|
||||
per_serve_res = rep_per_server.search(nginx_conf)
|
||||
if perip_res and per_serve_res:
|
||||
return True, ""
|
||||
elif perip_res or per_serve_res:
|
||||
tmp_res = perip_res or per_serve_res
|
||||
new_conf = nginx_conf[:tmp_res.start()] + (
|
||||
"\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;"
|
||||
"\n\t\tlimit_conn_zone $server_name zone=perserver:10m;"
|
||||
) + nginx_conf[tmp_res.end():]
|
||||
else:
|
||||
# 通过检查第一个server的位置
|
||||
rep_first_server = re.compile(r"http\s*\{(.*\n)*\s*server\s*\{")
|
||||
tmp_res = rep_first_server.search(nginx_conf)
|
||||
if tmp_res:
|
||||
old_http_conf = tmp_res.group()
|
||||
# 在第一个server项前添加
|
||||
server_idx = old_http_conf.rfind("server")
|
||||
new_http_conf = old_http_conf[:server_idx] + (
|
||||
"\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;"
|
||||
"\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n"
|
||||
) + old_http_conf[server_idx:]
|
||||
new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1)
|
||||
else:
|
||||
# 在没有配置其他server项目时,通过检查include server项目检查
|
||||
# 通检查 include /www/server/panel/vhost/nginx/*.conf; 位置
|
||||
rep_include = re.compile(r"http\s*\{(.*\n)*\s*include +/www/server/panel/vhost/nginx/\*\.conf;")
|
||||
tmp_res = rep_include.search(nginx_conf)
|
||||
if not tmp_res:
|
||||
return False, "The global configuration cache configuration failed"
|
||||
old_http_conf = tmp_res.group()
|
||||
|
||||
include_idx = old_http_conf.rfind("include ")
|
||||
new_http_conf = old_http_conf[:include_idx] + (
|
||||
"\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;"
|
||||
"\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n"
|
||||
) + old_http_conf[include_idx:]
|
||||
new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1)
|
||||
|
||||
public.writeFile(nginx_conf_file, new_conf)
|
||||
if public.checkWebConfig() is not True: # 检测失败,无法添加
|
||||
public.writeFile(nginx_conf_file, nginx_conf)
|
||||
return False, "The global configuration cache configuration failed"
|
||||
return True, ""
|
||||
|
||||
# 设置流量限制
|
||||
def set_limit_net(self, get):
|
||||
if public.get_webserver() != 'nginx':
|
||||
return public.returnMsg(False, 'SITE_NETLIMIT_ERR')
|
||||
try:
|
||||
site_id = int(get.site_id)
|
||||
per_server = int(get.perserver)
|
||||
perip = int(get.perip)
|
||||
limit_rate = int(get.limit_rate)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
return public.returnMsg(False, "Parameter error")
|
||||
|
||||
if per_server < 1 or perip < 1 or limit_rate < 1:
|
||||
return public.returnMsg(False, '并发限制,IP限制,流量限制必需大于0')
|
||||
|
||||
# 取配置文件
|
||||
site_info = public.M('sites').where("id=?", (site_id,)).find()
|
||||
if not isinstance(site_info, dict):
|
||||
return public.returnMsg(False, "站点信息查询错误")
|
||||
else:
|
||||
site_name = site_info["name"]
|
||||
filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name)
|
||||
site_conf: str = public.readFile(filename)
|
||||
if not isinstance(site_conf, str):
|
||||
return public.returnMsg(False, "配置文件读取错误")
|
||||
|
||||
flag, msg = self._set_nginx_conf_limit()
|
||||
if not flag:
|
||||
return public.returnMsg(False, msg)
|
||||
|
||||
per_server_str = ' limit_conn perserver {};'.format(per_server)
|
||||
perip_str = ' limit_conn perip {};'.format(perip)
|
||||
limit_rate_str = ' limit_rate {}k;'.format(limit_rate)
|
||||
|
||||
# 请求并发限制
|
||||
new_conf = site_conf
|
||||
ssl_end_res = re.search(r"#error_page 404/404.html;[^\n]*\n", new_conf)
|
||||
if ssl_end_res is None:
|
||||
return public.returnMsg(False, "未定位到SSL的相关配置,添加失败")
|
||||
ssl_end_idx = ssl_end_res.end()
|
||||
rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *", re.M)
|
||||
tmp_res = rep_limit_rate.search(new_conf)
|
||||
if tmp_res is not None :
|
||||
new_conf = rep_limit_rate.sub(limit_rate_str, new_conf)
|
||||
else:
|
||||
new_conf = new_conf[:ssl_end_idx] + limit_rate_str + "\n" + new_conf[ssl_end_idx:]
|
||||
|
||||
# IP并发限制
|
||||
rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *", re.M)
|
||||
tmp_res = rep_per_ip.search(new_conf)
|
||||
if tmp_res is not None:
|
||||
new_conf = rep_per_ip.sub(perip_str, new_conf)
|
||||
else:
|
||||
new_conf = new_conf[:ssl_end_idx] + perip_str + "\n" + new_conf[ssl_end_idx:]
|
||||
|
||||
rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *", re.M)
|
||||
tmp_res = rep_per_server.search(site_conf)
|
||||
if tmp_res is not None:
|
||||
new_conf = rep_per_server.sub(per_server_str, new_conf)
|
||||
else:
|
||||
new_conf = new_conf[:ssl_end_idx] + per_server_str + "\n" + new_conf[ssl_end_idx:]
|
||||
|
||||
public.writeFile(filename, new_conf)
|
||||
is_error = public.checkWebConfig()
|
||||
if is_error is not True:
|
||||
public.writeFile(filename, site_conf)
|
||||
return public.returnMsg(False, 'ERROR:<br><a style="color:red;">' + is_error.replace("\n", '<br>') + '</a>')
|
||||
|
||||
public.serviceReload()
|
||||
public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_OPEN_SUCCESS', (site_name,))
|
||||
return public.returnMsg(True, 'Successfully set')
|
||||
|
||||
# 关闭流量限制
|
||||
def close_limit_net(self, get):
|
||||
if public.get_webserver() != 'nginx':
|
||||
return public.returnMsg(False, 'SITE_NETLIMIT_ERR')
|
||||
if self.config_prefix is None:
|
||||
return public.returnMsg(False, "不支持的网站类型")
|
||||
try:
|
||||
site_id = int(get.site_id)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
return public.returnMsg(False, "Parameter error")
|
||||
|
||||
# 取回配置文件
|
||||
site_info = public.M('sites').where("id=?", (site_id,)).find()
|
||||
if not isinstance(site_info, dict):
|
||||
return public.returnMsg(False, "站点信息查询错误")
|
||||
else:
|
||||
site_name = site_info["name"]
|
||||
filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name)
|
||||
site_conf = public.readFile(filename)
|
||||
if not isinstance(site_conf, str):
|
||||
return public.returnMsg(False, "配置文件读取错误")
|
||||
|
||||
# 清理总并发
|
||||
rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *\n?", re.M)
|
||||
rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *\n?", re.M)
|
||||
rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *\n?", re.M)
|
||||
|
||||
new_conf = site_conf
|
||||
new_conf = rep_limit_rate.sub("", new_conf, 1)
|
||||
new_conf = rep_per_ip.sub("", new_conf, 1)
|
||||
new_conf = rep_per_server.sub("", new_conf, 1)
|
||||
|
||||
public.writeFile(filename, new_conf)
|
||||
is_error = public.checkWebConfig()
|
||||
if is_error is not True:
|
||||
public.writeFile(filename, site_conf)
|
||||
return public.returnMsg(False, 'ERROR:<br><a style="color:red;">' + is_error.replace("\n", '<br>') + '</a>')
|
||||
public.serviceReload()
|
||||
public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_CLOSE_SUCCESS', (site_name,))
|
||||
return public.returnMsg(True, 'SITE_NETLIMIT_CLOSE_SUCCESS')
|
||||
855
mod/base/web_conf/logmanager.py
Normal file
855
mod/base/web_conf/logmanager.py
Normal file
@@ -0,0 +1,855 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import sys
|
||||
from typing import Tuple, Optional, Union, List
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key
|
||||
from mod.base import json_response
|
||||
|
||||
|
||||
class _BaseLogFormat:
|
||||
panel_path = "/www/server/panel"
|
||||
|
||||
def __init__(self):
|
||||
self._config_file = ""
|
||||
self._config: Optional[dict] = None
|
||||
self._format_dict = None
|
||||
self._log_format_dir = ''
|
||||
|
||||
@property
|
||||
def config(self) -> dict:
|
||||
if self._config is None:
|
||||
try:
|
||||
self._config = json.loads(read_file(self._config_file))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
self._config = {}
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
if self._config is not None:
|
||||
write_file(self._config_file, json.dumps(self._config))
|
||||
|
||||
@property
|
||||
def log_format(self) -> dict:
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_config(self, name: str, keys: List[str], space_character=None) -> Optional[str]:
|
||||
if space_character and len(space_character) > 4:
|
||||
return "间隔符过长,请输入小于4位的间隔符"
|
||||
rep_name = re.compile(r"^\w+$")
|
||||
if rep_name.match(name) is None:
|
||||
return "名称只能包含数字、字母和下划线"
|
||||
if name in ("combined", "main"):
|
||||
return "请勿使用默认名称"
|
||||
error_key = []
|
||||
for k in keys:
|
||||
if k not in self.log_format:
|
||||
error_key.append(k)
|
||||
if error_key:
|
||||
return "无法识别以下日志关键字:【{}】".format(",".join(error_key))
|
||||
|
||||
# 添加日志格式
|
||||
def add_log_format(self, name: str, keys: List[str], space_character=" ") -> Optional[str]:
|
||||
error_msg = self.check_config(name, keys, space_character)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
if name in self.config:
|
||||
return "该名称的日志格式已存在"
|
||||
error_msg = self._set_to_config(name, keys, space_character, is_modify=False)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
|
||||
self.config[name] = {"keys": keys, "space_character": space_character, "sites": []}
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return None
|
||||
|
||||
# 修改日志格式
|
||||
def modify_log_format(self, name: str, keys: List[str], space_character=None) -> Optional[str]:
|
||||
error_msg = self.check_config(name, keys, space_character)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
if name not in self.config:
|
||||
return "该名称的日志格式不存在"
|
||||
|
||||
self.config[name]["keys"] = keys
|
||||
if space_character:
|
||||
self.config[name]["space_character"] = space_character
|
||||
else:
|
||||
space_character = self.config[name]["space_character"]
|
||||
|
||||
error_msg = self._set_to_config(name, keys, space_character, is_modify=True)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return None
|
||||
|
||||
# 删除日志格式
|
||||
def remove_log_format(self, name: str) -> Optional[str]:
|
||||
if name not in self.config:
|
||||
return "该名称的日志格式不存在"
|
||||
if len(self.config[name].get("sites", [])) > 1:
|
||||
return "该日志格式在【{}】网站中正在使用,请先移除".format(",".join(self.config[name]["sites"]))
|
||||
self._remove_form_config(name)
|
||||
|
||||
del self.config[name]
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return None
|
||||
|
||||
def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]:
|
||||
raise NotImplementedError
|
||||
|
||||
def _remove_form_config(self, name) -> None:
|
||||
conf_file = self._log_format_dir + "/{}_format.conf".format(name)
|
||||
if os.path.isfile(conf_file):
|
||||
os.remove(conf_file)
|
||||
|
||||
# 在配置文件中设置日志格式, log_format_name传入空字符串时,设置会默认
|
||||
def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]:
|
||||
"""
|
||||
mutil 为True时,不会自动重载配置
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# 设置日志格式
|
||||
def set_site_log_format(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]:
|
||||
if log_format_name not in self.config and log_format_name != "":
|
||||
return "该名称的日志格式不存在"
|
||||
error_msg = self.set_site_log_format_in_config(site_name, log_format_name, conf_prefix, mutil=mutil)
|
||||
if error_msg is not None:
|
||||
return error_msg
|
||||
if "sites" not in self.config[log_format_name]:
|
||||
self.config[log_format_name]["sites"] = []
|
||||
for name, sub_conf in self.config.items():
|
||||
if name == log_format_name:
|
||||
sub_conf["sites"].append(site_name) # 记录到配置文件中
|
||||
|
||||
if site_name in sub_conf.get("sites", []):
|
||||
sub_conf["sites"].remove(site_name) # 如果之前使用了其他的配置,则移除其他配置中的这个站点的关联
|
||||
|
||||
self.save_config()
|
||||
|
||||
|
||||
class _NgLog(_BaseLogFormat):
|
||||
|
||||
@property
|
||||
def log_format(self) -> dict:
|
||||
if self._format_dict is None:
|
||||
self._format_dict = {
|
||||
"server_addr": {
|
||||
"name": "服务器地址",
|
||||
"key": "$server_addr",
|
||||
},
|
||||
"server_port": {
|
||||
"name": "服务器端口",
|
||||
"key": "$server_port",
|
||||
},
|
||||
"host": {
|
||||
"name": "域名",
|
||||
"key": "$http_host",
|
||||
},
|
||||
"remote_addr": {
|
||||
"name": "客户端地址",
|
||||
"key": "$server_addr",
|
||||
},
|
||||
"remote_port": {
|
||||
"name": "客户端端口",
|
||||
"key": "$server_addr",
|
||||
},
|
||||
"protocol": {
|
||||
"name": "服务器协议",
|
||||
"key": "$server_protocol",
|
||||
},
|
||||
"req_length": {
|
||||
"name": "请求长度",
|
||||
"key": "$request_length",
|
||||
},
|
||||
"method": {
|
||||
"name": "请求方法",
|
||||
"key": "$request_method",
|
||||
},
|
||||
"uri": {
|
||||
"name": "请求uri",
|
||||
"key": "$request_uri",
|
||||
},
|
||||
"status": {
|
||||
"name": "状态码",
|
||||
"key": "$status",
|
||||
},
|
||||
"sent_bytes": {
|
||||
"name": "发送字节数",
|
||||
"key": "$body_bytes_sent",
|
||||
},
|
||||
"referer": {
|
||||
"name": "来源地址",
|
||||
"key": "$http_referer",
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "用户代理(User-Agent)",
|
||||
"key": "$http_user_agent",
|
||||
},
|
||||
"take_time": {
|
||||
"name": "请求用时",
|
||||
"key": "$request_time",
|
||||
},
|
||||
}
|
||||
return self._format_dict
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._config_file = "{}/data/ng_log_format.json".format(self.panel_path)
|
||||
self._log_format_dir = "{}/vhost/nginx/log_format".format(self.panel_path)
|
||||
|
||||
def _set_log_format_include(self) -> Optional[str]:
|
||||
config_file = "/www/server/nginx/conf/nginx.conf"
|
||||
config_data = read_file(config_file)
|
||||
if not config_data:
|
||||
return "配置文件丢失无法操作"
|
||||
if not os.path.isdir(self._log_format_dir):
|
||||
os.makedirs(self._log_format_dir)
|
||||
rep_include = re.compile(r"include\s+/www/server/panel/vhost/nginx/log_format/\*\.conf\s*;")
|
||||
if rep_include.search(config_data):
|
||||
return
|
||||
|
||||
rep_http = re.compile(r"\s*http\s*\{[^\n]*\n")
|
||||
res = rep_http.search(config_data)
|
||||
if not res:
|
||||
return "主配置文件中缺少http配置项,无法添加"
|
||||
include_str = "include {}/*.conf;\n".format(self._log_format_dir)
|
||||
new_conf = config_data[:res.end()] + include_str + config_data[res.end():]
|
||||
write_file(config_file, new_conf)
|
||||
|
||||
def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]:
|
||||
error_msg = self._set_log_format_include()
|
||||
if error_msg:
|
||||
return error_msg
|
||||
conf_file = self._log_format_dir + "/{}_format.conf".format(name)
|
||||
write_file(conf_file, (
|
||||
"log_format {} '{}';".format(name, space_character.join(map(lambda x: self.log_format[x]["key"], keys)))
|
||||
))
|
||||
|
||||
def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]:
|
||||
"""
|
||||
mutil 为True时,不会自动重载配置
|
||||
"""
|
||||
config_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, conf_prefix, site_name)
|
||||
config_data = read_file(config_file)
|
||||
if not config_data:
|
||||
return "配置文件丢失无法操作"
|
||||
|
||||
start_idx, end_idx = self.get_first_server_log_idx(config_data)
|
||||
if start_idx:
|
||||
rep_access_log = re.compile(r"\s*access_log\s+(?P<path>[^;\s]*)(\s+(?P<name>\w+))?;")
|
||||
res = rep_access_log.search(config_data[start_idx: end_idx])
|
||||
if res.group("name") == log_format_name:
|
||||
return
|
||||
new_access_log = "\n access_log {} {};".format(res.group("path"), log_format_name)
|
||||
new_conf = config_data[:start_idx] + new_access_log + config_data[end_idx:]
|
||||
else:
|
||||
last_server_idx = config_data.rfind("}") # server 范围内最后一个}的位置
|
||||
if last_server_idx == -1:
|
||||
return "配置文件格式错误无法操作"
|
||||
log_path = "{}/{}.log".format(get_log_path(), site_name)
|
||||
new_access_log = "\n access_log {} {};\n".format(log_path, log_format_name)
|
||||
new_conf = config_data[:last_server_idx] + new_access_log + config_data[last_server_idx:]
|
||||
write_file(config_file, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(config_file, config_data)
|
||||
return "配置修改失败"
|
||||
if webserver() == "nginx" and not mutil:
|
||||
service_reload()
|
||||
|
||||
# 获取配置文件中server等级的第一个access_log的位置
|
||||
@staticmethod
|
||||
def get_first_server_log_idx(config_data) -> Tuple[Optional[int], Optional[int]]:
|
||||
rep_server = re.compile(r"\s*server\s*\{")
|
||||
res = rep_server.search(config_data)
|
||||
if res is None:
|
||||
return None, None
|
||||
rep_log = re.compile(r"\s*access_log\s+(?P<path>[^;\s]*)(\s+(?P<name>\w+))?;", re.M)
|
||||
s_idx = res.end()
|
||||
l_n = 1
|
||||
length = len(config_data)
|
||||
while l_n > 0:
|
||||
next_l = config_data[s_idx:].find("{")
|
||||
next_r = config_data[s_idx:].find("}")
|
||||
if next_l == -1 and next_r == -1: # 都没有了跳过
|
||||
return None, None
|
||||
if next_r == -1 and next_l != -1: # 还剩 { 但是没有 } ,跳过
|
||||
return None, None
|
||||
if next_l == -1:
|
||||
next_l = length
|
||||
if next_l < next_r:
|
||||
if l_n == 1:
|
||||
res = rep_log.search(config_data[s_idx: s_idx + next_l])
|
||||
if res:
|
||||
return s_idx + res.start(), s_idx + res.end()
|
||||
l_n += 1
|
||||
else:
|
||||
l_n -= 1
|
||||
if l_n == 0:
|
||||
res = rep_log.search(config_data[s_idx: s_idx + next_l])
|
||||
if res:
|
||||
return s_idx + res.start(), s_idx + res.end()
|
||||
s_idx += min(next_l, next_r) + 1
|
||||
return None, None
|
||||
|
||||
# 设置站点的日志路径
|
||||
def set_site_log_path(self, site_name, site_log_path, conf_prefix, mutil=False) -> Optional[str]:
|
||||
if not os.path.isdir(site_log_path):
|
||||
return "不是一个存在的文件夹路径"
|
||||
|
||||
if site_log_path[-1] == "/":
|
||||
site_log_path = site_log_path[:-1]
|
||||
|
||||
# nginx
|
||||
nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name)
|
||||
nginx_config = read_file(nginx_config_path)
|
||||
if not nginx_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
|
||||
# nginx
|
||||
old_log_file = self.nginx_get_log_file_path(nginx_config, site_name, is_error_log=False)
|
||||
old_error_log_file = self.nginx_get_log_file_path(nginx_config, site_name, is_error_log=True)
|
||||
|
||||
if old_log_file and old_error_log_file:
|
||||
new_nginx_conf = nginx_config
|
||||
log_file_rep = re.compile(r"access_log +" + pre_re_key(old_log_file))
|
||||
error_log_file_rep = re.compile(r"error_log +" + pre_re_key(old_error_log_file))
|
||||
if log_file_rep.search(nginx_config):
|
||||
new_nginx_conf = log_file_rep.sub("access_log {}/{}.log".format(site_log_path, site_name),
|
||||
new_nginx_conf, 1)
|
||||
|
||||
if error_log_file_rep.search(nginx_config):
|
||||
new_nginx_conf = error_log_file_rep.sub("error_log {}/{}.error.log".format(site_log_path, site_name),
|
||||
new_nginx_conf, 1)
|
||||
|
||||
write_file(nginx_config_path, new_nginx_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(nginx_config_path, nginx_config)
|
||||
return "配置修改失败"
|
||||
if webserver() == "nginx" and not mutil:
|
||||
service_reload()
|
||||
|
||||
else:
|
||||
return "未找到日志配置,无法操作"
|
||||
|
||||
@staticmethod
|
||||
def nginx_get_log_file_path(nginx_config: str, site_name: str, is_error_log: bool = False):
|
||||
log_file = None
|
||||
if is_error_log:
|
||||
re_data = re.findall(r"error_log +(/(\S+/?)+) ?(.*?);", nginx_config)
|
||||
else:
|
||||
re_data = re.findall(r"access_log +(/(\S+/?)+) ?(.*?);", nginx_config)
|
||||
if re_data is None:
|
||||
log_file = None
|
||||
else:
|
||||
for i in re_data:
|
||||
file_path = i[0].strip(";")
|
||||
if file_path != "/dev/null" and not file_path.endswith("purge_cache.log"):
|
||||
if os.path.isdir(os.path.dirname(file_path)):
|
||||
log_file = file_path
|
||||
break
|
||||
|
||||
logsPath = '/www/wwwlogs/'
|
||||
if log_file is None:
|
||||
if is_error_log:
|
||||
log_file = logsPath + site_name + '.log'
|
||||
else:
|
||||
log_file = logsPath + site_name + '.error.log'
|
||||
if not os.path.isfile(log_file):
|
||||
log_file = None
|
||||
|
||||
return log_file
|
||||
|
||||
def get_site_log_path(self, site_name, conf_prefix) -> Union[str, dict]:
|
||||
config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name)
|
||||
config = read_file(config_path)
|
||||
if not config:
|
||||
return "站点配置文件丢失"
|
||||
log_file = self.nginx_get_log_file_path(config, site_name, is_error_log=False)
|
||||
error_log_file = self.nginx_get_log_file_path(config, site_name, is_error_log=False)
|
||||
if not (error_log_file and log_file):
|
||||
return "获取失败"
|
||||
return {
|
||||
"log_file": log_file,
|
||||
"error_log_file": error_log_file,
|
||||
}
|
||||
|
||||
def close_access_log(self, site_name, conf_prefix) -> Optional[str]:
|
||||
nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name)
|
||||
nginx_config = read_file(nginx_config_path)
|
||||
if not nginx_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
|
||||
start_idx, end_idx = self.get_first_server_log_idx(nginx_config)
|
||||
if not start_idx:
|
||||
return None
|
||||
new_conf = nginx_config
|
||||
|
||||
while start_idx is not None:
|
||||
new_conf = new_conf[:start_idx] + '# ' + new_conf[start_idx:]
|
||||
start_idx, end_idx = self.get_first_server_log_idx(new_conf)
|
||||
|
||||
write_file(nginx_config_path, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(nginx_config_path, nginx_config)
|
||||
return "配置修改失败"
|
||||
|
||||
return None
|
||||
|
||||
# 未完成
|
||||
def open_access_log(self, site_name, conf_prefix) -> Optional[str]:
|
||||
nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name)
|
||||
nginx_config = read_file(nginx_config_path)
|
||||
if not nginx_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
|
||||
new_conf = nginx_config.replace("#")
|
||||
|
||||
write_file(nginx_config_path, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(nginx_config_path, nginx_config)
|
||||
return "配置修改失败"
|
||||
|
||||
return None
|
||||
|
||||
def access_log_is_open(self, site_name, conf_prefix) -> bool:
|
||||
nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name)
|
||||
nginx_config = read_file(nginx_config_path)
|
||||
if not nginx_config:
|
||||
return False
|
||||
|
||||
start_idx, end_idx = self.get_first_server_log_idx(nginx_config)
|
||||
return start_idx is not None
|
||||
|
||||
|
||||
class _ApLog(_BaseLogFormat):
|
||||
|
||||
def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]:
|
||||
if log_format_name == "":
|
||||
log_format_name = "combined"
|
||||
config_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, conf_prefix, site_name)
|
||||
config_data = read_file(config_file)
|
||||
if not config_data:
|
||||
return "配置文件丢失无法操作"
|
||||
|
||||
custom_log_rep = re.compile(r'''\s*CustomLog\s+['"](?P<path>.*)['"](\s+(?P<name>.*))?''', re.M)
|
||||
new_custom_log = '\n CustomLog "{}" %s\n' % log_format_name
|
||||
new_conf_list = []
|
||||
idx = 0
|
||||
for tmp_res in custom_log_rep.finditer(config_data):
|
||||
new_conf_list.append(config_data[idx:tmp_res.start()])
|
||||
new_conf_list.append(new_custom_log.format(tmp_res.group("path")))
|
||||
idx = tmp_res.end()
|
||||
new_conf_list.append(config_data[idx:])
|
||||
new_conf = "".join(new_conf_list)
|
||||
|
||||
write_file(config_file, new_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(config_file, config_data)
|
||||
return "配置修改失败"
|
||||
if webserver() == "apache" and not mutil:
|
||||
service_reload()
|
||||
|
||||
# 设置站点的日志路径
|
||||
def set_site_log_path(self, site_name, site_log_path, conf_prefix, mutil=False) -> Optional[str]:
|
||||
if not os.path.isdir(site_log_path):
|
||||
return "不是一个存在的文件夹路径"
|
||||
|
||||
if site_log_path[-1] == "/":
|
||||
site_log_path = site_log_path[:-1]
|
||||
|
||||
# apache
|
||||
apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name)
|
||||
apache_config = read_file(apache_config_path)
|
||||
if not apache_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
|
||||
# apache
|
||||
old_log_file = self.apache_get_log_file_path(apache_config, site_name, is_error_log=False)
|
||||
old_error_log_file = self.apache_get_log_file_path(apache_config, site_name, is_error_log=True)
|
||||
|
||||
if old_log_file and old_error_log_file:
|
||||
new_apache_conf = apache_config
|
||||
log_file_rep = re.compile(r'''CustomLog +['"]?''' + pre_re_key(old_log_file) + '''['"]?''')
|
||||
error_log_file_rep = re.compile(r'''ErrorLog +['"]?''' + pre_re_key(old_error_log_file) + '''['"]?''')
|
||||
if log_file_rep.search(apache_config):
|
||||
new_apache_conf = log_file_rep.sub('CustomLog "{}/{}-access_log"'.format(site_log_path, site_name),
|
||||
new_apache_conf)
|
||||
|
||||
if error_log_file_rep.search(apache_config):
|
||||
new_apache_conf = error_log_file_rep.sub('ErrorLog "{}/{}.-error_log"'.format(site_log_path, site_name),
|
||||
new_apache_conf)
|
||||
write_file(apache_config_path, new_apache_conf)
|
||||
print(new_apache_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(apache_config_path, apache_config)
|
||||
return "配置修改失败"
|
||||
if webserver() == "apache" and not mutil:
|
||||
service_reload()
|
||||
else:
|
||||
return "未找到日志配置,无法操作"
|
||||
|
||||
@staticmethod
|
||||
def apache_get_log_file_path(apache_config: str, site_name: str, is_error_log: bool = False):
|
||||
log_file = None
|
||||
if is_error_log:
|
||||
re_data = re.findall(r'''ErrorLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config)
|
||||
else:
|
||||
re_data = re.findall(r'''CustomLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config)
|
||||
if re_data is None:
|
||||
log_file = None
|
||||
else:
|
||||
for i in re_data:
|
||||
file_path = i[0].strip('"').strip("'")
|
||||
if file_path != "/dev/null":
|
||||
if os.path.isdir(os.path.dirname(file_path)):
|
||||
log_file = file_path
|
||||
break
|
||||
|
||||
logsPath = '/www/wwwlogs/'
|
||||
if log_file is None:
|
||||
if is_error_log:
|
||||
log_file = logsPath + site_name + '-access_log'
|
||||
else:
|
||||
log_file = logsPath + site_name + '-error_log'
|
||||
if not os.path.isfile(log_file):
|
||||
log_file = None
|
||||
|
||||
return log_file
|
||||
|
||||
@staticmethod
|
||||
def close_access_log(site_name, conf_prefix) -> Optional[str]:
|
||||
apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name)
|
||||
apache_config = read_file(apache_config_path)
|
||||
if not apache_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
custom_log_rep = re.compile(r'''CustomLog +['"]?(/(\S+/?)+)['"]?(\s*.*)?''', re.M)
|
||||
new_conf_list = []
|
||||
idx = 0
|
||||
for tmp_res in custom_log_rep.finditer(apache_config):
|
||||
new_conf_list.append(apache_config[idx:tmp_res.start()])
|
||||
new_conf_list.append("# " + tmp_res.group())
|
||||
idx = tmp_res.end()
|
||||
new_conf_list.append(apache_config[idx:])
|
||||
new_conf = "".join(new_conf_list)
|
||||
write_file(apache_config_path, new_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(apache_config_path, apache_config)
|
||||
return "配置修改失败"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def open_access_log(site_name, conf_prefix) -> Optional[str]:
|
||||
apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name)
|
||||
apache_config = read_file(apache_config_path)
|
||||
if not apache_config:
|
||||
return "网站配置文件丢失,无法配置"
|
||||
new_conf = apache_config.replace("#CustomLog", "CustomLog")
|
||||
write_file(apache_config_path, new_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(apache_config_path, apache_config)
|
||||
return "配置修改失败"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def access_log_is_open(site_name, conf_prefix) -> bool:
|
||||
apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name)
|
||||
apache_config = read_file(apache_config_path)
|
||||
if not apache_config:
|
||||
return False
|
||||
if apache_config.find("#CustomLog") != -1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_site_log_path(self, site_name, conf_prefix) -> Union[str, dict]:
|
||||
config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name)
|
||||
config = read_file(config_path)
|
||||
if not config:
|
||||
return "站点配置文件丢失"
|
||||
log_file = self.apache_get_log_file_path(config, site_name, is_error_log=False)
|
||||
error_log_file = self.apache_get_log_file_path(config, site_name, is_error_log=False)
|
||||
if not (error_log_file and log_file):
|
||||
return "获取失败"
|
||||
return {
|
||||
"log_file": log_file,
|
||||
"error_log_file": error_log_file,
|
||||
}
|
||||
|
||||
@property
|
||||
def log_format(self) -> dict:
|
||||
if self._format_dict is None:
|
||||
self._format_dict = {
|
||||
"server_addr": {
|
||||
"name": "服务器地址",
|
||||
"key": "%A",
|
||||
},
|
||||
"server_port": {
|
||||
"name": "服务器端口",
|
||||
"key": "%p",
|
||||
},
|
||||
"host": {
|
||||
"name": "域名",
|
||||
"key": "%V",
|
||||
},
|
||||
"remote_addr": {
|
||||
"name": "客户端地址",
|
||||
"key": "%{c}a",
|
||||
},
|
||||
"remote_port": {
|
||||
"name": "客户端端口",
|
||||
"key": "%{remote}p",
|
||||
},
|
||||
"protocol": {
|
||||
"name": "服务器协议",
|
||||
"key": "%H",
|
||||
},
|
||||
"method": {
|
||||
"name": "请求方法",
|
||||
"key": "%m",
|
||||
},
|
||||
"uri": {
|
||||
"name": "请求uri",
|
||||
"key": r"\"%U\"",
|
||||
},
|
||||
"status": {
|
||||
"name": "状态码",
|
||||
"key": "%>s",
|
||||
},
|
||||
"sent_bytes": {
|
||||
"name": "发送字节数",
|
||||
"key": "%B",
|
||||
},
|
||||
"referer": {
|
||||
"name": "来源地址",
|
||||
"key": r"\"%{Referer}i\"",
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "用户代理(User-Agent)",
|
||||
"key": r"\"%{User-Agent}i\"",
|
||||
},
|
||||
"take_time": {
|
||||
"name": "请求用时",
|
||||
"key": "%{ms}T",
|
||||
},
|
||||
}
|
||||
return self._format_dict
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._config_file = "{}/data/ap_log_format.json".format(self.panel_path)
|
||||
self._log_format_dir = "{}/vhost/apache/log_format".format(self.panel_path)
|
||||
|
||||
def _set_log_format_include(self) -> Optional[str]:
|
||||
config_file = "/www/server/apache/conf/httpd.conf"
|
||||
config_data = read_file(config_file)
|
||||
if not config_data:
|
||||
return "配置文件丢失无法操作"
|
||||
if not os.path.isdir(self._log_format_dir):
|
||||
os.makedirs(self._log_format_dir)
|
||||
rep_include = re.compile(r"IncludeOptional\s+/www/server/panel/vhost/apache/log_format/\*\.conf")
|
||||
if rep_include.search(config_data):
|
||||
return
|
||||
new_conf = config_data + """
|
||||
<IfModule log_config_module>
|
||||
IncludeOptional /www/server/panel/vhost/apache/log_format/*.conf
|
||||
</IfModule>
|
||||
"""
|
||||
write_file(config_file, new_conf)
|
||||
|
||||
def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]:
|
||||
error_msg = self._set_log_format_include()
|
||||
if error_msg:
|
||||
return error_msg
|
||||
conf_file = self._log_format_dir + "/{}_format.conf".format(name)
|
||||
write_file(conf_file, (
|
||||
'LogFormat "{}" {}'.format(space_character.join(map(lambda x: self.log_format[x]["key"], keys)), name)
|
||||
))
|
||||
|
||||
|
||||
class RealLogMgr:
|
||||
|
||||
def __init__(self, conf_prefix: str = ""):
|
||||
self.conf_prefix = conf_prefix
|
||||
if webserver() == "nginx":
|
||||
self._log_format_tool = _NgLog()
|
||||
else:
|
||||
self._log_format_tool = _ApLog()
|
||||
|
||||
@staticmethod
|
||||
def remove_site_log_format_info(site_name: str):
|
||||
for logtool in (_NgLog(), _ApLog()):
|
||||
for _, conf in logtool.config.items():
|
||||
if site_name in conf.get("sites", []):
|
||||
conf["sites"].remove(site_name)
|
||||
logtool.save_config()
|
||||
|
||||
def log_format_data(self, site_name: str):
|
||||
log_format_data = None
|
||||
for name, data in self._log_format_tool.config.items():
|
||||
if site_name in data.get("sites", []):
|
||||
log_format_data = data
|
||||
log_format_data.update(name=name)
|
||||
return {
|
||||
"log_format": log_format_data,
|
||||
"rule": self._log_format_tool.log_format,
|
||||
"all_log_format": self._log_format_tool.config
|
||||
}
|
||||
|
||||
def add_log_format(self, name: str, keys: List[str], space_character=" ") -> Optional[str]:
|
||||
return self._log_format_tool.add_log_format(name, keys, space_character)
|
||||
|
||||
def modify_log_format(self, name: str, keys: List[str], space_character=None) -> Optional[str]:
|
||||
return self._log_format_tool.modify_log_format(name, keys, space_character)
|
||||
|
||||
def remove_log_format(self, name: str) -> Optional[str]:
|
||||
return self._log_format_tool.remove_log_format(name)
|
||||
|
||||
# log_format_name 为空字符串时表示恢复成默认的日志格式
|
||||
def set_site_log_format(self, site_name, log_format_name, mutil=False) -> Optional[str]:
|
||||
return self._log_format_tool.set_site_log_format(site_name, log_format_name, self.conf_prefix, mutil)
|
||||
|
||||
def set_site_log_path(self, site_name, site_log_path, mutil=False) -> Optional[str]:
|
||||
return self._log_format_tool.set_site_log_path(site_name, site_log_path, self.conf_prefix, mutil)
|
||||
|
||||
def get_site_log_path(self, site_name) -> Union[str, dict]:
|
||||
return self._log_format_tool.get_site_log_path(site_name, self.conf_prefix)
|
||||
|
||||
@staticmethod
|
||||
def site_crontab_log(site_name: str, hour: int, minute: int, save: int) -> bool:
|
||||
if DB("crontab").where("sName =? and sType = ?", ("ALL", "logs")).find():
|
||||
return True
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
import crontab
|
||||
crontabs = crontab.crontab()
|
||||
args = {
|
||||
"name": "切割日志[{}]".format(site_name),
|
||||
"type": 'day',
|
||||
"where1": '',
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
"sName": site_name,
|
||||
"sType": 'logs',
|
||||
"notice": '',
|
||||
"notice_channel": '',
|
||||
"save": save,
|
||||
"save_local": '1',
|
||||
"backupTo": '',
|
||||
"sBody": '',
|
||||
"urladdress": ''
|
||||
}
|
||||
res = crontabs.AddCrontab(args)
|
||||
if res and "id" in res.keys():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class LogMgr:
|
||||
|
||||
def __init__(self, conf_prefix: str = ""):
|
||||
self.conf_prefix = conf_prefix
|
||||
self._real_log_mgr = RealLogMgr(self.conf_prefix)
|
||||
|
||||
def log_format_data(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
data = self._real_log_mgr.log_format_data(site_name)
|
||||
return json_response(status=True, data=data)
|
||||
|
||||
def add_log_format(self, get):
|
||||
try:
|
||||
space_character = " "
|
||||
format_name = get.format_name.strip()
|
||||
keys = json.loads(get.keys.strip())
|
||||
if "space_character" in get:
|
||||
space_character = get.space_character
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.add_log_format(format_name, keys, space_character)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="添加成功")
|
||||
|
||||
def modify_log_format(self, get):
|
||||
try:
|
||||
space_character = None
|
||||
format_name = get.format_name.strip()
|
||||
keys = json.loads(get.keys.strip())
|
||||
if "space_character" in get:
|
||||
space_character = get.space_character
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.modify_log_format(format_name, keys, space_character)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="修改成功")
|
||||
|
||||
def remove_log_format(self, get):
|
||||
try:
|
||||
format_name = get.format_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.remove_log_format(format_name)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="Successfully delete")
|
||||
|
||||
def set_site_log_format(self, get):
|
||||
try:
|
||||
format_name = get.format_name.strip()
|
||||
site_name = get.site_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.set_site_log_format(site_name, log_format_name=format_name)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="添加成功")
|
||||
|
||||
def set_site_log_path(self, get):
|
||||
try:
|
||||
log_path = get.log_path.strip()
|
||||
site_name = get.site_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.set_site_log_path(site_name, site_log_path=log_path)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="修改路径成功")
|
||||
|
||||
def get_site_log_path(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.get_site_log_path(site_name)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, data=msg)
|
||||
|
||||
def site_crontab_log(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
hour = int(get.hour.strip())
|
||||
minute = int(get.minute.strip())
|
||||
save = int(get.save.strip())
|
||||
except (AttributeError, json.JSONDecodeError, TypeError, ValueError):
|
||||
return json_response(status=False, msg="参数类型错误")
|
||||
|
||||
msg = self._real_log_mgr.site_crontab_log(site_name, hour=hour, minute=minute, save=save)
|
||||
if isinstance(msg, str):
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, data=msg)
|
||||
90
mod/base/web_conf/nginx_realip.py
Normal file
90
mod/base/web_conf/nginx_realip.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import ipaddress
|
||||
import os
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from .util import webserver, check_server_config, write_file, read_file, service_reload
|
||||
|
||||
|
||||
class NginxRealIP:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def set_real_ip(self, site_name: str, ip_header: str, allow_ip: List[str], recursive: bool = False) -> Optional[
|
||||
str]:
|
||||
if not webserver() == 'nginx':
|
||||
return "only nginx web server is supported"
|
||||
res = check_server_config()
|
||||
if res:
|
||||
return "conifg error, please fix it first. ERROR: %s".format(res)
|
||||
self._set_ext_real_ip_file(site_name, status=True, ip_header=ip_header, allow_ip=allow_ip, recursive=recursive)
|
||||
res = check_server_config()
|
||||
if res:
|
||||
self._set_ext_real_ip_file(site_name, status=False, ip_header="", allow_ip=[], recursive=False)
|
||||
return "配置失败:{}".format(res)
|
||||
else:
|
||||
service_reload()
|
||||
|
||||
def close_real_ip(self, site_name: str):
|
||||
self._set_ext_real_ip_file(site_name, status=False, ip_header="", allow_ip=[], recursive=False)
|
||||
service_reload()
|
||||
return
|
||||
|
||||
def get_real_ip(self, site_name: str) -> Dict[str, Any]:
|
||||
return self._read_ext_real_ip_file(site_name)
|
||||
|
||||
def _set_ext_real_ip_file(self, site_name: str, status: bool, ip_header: str, allow_ip: List[str],
|
||||
recursive: bool = False):
|
||||
ext_file = "/www/server/panel/vhost/nginx/extension/{}/proxy_real_ip.conf".format(site_name)
|
||||
if not status:
|
||||
if os.path.exists(ext_file):
|
||||
os.remove(ext_file)
|
||||
return
|
||||
|
||||
if not os.path.exists(os.path.dirname(ext_file)):
|
||||
os.makedirs(os.path.dirname(ext_file))
|
||||
real_ip_from = ""
|
||||
for ip in allow_ip:
|
||||
tmp_ip = self.formatted_ip(ip)
|
||||
if tmp_ip:
|
||||
real_ip_from += " set_real_ip_from {};\n".format(ip)
|
||||
if not real_ip_from:
|
||||
real_ip_from = "set_real_ip_from 0.0.0.0/0;\nset_real_ip_from ::/0;\n"
|
||||
conf_data = "{}real_ip_header {};\nreal_ip_recursive {};\n".format(
|
||||
real_ip_from, ip_header, "on" if recursive else "off"
|
||||
)
|
||||
write_file(ext_file, conf_data)
|
||||
|
||||
@staticmethod
|
||||
def _read_ext_real_ip_file(site_name: str) -> Dict[str, Any]:
|
||||
ret = {
|
||||
"ip_header": "",
|
||||
"allow_ip": [],
|
||||
"recursive": False
|
||||
}
|
||||
ext_file = "/www/server/panel/vhost/nginx/extension/{}/proxy_real_ip.conf".format(site_name)
|
||||
if os.path.exists(ext_file):
|
||||
data = read_file(ext_file)
|
||||
if data:
|
||||
for line in data.split("\n"):
|
||||
line = line.strip("; ")
|
||||
if line.startswith("real_ip_header"):
|
||||
ret["ip_header"] = line.split()[1]
|
||||
elif line.startswith("set_real_ip_from"):
|
||||
ret["allow_ip"].append(line.split()[1])
|
||||
elif line.startswith("real_ip_recursive"):
|
||||
ret["recursive"] = True if line.split()[1] == "on" else False
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def formatted_ip(ip: str) -> str:
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip)
|
||||
return ip.compressed
|
||||
except:
|
||||
try:
|
||||
ip = ipaddress.ip_network(ip)
|
||||
return ip.compressed
|
||||
except:
|
||||
pass
|
||||
return ""
|
||||
639
mod/base/web_conf/proxy.py
Normal file
639
mod/base/web_conf/proxy.py
Normal file
@@ -0,0 +1,639 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import shutil
|
||||
import warnings
|
||||
import sys
|
||||
import traceback
|
||||
from hashlib import md5
|
||||
from typing import Tuple, Optional, Union, List, Dict, Any
|
||||
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key
|
||||
from mod.base import json_response
|
||||
|
||||
warnings.filterwarnings("ignore", category=SyntaxWarning)
|
||||
|
||||
class RealProxy:
|
||||
panel_path = "/www/server/panel"
|
||||
_proxy_conf_file = "{}/data/mod_proxy_file.conf".format(panel_path)
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
self.config_prefix: str = config_prefix
|
||||
self._config: Optional[List[dict]] = None
|
||||
|
||||
# {
|
||||
# "proxyname": "yyy",
|
||||
# "sitename": "www.12345test.com",
|
||||
# "proxydir": "/",
|
||||
# "proxysite": "http://www.baidu.com",
|
||||
# "todomain": "www.baidu.com",
|
||||
# "type": 0,
|
||||
# "cache": 0,
|
||||
# "subfilter": [
|
||||
# {"sub1": "", "sub2": ""},
|
||||
# {"sub1": "", "sub2": ""},
|
||||
# {"sub1": "", "sub2": ""}],
|
||||
# "advanced": 1,
|
||||
# "cachetime": 1
|
||||
# }
|
||||
|
||||
@property
|
||||
def config(self) -> List[dict]:
|
||||
if self._config is None:
|
||||
try:
|
||||
self._config = json.loads(read_file(self._proxy_conf_file))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
self._config = []
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
if self._config is not None:
|
||||
write_file(self._proxy_conf_file, json.dumps(self._config))
|
||||
|
||||
# 检查代理是否存在
|
||||
def _check_even(self, proxy_conf: dict, is_modify) -> bool:
|
||||
for i in self.config:
|
||||
if i["sitename"] == proxy_conf["sitename"]:
|
||||
if is_modify is False:
|
||||
if i["proxydir"] == proxy_conf["proxydir"] or i["proxyname"] == proxy_conf["proxyname"]:
|
||||
return True
|
||||
else:
|
||||
if i["proxyname"] != proxy_conf["proxyname"] and i["proxydir"] == proxy_conf["proxydir"]:
|
||||
return True
|
||||
|
||||
# 检测全局代理和目录代理是否同时存在
|
||||
def _check_proxy_even(self, proxy_conf: dict, is_modify) -> bool:
|
||||
n = 0
|
||||
if is_modify:
|
||||
for i in self.config:
|
||||
if i["sitename"] == proxy_conf["sitename"]:
|
||||
n += 1
|
||||
if n == 1:
|
||||
return False
|
||||
for i in self.config:
|
||||
if i["sitename"] == proxy_conf["sitename"]:
|
||||
if i["advanced"] != proxy_conf["advanced"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_args(self, get, is_modify=False) -> Union[str, dict]:
|
||||
if check_server_config():
|
||||
return '配置文件出错请先排查配置'
|
||||
data = {
|
||||
"advanced": 0,
|
||||
"proxydir": "",
|
||||
"cache": 0,
|
||||
"cachetime": 1,
|
||||
"type": 0,
|
||||
"todomain": "$host",
|
||||
}
|
||||
try:
|
||||
data["proxyname"] = get.proxyname.strip()
|
||||
data["sitename"] = get.sitename.strip()
|
||||
if "proxydir" in get:
|
||||
data["proxydir"] = get.proxydir.strip()
|
||||
data["proxysite"] = get.proxysite.strip()
|
||||
if "todomain" in get:
|
||||
data["todomain"] = get.todomain.strip()
|
||||
data["type"] = int(get.type.strip())
|
||||
data["cache"] = int(get.cache.strip())
|
||||
data["subfilter"] = json.loads(get.subfilter.strip())
|
||||
data["advanced"] = int(get.advanced.strip())
|
||||
data["cachetime"] = int(get.cachetime.strip())
|
||||
except:
|
||||
return "Parameter error"
|
||||
|
||||
if is_modify is False:
|
||||
if len(data["proxyname"]) < 3 or len(data["proxyname"]) > 40:
|
||||
return '名称必须大于3小于40个字符串'
|
||||
|
||||
if self._check_even(data, is_modify):
|
||||
return '指定反向代理名称或代理文件夹已存在'
|
||||
# 判断代理,只能有全局代理或目录代理
|
||||
if self._check_proxy_even(data, is_modify):
|
||||
return '不能同时设置目录代理和全局代理'
|
||||
# 判断cachetime类型
|
||||
if data["cachetime"] < 1:
|
||||
return "缓存时间不能为空"
|
||||
|
||||
rep = r"http(s)?\:\/\/"
|
||||
rep_re_key = re.compile(r'''[?=\[\])(*&^%$#@!~`{}><,'"\\]+''')
|
||||
# 检测代理目录格式
|
||||
if rep_re_key.search(data["proxydir"]):
|
||||
return "The agency directory cannot contain the following special symbols ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]"
|
||||
# 检测发送域名格式
|
||||
if get.todomain:
|
||||
if re.search("[}{#;\"\']+", data["todomain"]):
|
||||
return '发送域名格式错误:' + data["todomain"] + '<br>不能存在以下特殊字符【 } { # ; \" \' 】 '
|
||||
if webserver() != 'openlitespeed' and not get.todomain:
|
||||
data["todomain"] = "$host"
|
||||
|
||||
# 检测目标URL格式
|
||||
if not re.match(rep, data["proxysite"]):
|
||||
return '域名格式错误 ' + data["proxysite"]
|
||||
if rep_re_key.search(data["proxysite"]):
|
||||
return "目标URL不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]"
|
||||
|
||||
if not data["proxysite"].split('//')[-1]:
|
||||
return '目标URL不能为[http://或https://],请填写完整URL,如:https://www.yakpanel.com'
|
||||
|
||||
for s in data["subfilter"]:
|
||||
if not s["sub1"]:
|
||||
continue
|
||||
if not s["sub1"] and s["sub2"]:
|
||||
return '请输入被替换的内容'
|
||||
elif s["sub1"] == s["sub2"]:
|
||||
return '替换内容与被替换内容不能一致'
|
||||
return data
|
||||
|
||||
def check_location(self, site_name, proxy_dir: str) -> Optional[str]:
|
||||
# 伪静态文件路径
|
||||
rewrite_conf_path = "%s/vhost/rewrite/%s%s.conf" % (self.panel_path, self.config_prefix, site_name)
|
||||
# vhost文件
|
||||
vhost_path = "%s/vhost/nginx/%s%s.conf" % (self.panel_path, self.config_prefix, site_name)
|
||||
|
||||
rep_location = re.compile(r"location\s+(\^~\s*)?%s\s*{" % proxy_dir)
|
||||
|
||||
for i in [rewrite_conf_path, vhost_path]:
|
||||
conf = read_file(i)
|
||||
if isinstance(conf, str) and rep_location.search(conf):
|
||||
return '伪静态/站点主配置文件已经存在全局反向代理'
|
||||
|
||||
@staticmethod
|
||||
def _set_nginx_proxy_base():
|
||||
file = "/www/server/nginx/conf/proxy.conf"
|
||||
setup_path = "/www/server"
|
||||
if not os.path.exists(file):
|
||||
conf = '''proxy_temp_path %s/nginx/proxy_temp_dir;
|
||||
proxy_cache_path %s/nginx/proxy_cache_dir levels=1:2 keys_zone=cache_one:10m inactive=1d max_size=5g;
|
||||
client_body_buffer_size 512k;
|
||||
proxy_connect_timeout 60;
|
||||
proxy_read_timeout 60;
|
||||
proxy_send_timeout 60;
|
||||
proxy_buffer_size 32k;
|
||||
proxy_buffers 4 64k;
|
||||
proxy_busy_buffers_size 128k;
|
||||
proxy_temp_file_write_size 128k;
|
||||
proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;
|
||||
proxy_cache cache_one;''' % (setup_path, setup_path)
|
||||
write_file(file, conf)
|
||||
|
||||
conf = read_file(file)
|
||||
if conf and conf.find('include proxy.conf;') == -1:
|
||||
rep = r"include\s+mime.types;"
|
||||
conf = re.sub(rep, "include mime.types;\n\tinclude proxy.conf;", conf)
|
||||
write_file(file, conf)
|
||||
|
||||
def set_nginx_proxy_include(self, site_name) -> Optional[str]:
|
||||
self._set_nginx_proxy_base()
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not ng_conf:
|
||||
return "配置文件丢失"
|
||||
cure_cache = '''location ~ /purge(/.*) {
|
||||
proxy_cache_purge cache_one $host$1$is_args$args;
|
||||
#access_log /www/wwwlogs/%s_purge_cache.log;
|
||||
}''' % site_name
|
||||
|
||||
proxy_dir = "{}/vhost/nginx/proxy/{}".format(self.panel_path, site_name)
|
||||
if not os.path.isdir(os.path.dirname(proxy_dir)):
|
||||
os.makedirs(os.path.dirname(proxy_dir))
|
||||
|
||||
if not os.path.isdir(proxy_dir):
|
||||
os.makedirs(proxy_dir)
|
||||
|
||||
include_conf = (
|
||||
"\n #清理缓存规则\n"
|
||||
" %s\n"
|
||||
" #引用反向代理规则,注释后配置的反向代理将无效\n"
|
||||
" include /www/server/panel/vhost/nginx/proxy/%s/*.conf;\n"
|
||||
) % (cure_cache, site_name)
|
||||
|
||||
rep_include = re.compile(r"\s*include.*/proxy/.*/\*\.conf\s*;", re.M)
|
||||
if rep_include.search(ng_conf):
|
||||
return
|
||||
# 添加 引入
|
||||
rep_list = [
|
||||
(re.compile(r"\s*include\s+.*/rewrite/.*\.conf;(\s*#REWRITE-END)?"), False), # 先匹配伪静态,有伪静态就加到伪静态下
|
||||
(re.compile(r"#PHP-INFO-END"), False), # 匹配PHP配置, 加到php配置下
|
||||
(re.compile(r"\sinclude +.*/ip-restrict/.*\*\.conf;", re.M), False), # 匹配IP配置, 加其下
|
||||
(re.compile(r"#SECURITY-END"), False), # 匹配Referer配置, 加其下
|
||||
]
|
||||
|
||||
# 使用正则匹配确定插入位置
|
||||
def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool:
|
||||
tmp_res = tmp_rep.search(ng_conf)
|
||||
if not tmp_res:
|
||||
return False
|
||||
if use_start:
|
||||
new_conf = ng_conf[:tmp_res.start()] + include_conf + tmp_res.group() + ng_conf[tmp_res.end():]
|
||||
else:
|
||||
new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + include_conf + ng_conf[tmp_res.end():]
|
||||
|
||||
write_file(ng_file, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return False
|
||||
return True
|
||||
for r, s in rep_list:
|
||||
if set_by_rep_idx(r, s):
|
||||
break
|
||||
else:
|
||||
return "无法在配置文件中定位到需要添加的项目"
|
||||
|
||||
now_ng_conf = read_file(ng_file)
|
||||
# 清理文件缓存
|
||||
rep_location = re.compile(r"location\s+~\s+\.\*\\\.[^{]*{(\s*(expires|error_log|access_log).*;){3}\s*}\s*")
|
||||
|
||||
new__ng_conf = rep_location.sub("", now_ng_conf)
|
||||
write_file(ng_file, new__ng_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, now_ng_conf)
|
||||
|
||||
def un_set_nginx_proxy_include(self, site_name) -> Optional[str]:
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not ng_conf:
|
||||
return "配置文件丢失"
|
||||
rep_list = [
|
||||
re.compile(r"\s*#清理缓存规则\n"),
|
||||
re.compile(r"\s*location\s+~\s+/purge[^{]*{[^}]*}\s*"),
|
||||
re.compile(r"(#[^#\n]*\n)?\s*include.*/proxy/.*/\*\.conf\s*;[^\n]*\n"),
|
||||
]
|
||||
new_conf = ng_conf
|
||||
for rep in rep_list:
|
||||
new_conf = rep.sub("", new_conf, 1)
|
||||
|
||||
write_file(ng_file, new_conf)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return "配置移除失败"
|
||||
|
||||
def set_apache_proxy_include(self, site_name):
|
||||
ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, site_name)
|
||||
ap_conf = read_file(ap_file)
|
||||
if not ap_conf:
|
||||
return "配置文件丢失"
|
||||
proxy_dir = "{}/vhost/apache/proxy/{}".format(self.panel_path, site_name)
|
||||
|
||||
if not os.path.isdir(os.path.dirname(proxy_dir)):
|
||||
os.makedirs(os.path.dirname(proxy_dir))
|
||||
if not os.path.isdir(proxy_dir):
|
||||
os.makedirs(proxy_dir)
|
||||
|
||||
include_conf = (
|
||||
" #引用反向代理规则,注释后配置的反向代理将无效\n"
|
||||
" IncludeOptional /www/server/panel/vhost/apache/proxy/%s/*.conf\n"
|
||||
) % site_name
|
||||
|
||||
rep_include = re.compile(r"\s*IncludeOptional.*/proxy/.*/\*\.conf\s*;", re.M)
|
||||
if rep_include.search(ap_conf):
|
||||
return
|
||||
|
||||
# 添加 引入
|
||||
rep_list = [
|
||||
(re.compile(r"<FilesMatch \\\.php\$>(.|\n)*?</FilesMatch>[^\n]*\n"), False), # 匹配PHP配置, 加到php配置下
|
||||
(re.compile(r"CustomLog[^\n]*\n"), False), # 匹配Referer配置, 加其下
|
||||
]
|
||||
|
||||
# 使用正则匹配确定插入位置
|
||||
def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool:
|
||||
new_conf_list = []
|
||||
last_idx = 0
|
||||
for tmp in rep.finditer(ap_conf):
|
||||
new_conf_list.append(ap_conf[last_idx:tmp.start()])
|
||||
if use_start:
|
||||
new_conf_list.append(include_conf)
|
||||
new_conf_list.append(tmp.group())
|
||||
else:
|
||||
new_conf_list.append(tmp.group())
|
||||
new_conf_list.append(include_conf)
|
||||
last_idx = tmp.end()
|
||||
if last_idx == 0:
|
||||
return False
|
||||
|
||||
new_conf_list.append(ap_conf[last_idx:])
|
||||
|
||||
new_conf = "".join(new_conf_list)
|
||||
write_file(ap_file, new_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return False
|
||||
return True
|
||||
|
||||
for r, s in rep_list:
|
||||
if set_by_rep_idx(r, s):
|
||||
break
|
||||
else:
|
||||
return "无法在配置文件中定位到需要添加的项目"
|
||||
|
||||
def un_set_apache_proxy_include(self, site_name) -> Optional[str]:
|
||||
ng_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, site_name)
|
||||
ap_conf = read_file(ng_file)
|
||||
if not ap_conf:
|
||||
return "配置文件丢失"
|
||||
rep_include = re.compile(r"(#.*\n)?\s*IncludeOptiona.*/proxy/.*/\*\.conf\s*[^\n]\n")
|
||||
|
||||
new_conf = rep_include.sub("", ap_conf)
|
||||
|
||||
write_file(ng_file, new_conf)
|
||||
if webserver() == "apache" and check_server_config() is not None:
|
||||
write_file(ng_file, ap_conf)
|
||||
return "配置移除失败"
|
||||
|
||||
def set_nginx_proxy(self, proxy_data: dict) -> Optional[str]:
|
||||
proxy_name_md5 = self._calc_proxy_name_md5(proxy_data["proxyname"])
|
||||
ng_proxy_file = "%s/vhost/nginx/proxy/%s/%s_%s.conf" % (
|
||||
self.panel_path, proxy_data["sitename"], proxy_name_md5, proxy_data["sitename"])
|
||||
if proxy_data["type"] == 0:
|
||||
if os.path.isfile(ng_proxy_file):
|
||||
os.remove(ng_proxy_file)
|
||||
return
|
||||
|
||||
random_string = self._random_string()
|
||||
|
||||
# websocket前置map
|
||||
map_file = "{}/vhost/nginx/0.websocket.conf".format(self.panel_path)
|
||||
if not os.path.exists(map_file):
|
||||
write_file(map_file, '''
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}''')
|
||||
# 构造缓存配置
|
||||
ng_cache = r"""
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
expires 1m;
|
||||
}
|
||||
proxy_ignore_headers Set-Cookie Cache-Control expires;
|
||||
proxy_cache cache_one;
|
||||
proxy_cache_key $host$uri$is_args$args;
|
||||
proxy_cache_valid 200 304 301 302 %sm;""" % proxy_data["cachetime"]
|
||||
no_cache = r"""
|
||||
set $static_file%s 0;
|
||||
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
|
||||
{
|
||||
set $static_file%s 1;
|
||||
expires 1m;
|
||||
}
|
||||
if ( $static_file%s = 0 )
|
||||
{
|
||||
add_header Cache-Control no-cache;
|
||||
}""" % (random_string, random_string, random_string)
|
||||
|
||||
ng_proxy = '''
|
||||
#PROXY-START%s
|
||||
|
||||
location ^~ %s
|
||||
{
|
||||
proxy_pass %s;
|
||||
proxy_set_header Host %s;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_http_version 1.1;
|
||||
# proxy_hide_header Upgrade;
|
||||
|
||||
add_header X-Cache $upstream_cache_status;
|
||||
|
||||
#Set Nginx Cache
|
||||
%s
|
||||
%s
|
||||
}
|
||||
|
||||
#PROXY-END%s'''
|
||||
|
||||
# 构造替换字符串
|
||||
ng_sub_data_list = []
|
||||
for s in proxy_data["subfilter"]:
|
||||
if not s["sub1"]:
|
||||
continue
|
||||
if '"' in s["sub1"]:
|
||||
s["sub1"] = s["sub1"].replace('"', '\\"')
|
||||
if '"' in s["sub2"]:
|
||||
s["sub2"] = s["sub2"].replace('"', '\\"')
|
||||
ng_sub_data_list.append(' sub_filter "%s" "%s";' % (s["sub1"], s["sub2"]))
|
||||
if ng_sub_data_list:
|
||||
ng_sub_filter = '''
|
||||
proxy_set_header Accept-Encoding "";
|
||||
%s
|
||||
sub_filter_once off;''' % "\n".join(ng_sub_data_list)
|
||||
else:
|
||||
ng_sub_filter = ''
|
||||
|
||||
if proxy_data["proxydir"][-1] != '/':
|
||||
proxy_dir = proxy_data["proxydir"] + "/"
|
||||
else:
|
||||
proxy_dir = proxy_data["proxydir"]
|
||||
|
||||
if proxy_data["proxysite"][-1] != '/':
|
||||
proxy_site = proxy_data["proxysite"] + "/"
|
||||
else:
|
||||
proxy_site = proxy_data["proxysite"]
|
||||
|
||||
# 构造反向代理
|
||||
if proxy_data["cache"] == 1:
|
||||
ng_proxy_cache = ng_proxy % (
|
||||
proxy_dir, proxy_dir, proxy_site, proxy_data["todomain"], ng_sub_filter, ng_cache, proxy_dir)
|
||||
else:
|
||||
ng_proxy_cache = ng_proxy % (
|
||||
proxy_dir, proxy_dir, proxy_site, proxy_data["todomain"], ng_sub_filter, no_cache, proxy_dir)
|
||||
|
||||
write_file(ng_proxy_file, ng_proxy_cache)
|
||||
if webserver() == "nginx" and check_server_config() is not None:
|
||||
import public
|
||||
public.print_log(check_server_config())
|
||||
os.remove(ng_proxy_file)
|
||||
return "配置添加失败"
|
||||
|
||||
def set_apache_proxy(self, proxy_data: dict):
|
||||
proxy_name_md5 = self._calc_proxy_name_md5(proxy_data["proxyname"])
|
||||
ap_proxy_file = "%s/vhost/apache/proxy/%s/%s_%s.conf" % (
|
||||
self.panel_path, proxy_data["sitename"], proxy_name_md5, proxy_data["sitename"])
|
||||
if proxy_data["type"] == 0:
|
||||
if os.path.isfile(ap_proxy_file):
|
||||
os.remove(ap_proxy_file)
|
||||
return
|
||||
|
||||
ap_proxy = '''#PROXY-START%s
|
||||
<IfModule mod_proxy.c>
|
||||
ProxyRequests Off
|
||||
SSLProxyEngine on
|
||||
ProxyPass %s %s/
|
||||
ProxyPassReverse %s %s/
|
||||
</IfModule>
|
||||
#PROXY-END%s''' % (proxy_data["proxydir"], proxy_data["proxydir"], proxy_data["proxysite"],
|
||||
proxy_data["proxydir"],proxy_data["proxysite"], proxy_data["proxydir"])
|
||||
write_file(ap_proxy_file, ap_proxy)
|
||||
|
||||
@staticmethod
|
||||
def _random_string() -> str:
|
||||
from uuid import uuid4
|
||||
return "bt" + uuid4().hex[:6]
|
||||
|
||||
@staticmethod
|
||||
def _calc_proxy_name_md5(data: str) -> str:
|
||||
m = md5()
|
||||
m.update(data.encode("utf-8"))
|
||||
return m.hexdigest()
|
||||
|
||||
def create_proxy(self, get) -> Optional[str]:
|
||||
proxy_data = self.check_args(get, is_modify=False)
|
||||
if isinstance(proxy_data, str):
|
||||
return proxy_data
|
||||
if webserver() == "nginx":
|
||||
error_msg = self.check_location(proxy_data["sitename"], proxy_data["proxydir"])
|
||||
if error_msg:
|
||||
return error_msg
|
||||
|
||||
error_msg = self.set_nginx_proxy_include(proxy_data["sitename"])
|
||||
if webserver() == "nginx" and error_msg:
|
||||
return error_msg
|
||||
error_msg = self.set_apache_proxy_include(proxy_data["sitename"])
|
||||
if webserver() == "apache" and error_msg:
|
||||
return error_msg
|
||||
error_msg = self.set_nginx_proxy(proxy_data)
|
||||
if webserver() == "nginx" and error_msg:
|
||||
return error_msg
|
||||
self.set_apache_proxy(proxy_data)
|
||||
self.config.append(proxy_data)
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def modify_proxy(self, get) -> Optional[str]:
|
||||
proxy_data = self.check_args(get, is_modify=True)
|
||||
if isinstance(proxy_data, str):
|
||||
return proxy_data
|
||||
idx = None
|
||||
|
||||
for index, i in enumerate(self.config):
|
||||
if i["proxyname"] == proxy_data["proxyname"] and i["sitename"] == proxy_data["sitename"]:
|
||||
idx = index
|
||||
break
|
||||
if idx is None:
|
||||
return "未找到该名称的反向代理配置"
|
||||
|
||||
if webserver() == "nginx" and proxy_data["proxydir"] != self.config[idx]["proxydir"]:
|
||||
error_msg = self.check_location(proxy_data["sitename"], proxy_data["proxydir"])
|
||||
if error_msg:
|
||||
return error_msg
|
||||
|
||||
error_msg = self.set_nginx_proxy_include(proxy_data["sitename"])
|
||||
if webserver() == "nginx" and error_msg:
|
||||
return error_msg
|
||||
error_msg = self.set_apache_proxy_include(proxy_data["sitename"])
|
||||
if webserver() == "apache" and error_msg:
|
||||
return error_msg
|
||||
error_msg = self.set_nginx_proxy(proxy_data)
|
||||
if webserver() == "nginx" and error_msg:
|
||||
return error_msg
|
||||
self.set_apache_proxy(proxy_data)
|
||||
self.config[idx] = proxy_data
|
||||
self.save_config()
|
||||
service_reload()
|
||||
|
||||
def remove_proxy(self, site_name, proxy_name, multiple=False) -> Optional[str]:
|
||||
idx = None
|
||||
site_other = False
|
||||
for index, i in enumerate(self.config):
|
||||
if i["proxyname"] == proxy_name and i["sitename"] == site_name:
|
||||
idx = index
|
||||
if i["sitename"] == site_name and i["proxyname"] != proxy_name:
|
||||
site_other = True
|
||||
|
||||
if idx is None:
|
||||
return "未找到该名称的反向代理配置"
|
||||
|
||||
proxy_name_md5 = self._calc_proxy_name_md5(proxy_name)
|
||||
ng_proxy_file = "%s/vhost/nginx/proxy/%s/%s_%s.conf" % (
|
||||
self.panel_path, site_name, proxy_name_md5, site_name)
|
||||
ap_proxy_file = "%s/vhost/apache/proxy/%s/%s_%s.conf" % (
|
||||
self.panel_path, site_name, proxy_name_md5, site_name)
|
||||
if os.path.isfile(ap_proxy_file):
|
||||
os.remove(ap_proxy_file)
|
||||
|
||||
if os.path.isfile(ng_proxy_file):
|
||||
os.remove(ng_proxy_file)
|
||||
del self.config[idx]
|
||||
self.save_config()
|
||||
if not site_other:
|
||||
self.un_set_apache_proxy_include(site_name)
|
||||
self.un_set_nginx_proxy_include(site_name)
|
||||
if not multiple:
|
||||
service_reload()
|
||||
|
||||
def get_proxy_list(self, get) -> Union[str, List[Dict[str, Any]]]:
|
||||
try:
|
||||
site_name = get.sitename.strip()
|
||||
except (AttributeError, ValueError, TypeError):
|
||||
return "Parameter error"
|
||||
proxy_list = []
|
||||
web_server = webserver()
|
||||
for conf in self.config:
|
||||
if conf["sitename"] != site_name:
|
||||
continue
|
||||
md5_name = self._calc_proxy_name_md5(conf['proxyname'])
|
||||
conf["proxy_conf_file"] = "%s/vhost/%s/proxy/%s/%s_%s.conf" % (
|
||||
self.panel_path, web_server, site_name, md5_name, site_name)
|
||||
proxy_list.append(conf)
|
||||
return proxy_list
|
||||
|
||||
def remove_site_proxy_info(self, site_name):
|
||||
idx_list = []
|
||||
for index, i in enumerate(self.config):
|
||||
if i["sitename"] == site_name:
|
||||
idx_list.append(index)
|
||||
|
||||
for idx in idx_list[::-1]:
|
||||
del self.config[idx]
|
||||
|
||||
self.save_config()
|
||||
|
||||
ng_proxy_dir = "%s/vhost/nginx/proxy/%s" % (self.panel_path, site_name)
|
||||
ap_proxy_dir = "%s/vhost/apache/proxy/%s" % (self.panel_path, site_name)
|
||||
|
||||
if os.path.isdir(ng_proxy_dir):
|
||||
shutil.rmtree(ng_proxy_dir)
|
||||
|
||||
if os.path.isdir(ap_proxy_dir):
|
||||
shutil.rmtree(ap_proxy_dir)
|
||||
|
||||
|
||||
class Proxy(object):
|
||||
|
||||
def __init__(self, config_prefix=""):
|
||||
self.config_prefix = config_prefix
|
||||
self._p = RealProxy(self.config_prefix)
|
||||
|
||||
def create_proxy(self, get):
|
||||
msg = self._p.create_proxy(get)
|
||||
if msg:
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="Successfully added")
|
||||
|
||||
def modify_proxy(self, get):
|
||||
msg = self._p.modify_proxy(get)
|
||||
if msg:
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="修改成功")
|
||||
|
||||
def remove_proxy(self, get):
|
||||
try:
|
||||
site_name = get.sitename.strip()
|
||||
proxy_name = get.proxyname.strip()
|
||||
except:
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
msg = self._p.remove_proxy(site_name, proxy_name)
|
||||
if msg:
|
||||
return json_response(status=False, msg=msg)
|
||||
return json_response(status=True, msg="Successfully delete")
|
||||
|
||||
def get_proxy_list(self, get):
|
||||
data = self._p.get_proxy_list(get)
|
||||
if isinstance(data, str):
|
||||
return json_response(status=False, msg=data)
|
||||
else:
|
||||
return json_response(status=True, data=data)
|
||||
737
mod/base/web_conf/redirect.py
Normal file
737
mod/base/web_conf/redirect.py
Normal file
@@ -0,0 +1,737 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
import shutil
|
||||
import time
|
||||
from typing import Tuple, Optional, Union, Dict, List, Any
|
||||
from urllib import parse
|
||||
from itertools import product
|
||||
from .util import webserver, check_server_config, write_file, read_file, DB, service_reload
|
||||
from mod.base import json_response
|
||||
|
||||
|
||||
class RealRedirect:
|
||||
panel_path = "/www/server/panel"
|
||||
_redirect_conf_file = "{}/data/redirect.conf".format(panel_path)
|
||||
|
||||
_ng_redirect_domain_format = """
|
||||
if ($host ~ '^%s'){
|
||||
return %s %s%s;
|
||||
}
|
||||
"""
|
||||
_ng_redirect_path_format = """
|
||||
rewrite ^%s(.*) %s%s %s;
|
||||
"""
|
||||
_ap_redirect_domain_format = """
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %%{HTTP_HOST} ^%s [NC]
|
||||
RewriteRule ^(.*) %s%s [L,R=%s]
|
||||
</IfModule>
|
||||
"""
|
||||
_ap_redirect_path_format = """
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteRule ^%s(.*) %s%s [L,R=%s]
|
||||
</IfModule>
|
||||
"""
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
self._config: Optional[List[Dict[str, Union[str, int]]]] = None
|
||||
self.config_prefix = config_prefix
|
||||
self._webserver = None
|
||||
|
||||
@property
|
||||
def webserver(self) -> str:
|
||||
if self._webserver is not None:
|
||||
return self._webserver
|
||||
self._webserver = webserver()
|
||||
return self._webserver
|
||||
|
||||
@property
|
||||
def config(self) -> List[Dict[str, Union[str, int, List]]]:
|
||||
if self._config is not None:
|
||||
return self._config
|
||||
try:
|
||||
self._config = json.loads(read_file(self._redirect_conf_file))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
self._config = []
|
||||
if not isinstance(self._config, list):
|
||||
self._config = []
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
if self._config is not None:
|
||||
return write_file(self._redirect_conf_file, json.dumps(self._config))
|
||||
|
||||
def _check_redirect_domain_exist(self, site_name,
|
||||
redirect_domain: list,
|
||||
redirect_name: str = None,
|
||||
is_modify=False) -> Optional[List[str]]:
|
||||
res = set()
|
||||
redirect_domain_set = set(redirect_domain)
|
||||
for c in self.config:
|
||||
if c["sitename"] != site_name:
|
||||
continue
|
||||
if is_modify:
|
||||
if c["redirectname"] != redirect_name:
|
||||
res |= set(c["redirectdomain"]) & redirect_domain_set
|
||||
else:
|
||||
res |= set(c["redirectdomain"]) & redirect_domain_set
|
||||
return list(res) if res else None
|
||||
|
||||
def _check_redirect_path_exist(self, site_name,
|
||||
redirect_path: str,
|
||||
redirect_name: str = None) -> bool:
|
||||
for c in self.config:
|
||||
if c["sitename"] == site_name:
|
||||
if c["redirectname"] != redirect_name and c["redirectpath"] == redirect_path:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _parse_url_domain(url: str):
|
||||
return parse.urlparse(url).netloc
|
||||
|
||||
@staticmethod
|
||||
def _parse_url_path(url: str):
|
||||
return parse.urlparse(url).path
|
||||
|
||||
# 计算name md5
|
||||
@staticmethod
|
||||
def _calc_redirect_name_md5(redirect_name) -> str:
|
||||
md5 = hashlib.md5()
|
||||
md5.update(redirect_name.encode('utf-8'))
|
||||
return md5.hexdigest()
|
||||
|
||||
def _check_redirect(self, site_name, redirect_name, is_error=False):
|
||||
for i in self.config:
|
||||
if i["sitename"] != site_name:
|
||||
continue
|
||||
if is_error and "errorpage" in i and i["errorpage"] in [1, '1']:
|
||||
return i
|
||||
if i["redirectname"] == redirect_name:
|
||||
return i
|
||||
return None
|
||||
|
||||
# 创建修改配置检测
|
||||
def _check_redirect_args(self, get, is_modify=False) -> Union[str, Dict]:
|
||||
if check_server_config() is not None:
|
||||
return '配置文件出错请先排查配置'
|
||||
|
||||
try:
|
||||
site_name = get.sitename.strip()
|
||||
redirect_path = get.redirectpath.strip()
|
||||
redirect_type = get.redirecttype.strip()
|
||||
domain_or_path = get.domainorpath.strip()
|
||||
hold_path = int(get.holdpath)
|
||||
|
||||
to_url = ""
|
||||
to_path = ""
|
||||
error_page = 0
|
||||
redirect_domain = []
|
||||
redirect_name = ""
|
||||
status_type = 1
|
||||
|
||||
if "redirectname" in get and get.redirectname.strip():
|
||||
redirect_name = get.redirectname.strip()
|
||||
if "tourl" in get:
|
||||
to_url = get.tourl.strip()
|
||||
if "topath" in get:
|
||||
to_path = get.topath.strip()
|
||||
if "redirectdomain" in get:
|
||||
redirect_domain = json.loads(get.redirectdomain.strip())
|
||||
if "type" in get:
|
||||
status_type = int(get.type)
|
||||
if "errorpage" in get:
|
||||
error_page = int(get.errorpage)
|
||||
except (AttributeError, ValueError):
|
||||
return '参数错误'
|
||||
|
||||
if not is_modify:
|
||||
if not redirect_name:
|
||||
return "Parameter error, configuration name cannot be empty"
|
||||
# 检测名称是否重复
|
||||
if not (3 <= len(redirect_name) < 15):
|
||||
return '名称必须大于2小于15个字符串'
|
||||
|
||||
if self._check_redirect(site_name, redirect_name, error_page == 1):
|
||||
return '指定重定向名称已存在'
|
||||
|
||||
site_info = DB('sites').where("name=?", (site_name,)).find()
|
||||
if not isinstance(site_info, dict):
|
||||
return "站点信息查询错误"
|
||||
else:
|
||||
site_name = site_info["name"]
|
||||
|
||||
# 检测目标URL格式
|
||||
rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?"
|
||||
if to_url and not re.match(rep, to_url):
|
||||
return '目标URL格式不对【%s】' % to_url
|
||||
|
||||
# 非404页面de重定向检测项
|
||||
if error_page != 1:
|
||||
# 检测是否选择域名
|
||||
if domain_or_path == "domain":
|
||||
if not redirect_domain:
|
||||
return '请选择重定向域名'
|
||||
# 检测域名是否已经存在配置文件
|
||||
repeat_domain = self._check_redirect_domain_exist(site_name, redirect_domain, redirect_name, is_modify)
|
||||
if repeat_domain:
|
||||
return '重定向域名重复 %s' % repeat_domain
|
||||
|
||||
# 检查目标URL的域名和被重定向的域名是否一样
|
||||
tu = self._parse_url_domain(to_url)
|
||||
for d in redirect_domain:
|
||||
if d == tu:
|
||||
return '域名 "%s" 和目标域名一致请取消选择' % d
|
||||
else:
|
||||
if not redirect_path:
|
||||
return '请输入重定向路径'
|
||||
if redirect_path[0] != "/":
|
||||
return "路径格式不正确,格式为/xxx"
|
||||
# 检测路径是否有存在配置文件
|
||||
if self._check_redirect_path_exist(site_name, redirect_path, redirect_name):
|
||||
return '重定向路径重复 %s' % redirect_path
|
||||
|
||||
to_url_path = self._parse_url_path(to_url)
|
||||
if to_url_path.startswith(redirect_path):
|
||||
return '目标URL[%s]以被重定向的路径[%s]开头,会导致循环匹配' % (to_url_path, redirect_path)
|
||||
# 404页面重定向检测项
|
||||
else:
|
||||
if not to_url and not to_path:
|
||||
return '首页或自定义页面必须二选一'
|
||||
if to_path:
|
||||
to_path = "/"
|
||||
|
||||
return {
|
||||
"tourl": to_url,
|
||||
"topath": to_path,
|
||||
"errorpage": error_page,
|
||||
"redirectdomain": redirect_domain,
|
||||
"redirectname": redirect_name if redirect_name else str(int(time.time())),
|
||||
"type": status_type,
|
||||
"sitename": site_name,
|
||||
"redirectpath": redirect_path,
|
||||
"redirecttype": redirect_type,
|
||||
"domainorpath": domain_or_path,
|
||||
"holdpath": hold_path,
|
||||
}
|
||||
|
||||
def create_redirect(self, get) -> Tuple[bool, str]:
|
||||
res_conf = self._check_redirect_args(get, is_modify=False)
|
||||
if isinstance(res_conf, str):
|
||||
return False, res_conf
|
||||
|
||||
res = self._set_include(res_conf)
|
||||
if res is not None:
|
||||
return False, res
|
||||
res = self._write_config(res_conf)
|
||||
if res is not None:
|
||||
return False, res
|
||||
self.config.append(res_conf)
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return True, '创建成功'
|
||||
|
||||
def _set_include(self, res_conf) -> Optional[str]:
|
||||
flag, msg = self._set_nginx_redirect_include(res_conf)
|
||||
if not flag and webserver() == "nginx":
|
||||
return msg
|
||||
flag, msg = self._set_apache_redirect_include(res_conf)
|
||||
if not flag and webserver() == "apache":
|
||||
return msg
|
||||
|
||||
def _write_config(self, res_conf) -> Optional[str]:
|
||||
if res_conf["errorpage"] != 1:
|
||||
res = self.write_nginx_redirect_file(res_conf)
|
||||
if res is not None:
|
||||
return res
|
||||
res = self.write_apache_redirect_file(res_conf)
|
||||
if res is not None:
|
||||
return res
|
||||
else:
|
||||
self.unset_nginx_404_conf(res_conf["sitename"])
|
||||
res = self.write_nginx_404_redirect_file(res_conf)
|
||||
if res is not None:
|
||||
return res
|
||||
res = self.write_apache_404_redirect_file(res_conf)
|
||||
if res is not None:
|
||||
return res
|
||||
|
||||
def modify_redirect(self, get) -> Tuple[bool, str]:
|
||||
"""
|
||||
@name 修改、启用、禁用重定向
|
||||
@author hezhihong
|
||||
@param get.sitename 站点名称
|
||||
@param get.redirectname 重定向名称
|
||||
@param get.tourl 目标URL
|
||||
@param get.redirectdomain 重定向域名
|
||||
@param get.redirectpath 重定向路径
|
||||
@param get.redirecttype 重定向类型
|
||||
@param get.type 重定向状态 0禁用 1启用
|
||||
@param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向
|
||||
@param get.holdpath 保留路径 0不保留 1保留
|
||||
@return json
|
||||
"""
|
||||
# 基本信息检查
|
||||
res_conf = self._check_redirect_args(get, is_modify=True)
|
||||
if isinstance(res_conf, str):
|
||||
return False, res_conf
|
||||
|
||||
old_idx = None
|
||||
for i, conf in enumerate(self.config):
|
||||
if conf["redirectname"] == res_conf["redirectname"] and conf["sitename"] == res_conf["sitename"]:
|
||||
old_idx = i
|
||||
|
||||
res = self._set_include(res_conf)
|
||||
if res is not None:
|
||||
return False, res
|
||||
res = self._write_config(res_conf)
|
||||
if res is not None:
|
||||
return False, res
|
||||
|
||||
if old_idx is not None:
|
||||
self.config[old_idx].update(res_conf)
|
||||
else:
|
||||
self.config.append(res_conf)
|
||||
self.save_config()
|
||||
service_reload()
|
||||
return True, '修改成功'
|
||||
|
||||
def _set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]:
|
||||
ng_redirect_dir = "%s/vhost/nginx/redirect/%s" % (self.panel_path, redirect_conf["sitename"])
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"])
|
||||
if not os.path.exists(ng_redirect_dir):
|
||||
os.makedirs(ng_redirect_dir, 0o600)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not isinstance(ng_conf, str):
|
||||
return False, "nginx配置文件读取失败"
|
||||
|
||||
rep_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M)
|
||||
if rep_include.search(ng_conf):
|
||||
return True, ""
|
||||
redirect_include = (
|
||||
"#SSL-END\n"
|
||||
" #引用重定向规则,注释后配置的重定向代理将无效\n"
|
||||
" include {}/*.conf;"
|
||||
).format(ng_redirect_dir)
|
||||
|
||||
if "#SSL-END" not in ng_conf:
|
||||
return False, "添加配置失败,无法定位SSL相关配置的位置"
|
||||
|
||||
new_conf = ng_conf.replace("#SSL-END", redirect_include)
|
||||
write_file(ng_file, new_conf)
|
||||
if self.webserver == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return False, "添加配置失败"
|
||||
|
||||
return True, ""
|
||||
|
||||
def _un_set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]:
|
||||
ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"])
|
||||
ng_conf = read_file(ng_file)
|
||||
if not isinstance(ng_conf, str):
|
||||
return False, "nginx配置文件读取失败"
|
||||
|
||||
rep_include = re.compile(r"(#(.*)\n)?\s*include +.*/redirect/.*\*\.conf;")
|
||||
if not rep_include.search(ng_conf):
|
||||
return True, ""
|
||||
|
||||
new_conf = rep_include.sub("", ng_conf, 1)
|
||||
write_file(ng_file, new_conf)
|
||||
if self.webserver == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return False, "移除配置失败"
|
||||
|
||||
return True, ""
|
||||
|
||||
def _set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]:
|
||||
ap_redirect_dir = "%s/vhost/apache/redirect/%s" % (self.panel_path, redirect_conf["sitename"])
|
||||
ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"])
|
||||
if not os.path.exists(ap_redirect_dir):
|
||||
os.makedirs(ap_redirect_dir, 0o600)
|
||||
|
||||
ap_conf = read_file(ap_file)
|
||||
if not isinstance(ap_conf, str):
|
||||
return False, "apache配置文件读取失败"
|
||||
|
||||
rep_include = re.compile(r"\sIncludeOptional +.*/redirect/.*\*\.conf", re.M)
|
||||
include_count = len(list(rep_include.finditer(ap_conf)))
|
||||
if ap_conf.count("</VirtualHost>") == include_count:
|
||||
return True, ""
|
||||
|
||||
if include_count > 0:
|
||||
# 先清除已有的配置
|
||||
self._un_set_apache_redirect_include(redirect_conf)
|
||||
|
||||
rep_custom_log = re.compile(r"CustomLog .*\n")
|
||||
rep_deny_files = re.compile(r"\n\s*#DENY FILES")
|
||||
|
||||
include_conf = (
|
||||
"\n # 引用重定向规则,注释后配置的重定向代理将无效\n"
|
||||
" IncludeOptional {}/*.conf\n"
|
||||
).format(ap_redirect_dir)
|
||||
|
||||
new_conf = None
|
||||
|
||||
def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool:
|
||||
new_conf_list = []
|
||||
last_idx = 0
|
||||
for tmp in rep.finditer(ap_conf):
|
||||
new_conf_list.append(ap_conf[last_idx:tmp.start()])
|
||||
if use_start:
|
||||
new_conf_list.append(include_conf)
|
||||
new_conf_list.append(tmp.group())
|
||||
else:
|
||||
new_conf_list.append(tmp.group())
|
||||
new_conf_list.append(include_conf)
|
||||
last_idx = tmp.end()
|
||||
|
||||
new_conf_list.append(ap_conf[last_idx:])
|
||||
|
||||
nonlocal new_conf
|
||||
new_conf = "".join(new_conf_list)
|
||||
write_file(ap_file, new_conf)
|
||||
if self.webserver == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return False
|
||||
return True
|
||||
|
||||
if set_by_rep_idx(rep_custom_log, False) and rep_include.search(new_conf):
|
||||
return True, ""
|
||||
|
||||
if set_by_rep_idx(rep_deny_files, True) and rep_include.search(new_conf):
|
||||
return True, ""
|
||||
return False, "设置失败"
|
||||
|
||||
def _un_set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]:
|
||||
ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"])
|
||||
ap_conf = read_file(ap_file)
|
||||
if not isinstance(ap_conf, str):
|
||||
return False, "apache配置文件读取失败"
|
||||
|
||||
rep_include = re.compile(r"(#(.*)\n)?\s*IncludeOptional +.*/redirect/.*\*\.conf")
|
||||
if not rep_include.search(ap_conf):
|
||||
return True, ""
|
||||
|
||||
new_conf = rep_include.sub("", ap_conf)
|
||||
write_file(ap_file, new_conf)
|
||||
if self.webserver == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return False, "移除配置失败"
|
||||
|
||||
return True, ""
|
||||
|
||||
def write_nginx_redirect_file(self, redirect_conf: dict) -> Optional[str]:
|
||||
conf_file = "{}/vhost/nginx/redirect/{}/{}_{}.conf".format(
|
||||
self.panel_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]),
|
||||
redirect_conf["sitename"]
|
||||
)
|
||||
if redirect_conf["type"] == 1:
|
||||
to_url = redirect_conf["tourl"]
|
||||
conf_list = ["#REWRITE-START"]
|
||||
if redirect_conf["domainorpath"] == "domain":
|
||||
hold_path = "$request_uri" if redirect_conf["holdpath"] == 1 else ""
|
||||
for sd in redirect_conf["redirectdomain"]:
|
||||
if sd.startswith("*."):
|
||||
sd = r"[\w.]+\." + sd[2:]
|
||||
|
||||
conf_list.append(self._ng_redirect_domain_format % (
|
||||
sd, redirect_conf["redirecttype"], to_url, hold_path
|
||||
))
|
||||
else:
|
||||
redirect_path = redirect_conf["redirectpath"]
|
||||
if redirect_conf["redirecttype"] == "301":
|
||||
redirect_type = "permanent"
|
||||
else:
|
||||
redirect_type = "redirect"
|
||||
hold_path = "$1" if redirect_conf["holdpath"] == 1 else ""
|
||||
conf_list.append(self._ng_redirect_path_format % (redirect_path, to_url, hold_path, redirect_type))
|
||||
|
||||
conf_list.append("#REWRITE-END")
|
||||
|
||||
conf_data = "\n".join(conf_list)
|
||||
write_file(conf_file, conf_data)
|
||||
|
||||
if self.webserver == "nginx":
|
||||
error_msg = check_server_config()
|
||||
if error_msg is not None:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return 'ERROR: 配置出错<br><a style="color:red;">' + error_msg.replace("\n", '<br>') + '</a>'
|
||||
else:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
|
||||
def write_apache_redirect_file(self, redirect_conf: dict) -> Optional[str]:
|
||||
conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format(
|
||||
self.panel_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]),
|
||||
redirect_conf["sitename"]
|
||||
)
|
||||
if redirect_conf["type"] != 1:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return
|
||||
|
||||
to_url = redirect_conf["tourl"]
|
||||
conf_list = ["#REWRITE-START"]
|
||||
hold_path = "$1" if redirect_conf["holdpath"] == 1 else ""
|
||||
if redirect_conf["domainorpath"] == "domain":
|
||||
for sd in redirect_conf["redirectdomain"]:
|
||||
if sd.startswith("*."):
|
||||
sd = r"[\w.]+\." + sd[2:]
|
||||
|
||||
conf_list.append(self._ap_redirect_domain_format % (
|
||||
sd, to_url, hold_path, redirect_conf["redirecttype"]
|
||||
))
|
||||
else:
|
||||
redirect_path = redirect_conf["redirectpath"]
|
||||
conf_list.append(self._ap_redirect_path_format % (redirect_path, to_url, hold_path, redirect_conf["redirecttype"]))
|
||||
|
||||
conf_list.append("#REWRITE-END")
|
||||
|
||||
write_file(conf_file, "\n".join(conf_list))
|
||||
if self.webserver == "apache":
|
||||
error_msg = check_server_config()
|
||||
if error_msg is not None:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return 'ERROR: 配置出错<br><a style="color:red;">' + error_msg.replace("\n", '<br>') + '</a>'
|
||||
|
||||
def unset_nginx_404_conf(self, site_name):
|
||||
"""
|
||||
清理已有的 404 页面 配置
|
||||
"""
|
||||
need_clear_files = [
|
||||
"{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name),
|
||||
"{}/vhost/nginx/rewrite/{}{}.conf".format(self.panel_path, self.config_prefix, site_name),
|
||||
]
|
||||
rep_error_page = re.compile(r'(?P<prefix>.*)error_page +404 +/404\.html[^\n]*\n', re.M)
|
||||
rep_location_404 = re.compile(r'(?P<prefix>.*)location += +/404\.html[^}]*}')
|
||||
clear_files = [
|
||||
{
|
||||
"data": read_file(i),
|
||||
"path": i,
|
||||
} for i in need_clear_files
|
||||
]
|
||||
for file_info, rep in product(clear_files, (rep_error_page, rep_location_404)):
|
||||
if not isinstance(file_info["data"], str):
|
||||
continue
|
||||
tmp_res = rep.search(file_info["data"])
|
||||
if not tmp_res or tmp_res.group("prefix").find("#") != -1:
|
||||
continue
|
||||
file_info["data"] = rep.sub("", file_info["data"])
|
||||
|
||||
for i in clear_files:
|
||||
if not isinstance(i["data"], str):
|
||||
continue
|
||||
write_file(i["path"], i["data"])
|
||||
|
||||
def write_nginx_404_redirect_file(self, redirect_conf: dict) -> Optional[str]:
|
||||
"""
|
||||
设置nginx 404重定向
|
||||
"""
|
||||
r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"])
|
||||
file_path = "{}/vhost/nginx/redirect/{}".format(self.panel_path, redirect_conf["sitename"])
|
||||
file_name = '%s_%s.conf' % (r_name_md5, redirect_conf["sitename"])
|
||||
conf_file = os.path.join(file_path, file_name)
|
||||
if redirect_conf["type"] != 1:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return
|
||||
|
||||
_path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"]
|
||||
conf_data = (
|
||||
'#REWRITE-START\n'
|
||||
'error_page 404 = @notfound;\n'
|
||||
'location @notfound {{\n'
|
||||
' return {} {};\n'
|
||||
'}}\n#REWRITE-END'
|
||||
).format(redirect_conf["redirecttype"], _path)
|
||||
|
||||
write_file(conf_file, conf_data)
|
||||
if self.webserver == "nginx":
|
||||
error_msg = check_server_config()
|
||||
if error_msg is not None:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return 'ERROR: 配置出错<br><a style="color:red;">' + error_msg.replace("\n", '<br>') + '</a>'
|
||||
|
||||
def write_apache_404_redirect_file(self, redirect_conf: dict) -> Optional[str]:
|
||||
"""
|
||||
设置apache 404重定向
|
||||
"""
|
||||
r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"])
|
||||
conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format(
|
||||
self.panel_path, redirect_conf["sitename"], r_name_md5, redirect_conf["sitename"]
|
||||
)
|
||||
if redirect_conf["type"] != 1:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return
|
||||
|
||||
_path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"]
|
||||
conf_data = """
|
||||
#REWRITE-START
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %{{REQUEST_FILENAME}} !-f
|
||||
RewriteCond %{{REQUEST_FILENAME}} !-d
|
||||
RewriteRule . {} [L,R={}]
|
||||
</IfModule>
|
||||
#REWRITE-END
|
||||
""".format(_path, redirect_conf["redirecttype"])
|
||||
|
||||
write_file(conf_file, conf_data)
|
||||
if self.webserver == "apache":
|
||||
error_msg = check_server_config()
|
||||
if error_msg is not None:
|
||||
if os.path.exists(conf_file):
|
||||
os.remove(conf_file)
|
||||
return 'ERROR: 配置出错<br><a style="color:red;">' + error_msg.replace("\n", '<br>') + '</a>'
|
||||
|
||||
def remove_redirect(self, get, multiple=None) -> Tuple[bool, str]:
|
||||
try:
|
||||
site_name = get.sitename.strip()
|
||||
redirect_name = get.redirectname.strip()
|
||||
except AttributeError:
|
||||
return False, "Parameter error"
|
||||
target_idx = None
|
||||
have_other_redirect = False
|
||||
target_conf = None
|
||||
for i, conf in enumerate(self.config):
|
||||
if conf["redirectname"] != redirect_name and conf["sitename"] == site_name:
|
||||
have_other_redirect = True
|
||||
if conf["redirectname"] == redirect_name and conf["sitename"] == site_name:
|
||||
target_idx = i
|
||||
target_conf = conf
|
||||
|
||||
if target_idx is None:
|
||||
return False, '没有指定的配置'
|
||||
|
||||
r_md5_name = self._calc_redirect_name_md5(target_conf["redirectname"])
|
||||
ng_conf_file = "%s/vhost/nginx/redirect/%s/%s_%s.conf" % (
|
||||
self.panel_path, site_name, r_md5_name, site_name)
|
||||
if os.path.exists(ng_conf_file):
|
||||
os.remove(ng_conf_file)
|
||||
|
||||
ap_conf_file = "%s/vhost/nginx/apache/%s/%s_%s.conf" % (
|
||||
self.panel_path, site_name, r_md5_name, site_name)
|
||||
if os.path.exists(ap_conf_file):
|
||||
os.remove(ap_conf_file)
|
||||
|
||||
if not have_other_redirect:
|
||||
self._un_set_apache_redirect_include(target_conf)
|
||||
self._un_set_nginx_redirect_include(target_conf)
|
||||
|
||||
del self.config[target_idx]
|
||||
self.save_config()
|
||||
if not multiple:
|
||||
service_reload()
|
||||
|
||||
return True, '删除成功'
|
||||
|
||||
def mutil_remove_redirect(self, get):
|
||||
try:
|
||||
redirect_names = json.loads(get.redirectnames.strip())
|
||||
site_name = get.sitename.strip()
|
||||
except (AttributeError, json.JSONDecodeError, TypeError):
|
||||
return False, "Parameter error"
|
||||
del_successfully = []
|
||||
del_failed = []
|
||||
get_obj = type(get)()
|
||||
for redirect_name in redirect_names:
|
||||
get_obj.redirectname = redirect_name
|
||||
get_obj.sitename = site_name
|
||||
try:
|
||||
flag, msg = self.remove_redirect(get, multiple=1)
|
||||
if flag:
|
||||
del_failed[redirect_name] = msg
|
||||
continue
|
||||
del_successfully.append(redirect_name)
|
||||
except:
|
||||
del_failed.append(redirect_name)
|
||||
|
||||
service_reload()
|
||||
if not del_failed:
|
||||
return True, '删除重定向【{}】成功'.format(','.join(del_successfully))
|
||||
else:
|
||||
return True, '重定向【{}】删除成功,【{}】删除失败'.format(
|
||||
','.join(del_successfully), ','.join(del_failed)
|
||||
)
|
||||
|
||||
def get_redirect_list(self, get) -> Tuple[bool, Union[str, List[Dict[str, Any]]]]:
|
||||
try:
|
||||
error_page = None
|
||||
site_name = get.sitename.strip()
|
||||
if "errorpage" in get:
|
||||
error_page = int(get.errorpage)
|
||||
except (AttributeError, ValueError, TypeError):
|
||||
return False, "Parameter error"
|
||||
redirect_list = []
|
||||
web_server = self.webserver
|
||||
if self.webserver == 'openlitespeed':
|
||||
web_server = 'apache'
|
||||
for conf in self.config:
|
||||
if conf["sitename"] != site_name:
|
||||
continue
|
||||
if error_page is not None and error_page != int(conf['errorpage']):
|
||||
continue
|
||||
if 'errorpage' in conf and conf['errorpage'] in [1, '1']:
|
||||
conf['redirectdomain'] = ['404页面']
|
||||
|
||||
md5_name = self._calc_redirect_name_md5(conf['redirectname'])
|
||||
conf["redirect_conf_file"] = "%s/vhost/%s/redirect/%s/%s_%s.conf" % (
|
||||
self.panel_path, web_server, site_name, md5_name, site_name)
|
||||
conf["type"] = 1 if os.path.isfile(conf["redirect_conf_file"]) else 0
|
||||
redirect_list.append(conf)
|
||||
return True, redirect_list
|
||||
|
||||
def remove_site_redirect_info(self, site_name):
|
||||
for i in range(len(self.config) - 1, -1, -1):
|
||||
if self.config[i]["sitename"] == site_name:
|
||||
del self.config[i]
|
||||
self.save_config()
|
||||
|
||||
m_path = self.panel_path + '/vhost/nginx/redirect/' + site_name
|
||||
if os.path.exists(m_path):
|
||||
shutil.rmtree(m_path)
|
||||
|
||||
m_path = self.panel_path + '/vhost/apache/redirect/' + site_name
|
||||
if os.path.exists(m_path):
|
||||
shutil.rmtree(m_path)
|
||||
|
||||
|
||||
class Redirect(RealRedirect):
|
||||
|
||||
def __init__(self, config_prefix: str = ""):
|
||||
super().__init__(config_prefix)
|
||||
self.config_prefix = config_prefix
|
||||
|
||||
def remove_redirect_by_project_name(self, project_name):
|
||||
return self.remove_site_redirect_info(project_name)
|
||||
|
||||
def create_project_redirect(self, get):
|
||||
flag, msg = self.create_redirect(get)
|
||||
return json_response(status=flag, msg=msg)
|
||||
|
||||
def modify_project_redirect(self, get):
|
||||
flag, msg = self.modify_redirect(get)
|
||||
return json_response(status=flag, msg=msg)
|
||||
|
||||
def remove_project_redirect(self, get):
|
||||
flag, msg = self.remove_redirect(get)
|
||||
return json_response(status=flag, msg=msg)
|
||||
|
||||
def mutil_remove_project_redirect(self, get):
|
||||
flag, msg = self.mutil_remove_redirect(get)
|
||||
return json_response(status=flag, msg=msg)
|
||||
|
||||
def get_project_redirect_list(self, get):
|
||||
flag, data = self.get_redirect_list(get)
|
||||
if not flag:
|
||||
return json_response(status=flag, msg=data)
|
||||
else:
|
||||
return json_response(status=flag, data=data)
|
||||
363
mod/base/web_conf/referer.py
Normal file
363
mod/base/web_conf/referer.py
Normal file
@@ -0,0 +1,363 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Tuple, Optional, Union, Dict
|
||||
from .util import webserver, check_server_config, DB, \
|
||||
write_file, read_file, GET_CLASS, service_reload, pre_re_key
|
||||
from mod.base import json_response
|
||||
|
||||
|
||||
@dataclass
|
||||
class _RefererConf:
|
||||
name: str
|
||||
fix: str
|
||||
domains: str
|
||||
status: str
|
||||
return_rule: str
|
||||
http_status: str
|
||||
|
||||
def __str__(self):
|
||||
return '{"name"="%s","fix"="%s","domains"="%s","status"="%s","http_status"="%s","return_rule"="%s"}' % (
|
||||
self.name, self.fix, self.domains, self.status, self.http_status, self.return_rule
|
||||
)
|
||||
|
||||
|
||||
class RealReferer:
|
||||
_referer_conf_dir = '/www/server/panel/vhost/config' # 防盗链配置
|
||||
_ng_referer_conf_format = r''' #SECURITY-START 防盗链配置
|
||||
location ~ .*\.(%s)$ {
|
||||
expires 30d;
|
||||
access_log /dev/null;
|
||||
valid_referers %s;
|
||||
if ($invalid_referer){
|
||||
%s;
|
||||
}
|
||||
}
|
||||
#SECURITY-END'''
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
if not os.path.isdir(self._referer_conf_dir):
|
||||
os.makedirs(self._referer_conf_dir)
|
||||
self.config_prefix: str = config_prefix
|
||||
self._webserver = None
|
||||
|
||||
@property
|
||||
def webserver(self) -> str:
|
||||
if self._webserver is not None:
|
||||
return self._webserver
|
||||
self._webserver = webserver()
|
||||
return self._webserver
|
||||
|
||||
def get_config(self, site_name: str) -> Optional[_RefererConf]:
|
||||
try:
|
||||
config = json.loads(read_file("{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name)))
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
config = None
|
||||
if isinstance(config, dict):
|
||||
return _RefererConf(**config)
|
||||
return None
|
||||
|
||||
def save_config(self, site_name: str, data: Union[dict, str, _RefererConf]) -> bool:
|
||||
if isinstance(data, dict):
|
||||
c = json.dumps(data)
|
||||
elif isinstance(data, _RefererConf):
|
||||
c = json.dumps(str(data))
|
||||
else:
|
||||
c = data
|
||||
|
||||
file_path = "{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name)
|
||||
return write_file(file_path, c)
|
||||
|
||||
# 检测参数,如果正确则返回 配置数据类型的值,否则返回错误信息
|
||||
@staticmethod
|
||||
def check_args(get: Union[Dict, GET_CLASS]) -> Union[_RefererConf, str]:
|
||||
res = {}
|
||||
if isinstance(get, GET_CLASS):
|
||||
try:
|
||||
res["status"] = "true" if not hasattr(get, "status") else get.status.strip()
|
||||
res["http_status"] = "false" if not hasattr(get, "http_status") else get.http_status.strip()
|
||||
res["name"] = get.name.strip()
|
||||
res["fix"] = get.fix.strip()
|
||||
res["domains"] = get.domains.strip()
|
||||
res["return_rule"] = get.return_rule.strip()
|
||||
except AttributeError:
|
||||
return "Parameter error"
|
||||
else:
|
||||
try:
|
||||
res["status"] = "true" if "status" not in get else get["status"].strip()
|
||||
res["http_status"] = "false" if "http_status" not in get else get["http_status"].strip()
|
||||
res["name"] = get["name"].strip()
|
||||
res["fix"] = get["fix"].strip()
|
||||
res["domains"] = get["domains"].strip()
|
||||
res["return_rule"] = get["return_rule"].strip()
|
||||
except KeyError:
|
||||
return "Parameter error"
|
||||
|
||||
rconf = _RefererConf(**res)
|
||||
if rconf.status not in ("true", "false") and rconf.return_rule not in ("true", "false"):
|
||||
return "状态参数只能使用【true,false】"
|
||||
if rconf.return_rule not in ('404', '403', '200', '301', '302', '401') and rconf.return_rule[0] != "/":
|
||||
return "响应资源应使用URI路径或HTTP状态码,如:/test.png 或 404"
|
||||
if len(rconf.domains) < 3:
|
||||
return "防盗链域名不能为空"
|
||||
if len(rconf.fix) < 2:
|
||||
return 'URL后缀不能为空!'
|
||||
return rconf
|
||||
|
||||
def set_referer_security(self, rc: _RefererConf) -> Tuple[bool, str]:
|
||||
error_msg = self._set_nginx_referer_security(rc)
|
||||
if error_msg and self.webserver == "nginx":
|
||||
return False, error_msg
|
||||
error_msg = self._set_apache_referer_security(rc)
|
||||
if error_msg and self.webserver == "apache":
|
||||
return False, error_msg
|
||||
service_reload()
|
||||
self.save_config(rc.name, rc)
|
||||
return True, "设置成功"
|
||||
|
||||
def _set_nginx_referer_security(self, rc: _RefererConf) -> Optional[str]:
|
||||
ng_file = '/www/server/panel/vhost/nginx/{}{}.conf'.format(self.config_prefix, rc.name)
|
||||
ng_conf = read_file(ng_file)
|
||||
if not isinstance(ng_conf, str):
|
||||
return "nginx配置文件丢失,无法设置"
|
||||
start_idx, end_idx = self._get_nginx_referer_security_idx(ng_conf)
|
||||
if rc.status == "true":
|
||||
if rc.return_rule[0] == "/":
|
||||
return_rule = "rewrite /.* {} break".format(rc.return_rule)
|
||||
else:
|
||||
return_rule = 'return {}'.format(rc.return_rule)
|
||||
|
||||
valid_args_list = []
|
||||
if rc.http_status == "true":
|
||||
valid_args_list.extend(("none", "blocked"))
|
||||
valid_args_list.extend(map(lambda x: x.strip(), rc.domains.split(",")))
|
||||
valid_args = " ".join(valid_args_list)
|
||||
|
||||
location_args = "|".join(map(lambda x: pre_re_key(x.strip()), rc.fix.split(",")))
|
||||
if start_idx is not None:
|
||||
new_conf = ng_conf[:start_idx] + "\n" + (
|
||||
self._ng_referer_conf_format % (location_args, valid_args, return_rule)
|
||||
) + "\n" + ng_conf[end_idx:]
|
||||
else:
|
||||
rep_redirect_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M)
|
||||
redirect_include_res = rep_redirect_include.search(ng_conf)
|
||||
if redirect_include_res:
|
||||
new_conf = ng_conf[:redirect_include_res.end()] + "\n" + (
|
||||
self._ng_referer_conf_format % (location_args, valid_args, return_rule)
|
||||
) + ng_conf[redirect_include_res.end():]
|
||||
else:
|
||||
if "#SSL-END" not in ng_conf:
|
||||
return "添加配置失败,无法定位SSL相关配置的位置"
|
||||
|
||||
new_conf = ng_conf.replace("#SSL-END", "#SSL-END\n" + self._ng_referer_conf_format % (
|
||||
location_args, valid_args, return_rule))
|
||||
|
||||
else:
|
||||
if start_idx is None:
|
||||
return
|
||||
new_conf = ng_conf[:start_idx] + "\n" + ng_conf[end_idx:]
|
||||
|
||||
write_file(ng_file, new_conf)
|
||||
if self.webserver == "nginx" and check_server_config() is not None:
|
||||
write_file(ng_file, ng_conf)
|
||||
return "配置失败"
|
||||
|
||||
@staticmethod
|
||||
def _get_nginx_referer_security_idx(ng_conf: str) -> Tuple[Optional[int], Optional[int]]:
|
||||
rep_security = re.compile(
|
||||
r"(\s*#\s*SECURITY-START.*\n)?\s*location\s+~\s+\.\*\\\.\(.*(\|.*)?\)\$\s*\{[^}]*valid_referers"
|
||||
)
|
||||
res = rep_security.search(ng_conf)
|
||||
if res is None:
|
||||
return None, None
|
||||
|
||||
start_idx = res.start()
|
||||
s_idx = start_idx + ng_conf[start_idx:].find("{") + 1 # 起始位置
|
||||
l_n = 1
|
||||
max_idx = len(ng_conf)
|
||||
while l_n > 0:
|
||||
next_l = ng_conf[s_idx:].find("{")
|
||||
next_r = ng_conf[s_idx:].find("}") # 可能存在报错
|
||||
if next_r == -1:
|
||||
return None, None
|
||||
if next_l == -1:
|
||||
next_l = max_idx
|
||||
|
||||
if next_l < next_r:
|
||||
l_n += 1
|
||||
else:
|
||||
l_n -= 1
|
||||
s_idx += min(next_l, next_r) + 1
|
||||
|
||||
rep_comment = re.search(r"^\s*#\s*SECURITY-END[^\n]*\n", ng_conf[s_idx:])
|
||||
if rep_comment is not None:
|
||||
end_idx = s_idx + rep_comment.end()
|
||||
else:
|
||||
end_idx = s_idx
|
||||
|
||||
return start_idx, end_idx
|
||||
|
||||
@staticmethod
|
||||
def _build_apache_referer_security_conf(rc: _RefererConf) -> str:
|
||||
r_conf_list = ["#SECURITY-START 防盗链配置"]
|
||||
cond_format = " RewriteCond %{{HTTP_REFERER}} !{} [NC]"
|
||||
if rc.http_status == "false":
|
||||
r_conf_list.append(cond_format.format("^$"))
|
||||
|
||||
r_conf_list.extend(map(lambda x: cond_format.format(x.strip()), rc.domains.split(",")))
|
||||
|
||||
rule_format = " RewriteRule .({}) {} "
|
||||
if rc.return_rule[0] == "/":
|
||||
r_conf_list.append(rule_format.format(
|
||||
"|".join(map(lambda x: x.strip(), rc.fix.split(","))),
|
||||
rc.return_rule
|
||||
))
|
||||
else:
|
||||
r_conf_list.append(rule_format.format(
|
||||
"|".join(map(lambda x: x.strip(), rc.fix.split(","))),
|
||||
"/{s}.html [R={s},NC,L]".format(s=rc.return_rule)
|
||||
))
|
||||
|
||||
r_conf_list.append(" #SECURITY-END")
|
||||
|
||||
return "\n".join(r_conf_list)
|
||||
|
||||
# 根据配置正则确定位置 并将配置文件添加进去 use_start 参数指定添加的前后
|
||||
def _add_apache_referer_security_by_rep_idx(self,
|
||||
rep: re.Pattern,
|
||||
use_start: bool,
|
||||
ap_conf, ap_file, r_conf) -> bool:
|
||||
tmp_conf_list = []
|
||||
last_idx = 0
|
||||
for tmp in rep.finditer(ap_conf):
|
||||
tmp_conf_list.append(ap_conf[last_idx:tmp.start()])
|
||||
if use_start:
|
||||
tmp_conf_list.append("\n" + r_conf + "\n")
|
||||
tmp_conf_list.append(tmp.group())
|
||||
else:
|
||||
tmp_conf_list.append(tmp.group())
|
||||
tmp_conf_list.append("\n" + r_conf + "\n")
|
||||
last_idx = tmp.end()
|
||||
if last_idx == 0:
|
||||
return False
|
||||
|
||||
tmp_conf_list.append(ap_conf[last_idx:])
|
||||
_conf = "".join(tmp_conf_list)
|
||||
write_file(ap_file, _conf)
|
||||
if self.webserver == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _set_apache_referer_security(self, rc: _RefererConf) -> Optional[str]:
|
||||
ap_file = '/www/server/panel/vhost/apache/{}{}.conf'.format(self.config_prefix, rc.name)
|
||||
ap_conf = read_file(ap_file)
|
||||
if not isinstance(ap_conf, str):
|
||||
return "nginx配置文件丢失,无法设置"
|
||||
rep_security = re.compile(r"#\s*SECURITY-START(.|\n)#SECURITY-END.*\n")
|
||||
res = rep_security.search(ap_conf)
|
||||
if rc.status == "true":
|
||||
r_conf = self._build_apache_referer_security_conf(rc)
|
||||
if res is not None:
|
||||
new_conf_list = []
|
||||
_idx = 0
|
||||
for tmp_res in rep_security.finditer(ap_conf):
|
||||
new_conf_list.append(ap_conf[_idx:tmp_res.start()])
|
||||
new_conf_list.append("\n" + r_conf + "\n")
|
||||
_idx = tmp_res.end()
|
||||
new_conf_list.append(ap_conf[_idx:])
|
||||
new_conf = "".join(new_conf_list)
|
||||
write_file(ap_file, new_conf)
|
||||
if self.webserver == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return "配置修改失败"
|
||||
|
||||
rep_redirect_include = re.compile(r"IncludeOptional +.*/redirect/.*\*\.conf.*\n", re.M)
|
||||
rep_custom_log = re.compile(r"CustomLog .*\n")
|
||||
rep_deny_files = re.compile(r"\n\s*#DENY FILES")
|
||||
if self._add_apache_referer_security_by_rep_idx(rep_redirect_include, False, ap_conf, ap_file, r_conf):
|
||||
return
|
||||
if self._add_apache_referer_security_by_rep_idx(rep_custom_log, False, ap_conf, ap_file, r_conf):
|
||||
return
|
||||
if self._add_apache_referer_security_by_rep_idx(rep_deny_files, True, ap_conf, ap_file, r_conf):
|
||||
return
|
||||
return "设置添加失败"
|
||||
|
||||
else:
|
||||
if res is None:
|
||||
return
|
||||
|
||||
new_conf_list = []
|
||||
_idx = 0
|
||||
for tmp_res in rep_security.finditer(ap_conf):
|
||||
new_conf_list.append(ap_conf[_idx:tmp_res.start()])
|
||||
_idx = tmp_res.end()
|
||||
new_conf_list.append(ap_conf[_idx:])
|
||||
new_conf = "".join(new_conf_list)
|
||||
write_file(ap_file, new_conf)
|
||||
if self.webserver == "apache" and check_server_config() is not None:
|
||||
write_file(ap_file, ap_conf)
|
||||
return "配置修改失败"
|
||||
|
||||
def get_referer_security(self, site_name) -> Optional[dict]:
|
||||
r = self.get_config(site_name)
|
||||
if r is None:
|
||||
return None
|
||||
return json.loads(str(r))
|
||||
|
||||
def remove_site_referer_info(self, site_name):
|
||||
file_path = "{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name)
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
|
||||
# 从配置文件中获取referer配置信息
|
||||
# 暂时不实现,意义不大
|
||||
def _get_referer_security_by_conf(self, site_name):
|
||||
if self.webserver == "nginx":
|
||||
self._get_nginx_referer_security()
|
||||
else:
|
||||
self._get_apache_referer_security()
|
||||
|
||||
|
||||
class Referer:
|
||||
|
||||
def __init__(self, config_prefix: str):
|
||||
self.config_prefix: str = config_prefix
|
||||
self._r = RealReferer(self.config_prefix)
|
||||
|
||||
def get_referer_security(self, get):
|
||||
try:
|
||||
site_name = get.site_name.strip()
|
||||
except AttributeError:
|
||||
return json_response(status=False, msg="Parameter error")
|
||||
|
||||
data = self._r.get_referer_security(site_name)
|
||||
if data is None:
|
||||
default_conf = {
|
||||
"name": site_name,
|
||||
"fix": "jpg,jpeg,gif,png,js,css",
|
||||
"domains": "",
|
||||
"status": "false",
|
||||
"return_rule": "404",
|
||||
"http_status": "false",
|
||||
}
|
||||
site_info = DB("sites").where("name=?", (site_name,)).field('id').find()
|
||||
if not isinstance(site_info, dict):
|
||||
return json_response(status=False, msg="Site query error")
|
||||
domains_info = DB("domain").where("pid=?", (site_info["id"],)).field('name').select()
|
||||
if not isinstance(domains_info, list):
|
||||
return json_response(status=False, msg="Site query error")
|
||||
|
||||
default_conf["domains"] = ",".join(map(lambda x: x["name"], domains_info))
|
||||
return json_response(status=True, data=default_conf)
|
||||
|
||||
return json_response(status=True, data=data)
|
||||
|
||||
def set_referer_security(self, get):
|
||||
r = self._r.check_args(get)
|
||||
if isinstance(r, str):
|
||||
return json_response(status=False, msg=r)
|
||||
|
||||
flag, msg = self._r.set_referer_security(r)
|
||||
return json_response(status=flag, msg=msg)
|
||||
103
mod/base/web_conf/server_extension.py
Normal file
103
mod/base/web_conf/server_extension.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import public
|
||||
|
||||
class NginxExtension:
|
||||
_EXTENSION_DIR = "{}/vhost/nginx/extension".format(public.get_panel_path())
|
||||
|
||||
@classmethod
|
||||
def set_extension(cls, site_name: str, config_path: str) -> str:
|
||||
config_data = public.readFile(config_path)
|
||||
if not config_data:
|
||||
return ""
|
||||
return cls.set_extension_by_config(site_name, config_data)
|
||||
|
||||
@classmethod
|
||||
def set_extension_by_config(cls, site_name: str, config_data: str) -> str:
|
||||
ext_path = "{}/{}".format(cls._EXTENSION_DIR, site_name)
|
||||
if os.path.exists(ext_path):
|
||||
if not os.path.isdir(ext_path):
|
||||
os.remove(ext_path)
|
||||
os.makedirs(ext_path)
|
||||
else:
|
||||
os.makedirs(ext_path)
|
||||
|
||||
rep_exp_list=[
|
||||
re.compile(r"(?<!#)server\s*{(([^{\n]*\n)|(\s*#.*\n)){0,20}\s*root\s+/[^\n]*\n"),
|
||||
re.compile(r"(?<!#)server\s*{(([^{\n]*\n)|(\s*#.*\n)){0,20}\s*index\s+[^\n]*\n"),
|
||||
re.compile(r"(?<!#)server\s*{(([^{\n]*\n)|(\s*#.*\n)){0,20}\s*server_name\s+[^\n]*\n"),
|
||||
]
|
||||
|
||||
insert_ext = " include {}/*.conf;\n".format(ext_path)
|
||||
for rep_exp in rep_exp_list:
|
||||
find_list = list(re.finditer(rep_exp, config_data))
|
||||
if not find_list:
|
||||
continue
|
||||
for tmp in find_list[::-1]:
|
||||
config_data = config_data[:tmp.end()] + insert_ext + config_data[tmp.end():]
|
||||
break
|
||||
|
||||
return config_data
|
||||
|
||||
@classmethod
|
||||
def remove_extension(cls, site_name: str, config_path: str):
|
||||
ext_path = "{}/{}".format(cls._EXTENSION_DIR, site_name)
|
||||
if os.path.isdir(ext_path):
|
||||
shutil.rmtree(ext_path)
|
||||
|
||||
config_data= public.readFile(config_path)
|
||||
if not config_data:
|
||||
return None
|
||||
return cls.remove_extension_from_config(site_name, config_data)
|
||||
|
||||
@staticmethod
|
||||
def remove_extension_from_config(site_name: str, config_data: str):
|
||||
regexp = re.compile(r"\s*include\s+/.*extension/.*/\*\.conf;[^\n]*\n")
|
||||
return re.sub(regexp, "\n", config_data)
|
||||
|
||||
@staticmethod
|
||||
def has_extension(conf_data: str) -> bool:
|
||||
regexp = re.compile(r"\s*include\s+/.*extension/.*/\*\.conf;[^\n]*\n")
|
||||
return bool(re.search(regexp, conf_data))
|
||||
|
||||
|
||||
class ApacheExtension(NginxExtension):
|
||||
_EXTENSION_DIR = "{}/vhost/apache/extension".format(public.get_panel_path())
|
||||
|
||||
@classmethod
|
||||
def set_extension_by_config(cls, site_name: str, config_data: str) -> str:
|
||||
ext_path = "{}/{}".format(cls._EXTENSION_DIR, site_name)
|
||||
if not os.path.exists(ext_path):
|
||||
os.makedirs(ext_path)
|
||||
else:
|
||||
if not os.path.isdir(ext_path):
|
||||
os.remove(ext_path)
|
||||
os.makedirs(ext_path)
|
||||
|
||||
rep_exp_list=[
|
||||
re.compile(r"<VirtualHost\s+\S+:\d+>\s(.*\n){0,8}\s*ServerAlias\s+[^\n]*\n"),
|
||||
re.compile(r"<VirtualHost\s+\S+:\d+>\s(.*\n){0,6}\s*DocumentRoot\s+[^\n]*\n"),
|
||||
]
|
||||
|
||||
insert_ext = " IncludeOptional {}/*.conf\n".format(ext_path)
|
||||
for rep_exp in rep_exp_list:
|
||||
find_list = list(re.finditer(rep_exp, config_data))
|
||||
if not find_list:
|
||||
continue
|
||||
for tmp in find_list[::-1]:
|
||||
config_data = config_data[:tmp.end()] + insert_ext + config_data[tmp.end():]
|
||||
break
|
||||
|
||||
return config_data
|
||||
|
||||
@staticmethod
|
||||
def remove_extension_from_config(site_name: str, config_data: str):
|
||||
regexp = re.compile(r"\s*IncludeOptional\s+/.*extension/.*/\*\.conf[^\n]*\n")
|
||||
return re.sub(regexp, "\n", config_data)
|
||||
|
||||
@staticmethod
|
||||
def has_extension(conf_data: str) -> bool:
|
||||
regexp = re.compile(r"\s*IncludeOptional\s+/.*extension/.*/\*\.conf[^\n]*\n")
|
||||
return bool(re.search(regexp, conf_data))
|
||||
1335
mod/base/web_conf/ssl.py
Normal file
1335
mod/base/web_conf/ssl.py
Normal file
File diff suppressed because it is too large
Load Diff
200
mod/base/web_conf/util.py
Normal file
200
mod/base/web_conf/util.py
Normal file
@@ -0,0 +1,200 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Tuple, Callable
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
|
||||
import public
|
||||
|
||||
|
||||
def webserver() -> Optional[str]:
|
||||
if os.path.exists('/www/server/nginx/sbin/nginx'):
|
||||
web_server = 'nginx'
|
||||
elif os.path.exists('/www/server/apache/bin/apachectl'):
|
||||
web_server = 'apache'
|
||||
elif os.path.exists('/usr/local/lsws/bin/lswsctrl'):
|
||||
web_server = 'openlitespeed'
|
||||
else:
|
||||
web_server = None
|
||||
return web_server
|
||||
|
||||
|
||||
def check_server_config() -> Optional[str]:
|
||||
w_s = webserver()
|
||||
setup_path = "/www/server"
|
||||
if w_s == 'nginx':
|
||||
shell_str = (
|
||||
"ulimit -n 8192; "
|
||||
"{setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf"
|
||||
).format(setup_path=setup_path)
|
||||
result: Tuple[str, str] = public.ExecShell(shell_str)
|
||||
searchStr = 'successful'
|
||||
elif w_s == 'apache':
|
||||
shell_str = (
|
||||
"ulimit -n 8192; "
|
||||
"{setup_path}/apache/bin/apachectl -t"
|
||||
).format(setup_path=setup_path)
|
||||
result: Tuple[str, str] = public.ExecShell(shell_str)
|
||||
searchStr = 'Syntax OK'
|
||||
else:
|
||||
return None
|
||||
if result[1].find(searchStr) == -1:
|
||||
public.WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1],))
|
||||
return result[1]
|
||||
|
||||
|
||||
def read_file(filename, mode='r') -> Optional[str]:
|
||||
"""
|
||||
读取文件内容
|
||||
@filename 文件名
|
||||
return string(bin) 若文件不存在,则返回None
|
||||
"""
|
||||
import os
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
fp = None
|
||||
try:
|
||||
fp = open(filename, mode=mode)
|
||||
f_body = fp.read()
|
||||
except:
|
||||
return None
|
||||
finally:
|
||||
if fp and not fp.closed:
|
||||
fp.close()
|
||||
return f_body
|
||||
|
||||
|
||||
def write_file(filename: str, s_body: str, mode='w+') -> bool:
|
||||
"""
|
||||
写入文件内容
|
||||
@filename 文件名
|
||||
@s_body 欲写入的内容
|
||||
return bool 若文件不存在则尝试自动创建
|
||||
"""
|
||||
try:
|
||||
fp = open(filename, mode=mode)
|
||||
fp.write(s_body)
|
||||
fp.close()
|
||||
return True
|
||||
except:
|
||||
try:
|
||||
fp = open(filename, mode=mode, encoding="utf-8")
|
||||
fp.write(s_body)
|
||||
fp.close()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def debug_api_warp(fn):
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
except:
|
||||
public.print_log(public.get_error_info())
|
||||
return {
|
||||
|
||||
}
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# 重载Web服务配置
|
||||
def service_reload():
|
||||
setup_path = "/www/server"
|
||||
if os.path.exists('{}/nginx/sbin/nginx'.format(setup_path)):
|
||||
result = public.ExecShell('/etc/init.d/nginx reload')
|
||||
if result[1].find('nginx.pid') != -1:
|
||||
public.ExecShell('pkill -9 nginx && sleep 1')
|
||||
public.ExecShell('/etc/init.d/nginx start')
|
||||
elif os.path.exists('{}/apache/bin/apachectl'.format(setup_path)):
|
||||
result = public.ExecShell('/etc/init.d/httpd reload')
|
||||
else:
|
||||
result = public.ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart')
|
||||
return result
|
||||
|
||||
|
||||
# 防正则转译
|
||||
def pre_re_key(input_str: str) -> str:
|
||||
re_char = ['$', '(', ')', '*', '+', '.', '[', ']', '{', '}', '?', '^', '|', '\\']
|
||||
res = []
|
||||
for i in input_str:
|
||||
if i in re_char:
|
||||
res.append("\\" + i)
|
||||
else:
|
||||
res.append(i)
|
||||
return "".join(res)
|
||||
|
||||
|
||||
def get_log_path() -> str:
|
||||
log_path = public.readFile("{}/data/sites_log_path.pl".format(public.get_panel_path()))
|
||||
if isinstance(log_path, str) and os.path.isdir(log_path):
|
||||
return log_path
|
||||
return public.GetConfigValue('logs_path')
|
||||
|
||||
|
||||
|
||||
# 2024/4/18 上午9:44 域名编码转换
|
||||
def to_puny_code(domain):
|
||||
try:
|
||||
try:
|
||||
import idna
|
||||
except:
|
||||
os.system("btpip install idna -I")
|
||||
import idna
|
||||
|
||||
import re
|
||||
match = re.search(u"[^u\0000-u\001f]+", domain)
|
||||
if not match:
|
||||
return domain
|
||||
try:
|
||||
if domain.startswith("*."):
|
||||
return "*." + idna.encode(domain[2:]).decode("utf8")
|
||||
else:
|
||||
return idna.encode(domain).decode("utf8")
|
||||
except:
|
||||
return domain
|
||||
except:
|
||||
return domain
|
||||
|
||||
|
||||
# 2024/4/18 下午5:48 中文路径处理
|
||||
def to_puny_code_path(path):
|
||||
if sys.version_info[0] == 2: path = path.encode('utf-8')
|
||||
if os.path.exists(path): return path
|
||||
import re
|
||||
match = re.search(u"[\x80-\xff]+", path)
|
||||
if not match: match = re.search(u"[\u4e00-\u9fa5]+", path)
|
||||
if not match: return path
|
||||
npath = ''
|
||||
for ph in path.split('/'):
|
||||
npath += '/' + to_puny_code(ph)
|
||||
return npath.replace('//', '/')
|
||||
|
||||
|
||||
|
||||
class _DB:
|
||||
|
||||
def __call__(self, table: str):
|
||||
import db
|
||||
with db.Sql() as t:
|
||||
t.table(table)
|
||||
return t
|
||||
|
||||
|
||||
DB = _DB()
|
||||
|
||||
GET_CLASS = public.dict_obj
|
||||
|
||||
listen_ipv6: Callable[[], bool] = public.listen_ipv6
|
||||
|
||||
ExecShell: Callable = public.ExecShell
|
||||
|
||||
|
||||
def use_http2() -> bool:
|
||||
versionStr = public.readFile('/www/server/nginx/version.pl')
|
||||
if isinstance(versionStr, str):
|
||||
if versionStr.find('1.8.1') == -1:
|
||||
return True
|
||||
return False
|
||||
Reference in New Issue
Block a user