Update to 7.7.0

Since version 7.7.0, we recommend yours update python to 3.12.

[+] Using nginx technology to load static files improves access speed
[+] Refactor homepage, website, FTP, and database using vue3
[+] Table loading changed to skeleton screen
[+] Add Website statistics-v2 professional plug-in
[+] Add Home page - top 5 resource occupancy
[+] Add protection for Files management (requires Tamper-proof for Enterprise 3.7)
[+] Website, FTP, Databases page add program status
[+] Add FTP log analysis (only supports Centos)
[+] Add password-free login to phpMyAdmin
[+] Add Proxy Project in Website (Supported when web service uses Nginx)
[+] Add WP Toolkit (Pro version only)
[+] Redesigned Docker module
[+] Add WP Toolkit Protection
[+] Add WP Toolkit Backup and Restore
[+] Add WP Toolkit Migrated
[+] Add WP Toolkit Clone site (supports new domain and subdomain)
[+] Add WP Toolkit Create site from backup of other panel
[+] Add WP Toolkit support for Cron automatic backup (only save Local disk)
[+] Add WP Toolkit operation log
[+] Add Integrity check for WP Toolkit
[+] Add WP Toolkit plug-in management and themes management

[*] Optimize phpMyAdmin formula access method
[*] Optimize Home page PHP display problem
[*] Optimize jump to the login interface after the login expires
[*] Optimize automatic renewal of SSL at some times
[*] Optimize Let's Encrypt to increase application success rate

[-] Fix Logs Audit cannot be opened
[-] Fix apache URL rewrite issue
[-] Fix phpmyadmin installation problem
[-] Fix the problem that some servers cannot install software
[-] Fix upload file error
[-] Fix left menu hiding problem
[-] Fix aaPanel Mobile QR code display problem
[-] Fix problem that third-party plug-ins are not displayed in the App Store
[-] Fix issue where the menu bar is blank when opening new tabs
[-] Fixed panel not being accessible in some cases
[-] Fix the issue where Curl warning caused the inability to apply for SSL
[-] Fix Quota issues for Website, FTP, Databases
[-] Fix file interface display problem on mobile terminal
This commit is contained in:
Jack
2024-07-19 11:25:10 +08:00
parent ed34994f45
commit ed55fa708d
1949 changed files with 329210 additions and 26644 deletions
Binary file not shown.
Binary file not shown.
+621 -416
View File
File diff suppressed because it is too large Load Diff
+26 -123
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
from flask import session,request
@@ -794,19 +794,19 @@ class ajax:
if not os.path.exists(filename): return public.return_msg_gettext(False,'Requested PHP version does NOT exist!')
phpini = public.readFile(filename)
data = {}
rep = "disable_functions\s*=\s{0,1}(.*)\n"
rep = "disable_functions\\s*=\\s{0,1}(.*)\n"
tmp = re.search(rep,phpini)
if tmp:
data['disable_functions'] = tmp.groups()[0]
rep = "upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)"
rep = r"upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)"
tmp = re.search(rep,phpini)
if tmp:
data['max'] = tmp.groups()[0]
rep = u"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n"
rep = u"\n;*\\s*cgi\\.fix_pathinfo\\s*=\\s*([0-9]+)\\s*\n"
tmp = re.search(rep,phpini)
if tmp:
if tmp.groups()[0] == '0':
@@ -1048,7 +1048,7 @@ class ajax:
#AUTH_END
"""
# nginx配置文件
ssl_conf = """server
ssl_conf = r"""server
{
listen 887 ssl;
server_name phpmyadmin;
@@ -1095,7 +1095,7 @@ class ajax:
#AUTH_END
"""
# apache配置
ssl_conf = '''Listen 887
ssl_conf = r'''Listen 887
<VirtualHost *:887>
ServerAdmin webmaster@example.com
DocumentRoot "/www/server/phpmyadmin"
@@ -1134,6 +1134,13 @@ class ajax:
</Directory>
</VirtualHost>'''.format(public.get_php_proxy(v["ext"]["phpversion"],'apache'),auth)
public.writeFile("/www/server/panel/vhost/apache/phpmyadmin.conf", ssl_conf)
import firewalls
fw = firewalls.firewalls()
fw.AddAcceptPort(public.to_dict_obj({
'port': '887',
'ps': public.get_msg_gettext('New phpMyAdmin SSL Port'),
}))
else:
if os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"):
os.remove("/www/server/panel/vhost/nginx/phpmyadmin.conf")
@@ -1331,9 +1338,9 @@ class ajax:
conf = public.readFile('/etc/init.d/memcached')
result = {}
result['bind'] = re.search('IP=(.+)',conf).groups()[0]
result['port'] = int(re.search('PORT=(\d+)',conf).groups()[0])
result['maxconn'] = int(re.search('MAXCONN=(\d+)',conf).groups()[0])
result['cachesize'] = int(re.search('CACHESIZE=(\d+)',conf).groups()[0])
result['port'] = int(re.search(r'PORT=(\d+)',conf).groups()[0])
result['maxconn'] = int(re.search(r'MAXCONN=(\d+)',conf).groups()[0])
result['cachesize'] = int(re.search(r'CACHESIZE=(\d+)',conf).groups()[0])
tn = telnetlib.Telnet(result['bind'],result['port'])
tn.write(b"stats\n")
tn.write(b"quit\n")
@@ -1358,9 +1365,9 @@ class ajax:
confFile = '/etc/init.d/memcached'
conf = public.readFile(confFile)
conf = re.sub('IP=.+','IP='+get.ip,conf)
conf = re.sub('PORT=\d+','PORT='+get.port,conf)
conf = re.sub('MAXCONN=\d+','MAXCONN='+get.maxconn,conf)
conf = re.sub('CACHESIZE=\d+','CACHESIZE='+get.cachesize,conf)
conf = re.sub(r'PORT=\d+','PORT='+get.port,conf)
conf = re.sub(r'MAXCONN=\d+','MAXCONN='+get.maxconn,conf)
conf = re.sub(r'CACHESIZE=\d+','CACHESIZE='+get.cachesize,conf)
public.writeFile(confFile,conf)
public.ExecShell(confFile + ' reload')
return public.return_msg_gettext(True,'Setup successfully!')
@@ -1369,8 +1376,8 @@ class ajax:
def GetRedisStatus(self,get):
import re
c = public.readFile('/www/server/redis/redis.conf')
port = re.findall('\n\s*port\s+(\d+)',c)[0]
password = re.findall('\n\s*requirepass\s+(.+)',c)
port = re.findall('\n\\s*port\\s+(\\d+)',c)[0]
password = re.findall('\n\\s*requirepass\\s+(.+)',c)
if password:
password = ' -a ' + password[0]
else:
@@ -1431,113 +1438,9 @@ class ajax:
if not os.path.exists(get.path): return public.return_msg_gettext(False,'Log file does NOT exist!')
return public.returnMsg(True,public.xsssec(public.GetNumLines(get.path,1000)))
def get_pd(self,get):
from BTPanel import cache
tmp = -1
try:
import panelPlugin
# get = public.dict_obj()
# get.init = 1
tmp1 = panelPlugin.panelPlugin().get_cloud_list(get)
except:
tmp1 = None
if tmp1:
tmp = tmp1[public.to_string([112, 114, 111])]
ltd = tmp1.get('ltd', -1)
else:
ltd = -1
tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110]))
if tmp4:
tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4
if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1')
tmp = public.readFile(tmp_f)
if tmp: tmp = int(tmp)
if not ltd: ltd = -1
if tmp == None: tmp = -1
if ltd < 1:
if ltd == -2:
tmp3 = public.to_string(
[60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, 100,
45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101,
61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111,
110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97,
114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807,
26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111,
102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, 82, 69, 78, 69, 87,
60, 47, 97,
62, 60, 47, 115, 112, 97, 110, 62])
elif tmp == -1:
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98,
116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, 99, 108, 105, 99,
107,
61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112,
114,
111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107,
32, 116, 111, 32,
103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, 36153, 29256, 60, 47, 115, 112,
97, 110, 62])
elif tmp == -2:
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32,
115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35,
102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103,
104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45,
114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399,
60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34,
98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61,
34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114,
111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97,
110, 62])
if tmp >= 0 and ltd in [-1, -2]:
if tmp == 0:
tmp2 = public.to_string([76, 105, 102, 101, 116, 105, 109, 101])
tmp3 = public.to_string(
[60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, 114,
111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 60, 115, 112, 97, 110, 32, 115,
116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54,
100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32,
98, 111, 108, 100, 59, 34, 62, 123, 48, 125, 60, 47, 115, 112, 97, 110, 62, 60,
47, 115, 112, 97, 110, 62]).format(tmp2)
else:
tmp2 = time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(tmp))
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116,
112, 114, 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112,
97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114,
58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119,
101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114,
103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123,
48, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115,
115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105,
99,
107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119,
95,
112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60,
47, 115, 112, 97, 110, 62]).format(tmp2)
else:
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112,
114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, 99, 108, 105, 99, 107,
61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 112,
114, 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99,
107, 32, 116,
111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82,
69, 69, 60, 47, 115, 112, 97, 110, 62])
else:
tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116,
100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, 97, 110, 32, 115,
116,
121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50,
54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111,
108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53,
112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108,
97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105,
99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95,
112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115,
112, 97, 110, 62]).format(
time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(ltd)))
return tmp3, tmp, ltd
# 获取授权信息
def get_pd(self, get):
return public.get_pd(get)
#检查用户绑定是否正确
def check_user_auth(self,get):
+26 -26
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -52,7 +52,7 @@ class apache:
data = {}
# 计算启动时间
Uptime = re.search("ServerUptimeSeconds:\s+(.*)",result)
Uptime = re.search(r"ServerUptimeSeconds:\s+(.*)",result)
if not Uptime:
return public.return_msg_gettext(False, "Get worker Uptime False")
Uptime = int(Uptime.group(1))
@@ -63,11 +63,11 @@ class apache:
min = math.floor(min - (days * 60 * 24) - (hours * 60))
#格式化重启时间
restarttime = re.search("RestartTime:\s+(.*)",result)
restarttime = re.search(r"RestartTime:\s+(.*)",result)
if not restarttime:
return public.return_msg_gettext(False, "Get worker Restart Time False")
restarttime = restarttime.group(1)
rep = "\w+,\s([\w-]+)\s([\d\:]+)\s\w+"
rep = r"\w+,\s([\w-]+)\s([\d\:]+)\s\w+"
date = re.search(rep,restarttime)
if not date:
return public.return_msg_gettext(False, "Get worker date False")
@@ -85,28 +85,28 @@ class apache:
date = date.split("-")
date = "%s-%s-%s" % (date[2],date[1],date[0])
reqpersec = re.search("ReqPerSec:\s+(.*)", result)
reqpersec = re.search(r"ReqPerSec:\s+(.*)", result)
if not reqpersec:
return public.return_msg_gettext(False, "Get worker reqpersec False")
reqpersec = reqpersec.group(1)
if re.match("^\.", reqpersec):
if re.match(r"^\.", reqpersec):
reqpersec = "%s%s" % (0,reqpersec)
data["RestartTime"] = "%s %s" % (date,timedetail)
data["UpTime"] = "%s day %s hour %s minute" % (str(int(days)),str(int(hours)),str(int(min)))
total_acc = re.search("Total Accesses:\s+(\d+)",result)
total_acc = re.search(r"Total Accesses:\s+(\d+)",result)
if not total_acc:
return public.return_msg_gettext(False, "Get worker TotalAccesses False")
data["TotalAccesses"] = total_acc.group(1)
total_kb = re.search("Total kBytes:\s+(\d+)",result)
total_kb = re.search(r"Total kBytes:\s+(\d+)",result)
if not total_kb:
return public.return_msg_gettext(False, "Get worker TotalKBytes False")
data["TotalKBytes"] = total_kb.group(1)
data["ReqPerSec"] = round(float(reqpersec), 2)
busywork = re.search("BusyWorkers:\s+(\d+)",result)
busywork = re.search(r"BusyWorkers:\s+(\d+)",result)
if not busywork:
return public.return_msg_gettext(False, "Get worker BusyWorkers False")
data["BusyWorkers"] = busywork.group(1)
idlework = re.search("IdleWorkers:\s+(\d+)",result)
idlework = re.search(r"IdleWorkers:\s+(\d+)",result)
if not idlework:
return public.return_msg_gettext(False, "Get worker IdleWorkers False")
data["IdleWorkers"] = idlework.group(1)
@@ -119,7 +119,7 @@ class apache:
apachempmcontent = public.readFile(self.apachempmfile)
if not "mpm_event_module" in apachempmcontent:
return public.returnMsg(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty")
apachempmcontent = re.search("\<IfModule mpm_event_module\>(\n|.)+?\</IfModule\>",apachempmcontent).group()
apachempmcontent = re.search(r"\<IfModule mpm_event_module\>(\n|.)+?\</IfModule\>",apachempmcontent).group()
ps = ["%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Request timeout')),
public.get_msg_gettext('Keep alive'),
"%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Connection timeout')),
@@ -131,7 +131,7 @@ class apache:
conflist = []
n = 0
for i in gets:
rep = "(%s)\s+(\w+)" % i
rep = r"(%s)\s+(\w+)" % i
k = re.search(rep, apachedefaultcontent)
if not k:
return public.return_msg_gettext(False, "Get Key {} False",(i,))
@@ -154,7 +154,7 @@ class apache:
gets = ["StartServers","MaxSpareThreads","MinSpareThreads","ThreadsPerChild","MaxRequestWorkers","MaxConnectionsPerChild"]
n = 0
for i in gets:
rep = "(%s)\s+(\w+)" % i
rep = r"(%s)\s+(\w+)" % i
k = re.search(rep, apachempmcontent)
if not k:
return public.return_msg_gettext(False, "Get Key {} False",(i,))
@@ -175,7 +175,7 @@ class apache:
if not "mpm_event_module" in apachempmcontent:
return public.return_msg_gettext(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty")
conflist = []
getdict = get.__dict__
getdict = get.get_items()
for i in getdict.keys():
if i != "__module__" and i != "__doc__" and i != "data" and i != "args" and i != "action":
getpost = {
@@ -189,11 +189,11 @@ class apache:
return public.return_msg_gettext(False, 'Parameter ERROR!')
else:
print(c["value"])
if not re.search("\d+", c["value"]):
if not re.search(r"\d+", c["value"]):
print(c["name"],c["value"])
return public.return_msg_gettext(False, 'Parameter ERROR!')
rep = "%s\s+\w+" % c["name"]
rep = r"%s\s+\w+" % c["name"]
if re.search(rep,apachedefaultcontent):
newconf = "%s %s" % (c["name"],c["value"])
apachedefaultcontent = re.sub(rep,newconf,apachedefaultcontent)
@@ -214,7 +214,7 @@ class apache:
def add_httpd_access_log_format(self,args):
'''
@name 添加httpd日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param log_format 需要设置的日志格式["$server_name","$remote_addr","-"....]
@param log_format_name
@param act 操作方式 add/edit
@@ -244,13 +244,13 @@ class apache:
def del_httpd_access_log_format(self,args):
'''
@name 删除日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param log_format_name
'''
conf = public.readFile(self.httpdconf)
if not conf:
return public.return_msg_gettext(False, 'Configuration file not exist')
reg = '\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name)
reg = r'\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name)
conf = re.sub(reg,'',conf)
self._del_format_log_of_website(args.log_format_name)
public.writeFile(self.httpdconf,conf)
@@ -270,7 +270,7 @@ class apache:
if not site_format_log_status[s]:
continue
website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(s)
format_exist_reg = 'CustomLog\s+"/www.*"\s+{}'.format(log_format_name)
format_exist_reg = r'CustomLog\s+"/www.*"\s+{}'.format(log_format_name)
conf = public.readFile(website_conf_file)
if not conf:continue
if not re.search(format_exist_reg,conf):continue
@@ -323,7 +323,7 @@ class apache:
format_name = [i.split('LOG_FORMAT_BEGIN_')[-1] for i in data]
format_log = {}
for i in format_name:
format_reg = "#LOG_FORMAT_BEGIN_{n}(\n|.)+LogFormat\s+\'(.*)\'\s+{n}".format(n=i)
format_reg = r"#LOG_FORMAT_BEGIN_{n}(\n|.)+LogFormat\s+\'(.*)\'\s+{n}".format(n=i)
tmp = re.search(format_reg,conf)
if not tmp:
continue
@@ -336,7 +336,7 @@ class apache:
def set_httpd_format_log_to_website(self,args):
'''
@name 设置网站日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param sites aaa.com,bbb.com
@param log_format_name
'''
@@ -344,13 +344,13 @@ class apache:
sites = loads(args.sites)
try:
all_site = public.M('sites').field('name').select()
reg = 'CustomLog\s+"/www.*{}\s*'.format(args.log_format_name)
reg = r'CustomLog\s+"/www.*{}\s*'.format(args.log_format_name)
for site in all_site:
website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(site['name'])
conf = public.readFile(website_conf_file)
if not conf:
return public.return_msg_gettext(False, 'Configuration file not exist')
format_exist_reg = '(CustomLog\s+"/www.*\_log).*'
format_exist_reg = r'(CustomLog\s+"/www.*\_log).*'
access_log = re.search(format_exist_reg, conf).groups()[0] + '" ' + args.log_format_name
if site['name'] not in sites and re.search(format_exist_reg,conf):
access_log = ' '.join(access_log.split()[:-1])
+3 -3
View File
@@ -1,8 +1,8 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 1249648969@qq.com
# | 主控 备份
@@ -283,7 +283,7 @@ class backup_bak:
public.ExecShell("sed -i '/password=/d' /etc/my.cnf")
if act:
mycnf = public.readFile('/etc/my.cnf');
rep = "\[mysqldump\]\nuser=root"
rep = "\\[mysqldump\\]\nuser=root"
sea = "[mysqldump]\n"
subStr = sea + "user=root\npassword=\"" + root + "\"\n";
mycnf = mycnf.replace(sea,subStr)
+152 -1
View File
@@ -47,6 +47,9 @@ class SimpleCache(BaseCache):
__session_key = 'BT_:'
__session_basedir = '/www/server/panel/data/session'
__SHM_PREFIX = 'SHM_:'
__SHM_BASEDIR = '/dev/shm/aap-shm'
def __init__(self, threshold=500, default_timeout=300):
BaseCache.__init__(self, default_timeout)
self._cache = {}
@@ -109,6 +112,15 @@ class SimpleCache(BaseCache):
def get(self, key):
if not isinstance(key,str): return None
try:
# 优先从shm中查找
_shm_val = self.__get_shm(key)
if _shm_val is not None:
return _shm_val
except: pass
try:
expires, value = self._cache[key]
if expires == 0 or expires > time():
@@ -125,6 +137,12 @@ class SimpleCache(BaseCache):
if value_type not in type_list:
return False
try:
# 优先写入shm
if self.__set_shm(key, value, timeout):
return True
except: pass
# 过期清理
expires = self._normalize_timeout(timeout)
self._prune()
@@ -148,6 +166,12 @@ class SimpleCache(BaseCache):
if value_type not in type_list:
return False
try:
# 优先写入shm
if self.__add_shm(key, value, timeout):
return True
except: pass
expires = self._normalize_timeout(timeout)
self._prune()
try:
@@ -162,11 +186,23 @@ class SimpleCache(BaseCache):
return True
def delete(self, key):
try:
# 优先删除shm
if self.__del_shm(key):
return True
except: pass
result = self._cache.pop(key, None) is not None
self.del_session_by_file(key)
return result
def has(self, key):
try:
# 优先shm
if self.__has_shm(key):
return True
except: pass
try:
expires, value = self._cache[key]
return expires == 0 or expires > time()
@@ -174,7 +210,6 @@ class SimpleCache(BaseCache):
if self.get_session_by_file(key): return True
return False
def get_expire_time(self, key):
try:
expires, value = self._cache[key]
@@ -194,3 +229,119 @@ class SimpleCache(BaseCache):
m.update(strings.encode('utf-8'))
return m.hexdigest()
def __set_shm(self, key, value, timeout=None):
'''
@name 尝试将缓存写入shm目录
@author Zhj<2022-10-08>
@param key<string> 键名
@param value<mixed> 值
@param timeout<int> 存活时间/秒
@return bool
'''
if key[:5] != self.__SHM_PREFIX:
return False
self.__makesure_shm_basedir()
expires = struct.pack('f', self._normalize_timeout(timeout))
filename = '/'.join((self.__SHM_BASEDIR, self.md5(key)))
with open(filename, 'wb') as fp:
fp.write(expires + pickle.dumps(value, pickle.HIGHEST_PROTOCOL))
os.chmod(filename, 384)
return True
def __get_shm(self, key):
'''
@name 尝试从shm目录下读取缓存
@author Zhj<2022-10-08>
@param key<string> 键名
@return mixed|None
'''
if key[:5] != self.__SHM_PREFIX:
return None
self.__makesure_shm_basedir()
filename = '/'.join((self.__SHM_BASEDIR, self.md5(key)))
if not os.path.exists(filename): return None
with open(filename, 'rb') as fp:
_val = fp.read()
expires = struct.unpack('f', _val[:4])[0]
# 过期 删除缓存文件
if expires > 0 and expires <= time():
os.remove(filename)
return None
return pickle.loads(_val[4:])
def __del_shm(self, key):
'''
@name 删除shm目录下的缓存
@author Zhj<2022-10-08>
@param key<string> 键名
@return bool
'''
if key[:5] != self.__SHM_PREFIX:
return False
self.__makesure_shm_basedir()
filename = '/'.join((self.__SHM_BASEDIR, self.md5(key)))
if os.path.exists(filename):
os.remove(filename)
return True
def __has_shm(self, key):
'''
@name 检查shm目录下的缓存是否存在
@author Zhj<2022-10-08>
@param key<string> 键名
@return bool
'''
if key[:5] != self.__SHM_PREFIX:
return False
self.__makesure_shm_basedir()
filename = '/'.join((self.__SHM_BASEDIR, self.md5(key)))
if not os.path.exists(filename): return False
# 获取缓存过期时间
with open(filename, 'rb') as fp:
expires = struct.unpack('f', fp.read(4))[0]
# 过期 删除缓存文件
if expires > 0 and expires <= time():
os.remove(filename)
return False
return True
def __add_shm(self, key, value, timeout=None):
'''
@name 尝试添加缓存到shm目录下
@author Zhj<2022-10-08>
@param key<string> 键名
@param value<mixed> 值
@param timeout<int> 缓存存活时间/秒
@return bool
'''
if self.__has_shm(key):
return False
return self.__set_shm(key, value, timeout)
def __makesure_shm_basedir(self):
'''
@name 确保shm下的缓存目录存在
@author Zhj<2022-10-08>
@return void
'''
if not os.path.exists(self.__SHM_BASEDIR):
os.makedirs(self.__SHM_BASEDIR, 384)
+135 -37
View File
@@ -1,12 +1,12 @@
#coding: utf-8
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
from BTPanel import session, cache , request, redirect, g,abort
from BTPanel import session, cache , request, redirect, g,abort, Response
from datetime import datetime
from public import dict_obj
import os
@@ -21,13 +21,13 @@ class panelSetup:
panel_path = public.get_panel_path()
if os.getcwd() != panel_path: os.chdir(panel_path)
g.ua = request.headers.get('User-Agent','')
g.ua = request.headers.get('User-Agent', '')
if g.ua:
ua = g.ua.lower()
if ua.find('spider') != -1 or g.ua.find('bot') != -1:
return abort(403)
g.version = '6.8.37'
g.version = '7.0.6'
g.title = public.GetConfigValue('title')
g.uri = request.path
g.debug = os.path.exists('data/debug.pl')
@@ -55,7 +55,6 @@ class panelSetup:
self.other_import()
return None
def other_import(self):
g.o = public.readFile('data/o.pl')
g.other_css = []
@@ -71,7 +70,6 @@ class panelSetup:
if os.path.exists(js_file): g.other_js.append('/static/other/{}'.format(js_name))
class panelAdmin(panelSetup):
setupPath = '/www/server'
@@ -82,6 +80,7 @@ class panelAdmin(panelSetup):
return result
result = self.check_login()
if result:
# public.print_log("local 2登录检查返回 {}".format(result))
return result
result = self.setSession()
if result:
@@ -100,7 +99,8 @@ class panelAdmin(panelSetup):
if request.method == 'GET':
g.menus = public.get_menus()
g.yaer = datetime.now().year
session["top_tips"] = public.get_msg_gettext("The current IE browser version is too low to display some features, please use another browser. Or if you use a browser developed by a Chinese company, please switch to Extreme Mode!")
session["top_tips"] = public.get_msg_gettext(
"The current IE browser version is too low to display some features, please use another browser. Or if you use a browser developed by a Chinese company, please switch to Extreme Mode!")
session["bt_help"] = public.get_msg_gettext("For Support|Suggestions, please visit the aaPanel Forum")
session["download"] = public.get_msg_gettext("Downloading:")
if not 'brand' in session:
@@ -121,7 +121,7 @@ class panelAdmin(panelSetup):
# 检查Web服务器类型
def checkWebType(self):
#if request.method == 'GET':
# if request.method == 'GET':
if not 'webserver' in session:
if os.path.exists('/usr/local/lsws/bin/lswsctrl'):
session['webserver'] = 'openlitespeed'
@@ -130,11 +130,12 @@ class panelAdmin(panelSetup):
else:
session['webserver'] = 'nginx'
if not 'webversion' in session:
if os.path.exists(self.setupPath+'/'+session['webserver']+'/version.pl'):
session['webversion'] = public.ReadFile(self.setupPath+'/'+session['webserver']+'/version.pl').strip()
if os.path.exists(self.setupPath + '/' + session['webserver'] + '/version.pl'):
session['webversion'] = public.ReadFile(
self.setupPath + '/' + session['webserver'] + '/version.pl').strip()
if not 'phpmyadminDir' in session:
filename = self.setupPath+'/data/phpmyadminDirName.pl'
filename = self.setupPath + '/data/phpmyadminDirName.pl'
if os.path.exists(filename):
session['phpmyadminDir'] = public.ReadFile(filename).strip()
return False
@@ -144,15 +145,38 @@ class panelAdmin(panelSetup):
if os.path.exists('data/close.pl'):
return redirect('/close')
# 跳转到登录页面
def to_login(self, url, msg=" Login has expired, please log in again"):
x_http_token = request.headers.get('X-Http-Token', '')
if x_http_token:
# 如果是ajax请求
# res = {"status": False, "msg": msg, "redirect": url}
# 修改为通用返回方式
res = {
"status": -1,
"timestamp": int(time.time()),
"message": {
# "result": "successfully added!",
"msg": msg,
"redirect": url
}
}
# public.print_log("有x_http_token res --{}".format(res))
return Response(json.dumps(res), content_type='application/json')
# public.print_log("无x_http_token 跳转 --{}".format(url))
return redirect(url)
# 检查登录
def check_login(self):
def check_login1(self):
try:
api_check = True
g.api_request = False
if not 'login' in session:
api_check = self.get_sk()
if api_check:
if not isinstance(api_check,dict):
if not isinstance(api_check, dict):
if public.get_admin_path() == '/login':
return redirect('/login?err=1')
return api_check
@@ -178,12 +202,12 @@ class panelAdmin(panelSetup):
if api_check:
now_time = time.time()
session_timeout = session.get('session_timeout',0)
session_timeout = session.get('session_timeout', 0)
if session_timeout < now_time and session_timeout != 0:
session.clear()
return redirect(public.get_admin_path())
login_token = session.get('login_token','')
login_token = session.get('login_token', '')
if login_token:
if login_token != public.get_login_token_auth():
session.clear()
@@ -201,51 +225,120 @@ class panelAdmin(panelSetup):
self.check_session()
except:
# public.print_log(public.get_error_info())
public.print_log(public.get_error_info())
session.clear()
public.print_error()
return redirect('/login?id=2')
# 检查登录
def check_login(self):
try:
api_check = True
g.api_request = False
if not 'login' in session:
api_check = self.get_sk()
if api_check:
# if not isinstance(api_check, dict):
# if public.get_admin_path() == '/login':
# return redirect('/login?err=1')
return api_check
g.api_request = True
else:
if session['login'] == False:
session.clear()
return self.to_login(public.get_admin_path())
if 'tmp_login_expire' in session:
s_file = 'data/session/{}'.format(session['tmp_login_id'])
if session['tmp_login_expire'] < time.time():
session.clear()
if os.path.exists(s_file): os.remove(s_file)
return self.to_login(public.get_admin_path(), 'The temporary login has expired, please log in again')
if not os.path.exists(s_file):
session.clear()
return self.to_login(public.get_admin_path(),'The temporary login has expired, please log in again')
# 检查客户端hash -- 不要删除
if not public.check_client_hash():
session.clear()
return self.to_login(public.get_admin_path(),'Client verification failed, please log in again')
if api_check:
now_time = time.time()
session_timeout = session.get('session_timeout', 0)
if session_timeout < now_time and session_timeout != 0:
session.clear()
return self.to_login(public.get_admin_path(),"Login session has expired, please log in again")
login_token = session.get('login_token', '')
if login_token:
if login_token != public.get_login_token_auth():
session.clear()
return self.to_login(public.get_admin_path(),'Login verification failed, please log in again')
# if api_check:
# filename = 'data/sess_files/' + public.get_sess_key()
# if not os.path.exists(filename):
# session.clear()
# return redirect(public.get_admin_path())
# 标记新的会话过期时间
self.check_session()
except:
public.print_log(public.get_error_info())
session.clear()
public.print_error()
public.print_log("except Login has expired, please log in again")
return self.to_login('/login',' Login has expired, please log in again')
def check_session(self):
white_list = ['/favicon.ico', '/system?action=GetNetWork']
if g.uri in white_list:
return
session['session_timeout'] = time.time() + public.get_session_timeout()
# 获取sk
def get_sk(self):
save_path = '/www/server/panel/config/api.json'
if not os.path.exists(save_path):
return public.redirect_to_login()
return public.redirect_to_login(None)
# return self.to_login(public.get_admin_path(), "Login session has expired, please log in again")
try:
api_config = json.loads(public.ReadFile(save_path))
except:
os.remove(save_path)
return public.redirect_to_login()
return public.redirect_to_login(None)
# return self.to_login(public.get_admin_path(), "Login session has expired, please log in again")
if not api_config['open']:
return public.redirect_to_login()
return public.redirect_to_login(None)
# return self.to_login(public.get_admin_path(), "Login session has expired, please log in again")
from BTPanel import get_input
get = get_input()
client_ip = public.GetClientIp()
if not 'client_bind_token' in get:
if not 'request_token' in get or not 'request_time' in get:
return public.redirect_to_login()
return public.redirect_to_login(None)
# return self.to_login(public.get_admin_path(), "Login session has expired, please log in again")
num_key = client_ip + '_api'
if not public.get_error_num(num_key, 20):
return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour')
return public.returnJson(False, '20 consecutive verification failures, prohibited for 1 hour')
if not public.is_api_limit_ip(api_config['limit_addr'], client_ip): # client_ip in api_config['limit_addr']:
if not public.is_api_limit_ip(api_config['limit_addr'],
client_ip): # client_ip in api_config['limit_addr']:
public.set_error_num(num_key)
return public.returnJson(False,'%s[' % public.get_msg_gettext("20 consecutive verification failures, prohibited for 1 hour")+client_ip+']')
return public.returnJson(False, '%s[' % public.get_msg_gettext(
"IP validation failed, your access IP is") + client_ip + ']')
else:
num_key = client_ip + '_app'
if not public.get_error_num(num_key,20):
return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour')
if not public.get_error_num(num_key, 20):
return public.returnJson(False, '20 consecutive verification failures, prohibited for 1 hour')
a_file = '/dev/shm/' + get.client_bind_token
if not public.path_safe_check(get.client_bind_token):
@@ -256,8 +349,8 @@ class panelAdmin(panelSetup):
import panelApi
if not panelApi.panelApi().get_app_find(get.client_bind_token):
public.set_error_num(num_key)
return public.returnJson(False,'Unbound device')
public.writeFile(a_file,'')
return public.returnJson(False, 'Unbound device')
public.writeFile(a_file, '')
if not 'key' in api_config:
public.set_error_num(num_key)
@@ -275,17 +368,23 @@ class panelAdmin(panelSetup):
g.aes_key = api_config['key']
request_token = public.md5(get.request_time + api_config['token'])
if get.request_token == request_token:
public.set_error_num(num_key,True)
public.set_error_num(num_key, True)
return False
public.set_error_num(num_key)
return public.returnJson(False,'Secret key verification failed')
return public.returnJson(False, 'Secret key verification failed')
# 检查系统配置
def checkConfig(self):
if not 'config' in session:
session['config'] = public.M('config').where("id=?", ('1',)).field(
'webserver,sites_path,backup_path,status,mysql_root').find()
if not 'email' in session['config']:
# 4.29 修复config可能是空列表导致赋值不上的问题
if not session['config']:
session['config'] = {}
# if not 'email' in session['config']:
if session['config'] and not 'email' in session['config']:
session['config']['email'] = public.M(
'users').where("id=?", ('1',)).getField('email')
if not 'address' in session:
@@ -310,8 +409,7 @@ class panelAdmin(panelSetup):
session['server_os'] = tmp
return False
def get_osname(self,i_file):
def get_osname(self, i_file):
'''
@name 从指定文件中获取系统名称
@author hwliang<2021-04-07>
+10 -12
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x3
# | aaPanel x3
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import base64
import public,re,os,nginx,apache,json,time,ols
@@ -510,7 +510,8 @@ class config:
reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$"
if not re.match(reg, get.domain): return public.return_msg_gettext(False,'Format of primary domain is incorrect')
if get.address:
if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", get.address):
from public.regexplib import match_ipv4, match_ipv6
if not match_ipv4.match(get.address) and not match_ipv6.match(get.address) :
return public.return_msg_gettext(False, 'Please set the correct Server IP')
oldPort = public.GetHost(True)
if not 'port' in get:
@@ -1307,7 +1308,7 @@ class config:
passwd = get.passwd
if g != "files":
iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})"
rep_domain = "^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$"
rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$"
if not re.search(iprep, ip) and not re.search(rep_domain, ip):
if ip != "localhost":
return public.returnMsg(False, 'Please enter the correct [domain or IP]!')
@@ -2441,7 +2442,7 @@ class config:
if not 'status_code' in get:
return public.return_msg_gettext(False,'Parameter ERROR!')
if re.match("^\d+$", get.status_code):
if re.match(r"^\d+$", get.status_code):
status_code = int(get.status_code)
if status_code != 0:
if status_code < 100 or status_code > 999:
@@ -2878,7 +2879,7 @@ class config:
}
# request发送post请求并指定form_data参数
res = public.httpPost(url, data)
# public.print_log("获取问卷@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ {}".format(res))
try:
res = json.loads(res)
except:
@@ -3162,7 +3163,7 @@ class config:
"USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息
"ERROR_INFO": error, # 错误信息
"PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists("/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间
"TYPE": 1,
"TYPE": 101,
"ERROR_ID": "{}_{}".format(error.split("\n")[0].strip(),get.get("uri", ""))
}
pkey = public.Md5(error_infos["ERROR_INFO"])
@@ -3170,12 +3171,9 @@ class config:
# 提交
if not public.cache_get(pkey):
try:
public.run_thread(public.httpPost("https://api.bt.cn/bt_error/index.php", error_infos))
public.run_thread(public.httpPost("https://geterror.aapanel.com/bt_error/index.php", error_infos))
public.cache_set(pkey, 1, 1800)
except Exception as e:
pass
return public.returnMsg(True, "OK")
+5 -4
View File
@@ -1,13 +1,13 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import public,db,os,time,re, json
from BTPanel import session,cache
from BTPanel import session, cache
class crontab:
field = 'id,name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sName,sBody,sType,urladdress'
field += ",save_local,notice,notice_channel"
@@ -401,6 +401,7 @@ class crontab:
id = get['id']
find = public.M('crontab').where("id=?",(id,)).field('name,echo').find()
if not find: return public.return_msg_gettext(False, 'The specified task does not exist!')
if not self.remove_for_crond(find['echo']): return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!')
cronPath = public.GetConfigValue('setup_path') + '/cron'
sfile = cronPath + '/' + find['echo']
+3 -3
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: <290070744@qq.com>
# Author: <290070744@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
+4 -6
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import sys,os,re,time
if not 'class/' in sys.path:
@@ -223,7 +223,6 @@ class data:
* @return Json page.分页数 , count.总行数 data.取回的数据
'''
def getData(self,get):
import one_key_wp
# # net_flow_type = {
# # "total_flow": "总流量",
# # "7_day_total_flow": "近7天流量",
@@ -262,14 +261,12 @@ class data:
# # get.order = 'id desc'
# # # net_flow_dict["order_type"] = order_type
# # public.writeFile(net_flow_json_file, json.dumps(net_flow_dict))
# 如果网站列表包含 rname 字段排序 先检查表内是否有 rname字段
if hasattr(get, "order") and get.table == 'sites':
if get.order.startswith('rname'):
data = public.M('sites').find()
if 'rname' not in data.keys():
public.M('sites').execute("ALTER TABLE 'sites' ADD 'rname' text DEFAULT ''", ())
table = get.table
data = self.GetSql(get)
SQL = public.M(table)
@@ -331,6 +328,7 @@ class data:
data['data'][i]['attack'] = self.get_analysis(get,data['data'][i])
data['data'][i]['project_type'] = SQL.table('sites').where('id=?',(data['data'][i]['id'])).field('project_type').find()['project_type']
if data['data'][i]['project_type'] == 'WP':
import one_key_wp
data['data'][i]['cache_status'] = one_key_wp.one_key_wp().get_cache_status(data['data'][i]['id'])
if not data['data'][i]['status'] in ['0','1',0,1]:
data['data'][i]['status'] = '1'
+19 -19
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
@@ -418,7 +418,7 @@ ssl-key=/www/server/mysql/mysql-test/std_data/server-key.pem
reg = "ssl-ca=/www.*\n.*\n.*server-key.pem\n"
conf = re.sub(reg, "", conf)
if os.path.exists('/www/server/mysql/mysql-test/std_data/server-cert.pem'):
conf = re.sub('\[mysqld\]', '[mysqld]\nskip_ssl', conf)
conf = re.sub(r'\[mysqld\]', '[mysqld]\nskip_ssl', conf)
public.writeFile(conf_file, conf)
return public.return_msg_gettext(True, 'Setup successfully!')
# create_ssl = None
@@ -429,7 +429,7 @@ ssl-key=/www/server/mysql/mysql-test/std_data/server-key.pem
# if create_ssl:
# self._create_mysql_ssl()
if "ssl-ca" not in conf:
conf = re.sub('\[mysqld\]', '[mysqld]' + ssl_original_path, conf)
conf = re.sub(r'\[mysqld\]', '[mysqld]' + ssl_original_path, conf)
conf = re.sub('skip_ssl\n', '', conf)
public.writeFile(conf_file, conf)
# public.ExecShell('chown mysql.mysql /www/server/data/*.pem')
@@ -792,7 +792,7 @@ SetLink
password = get['password'].strip()
try:
if not password: return public.return_msg_gettext(False, 'Root password cannot be empty')
rep = "^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$"
rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$"
if not re.match(rep, password): return public.return_msg_gettext(False,
'Database password cannot contain special characters!')
self.sid = get.get('sid/d', 0)
@@ -864,7 +864,7 @@ SetLink
db_find = public.M('databases').where('id=?', (id,)).find()
name = db_find['name']
rep = "^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$"
rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$"
if not re.match(rep, newpassword): return public.return_msg_gettext(False,
'Database password cannot contain special characters!')
# 修改MYSQL
@@ -1252,7 +1252,7 @@ SetLink
ps = public.get_msg_gettext('Test Database')
# XSS filter
if not re.match("^[\w+\.-]+$", value[0]): continue
if not re.match(r"^[\w+\.-]+$", value[0]): continue
addTime = time.strftime('%Y-%m-%d %X', time.localtime())
@@ -1317,9 +1317,9 @@ SetLink
public.CheckMyCnf()
myfile = '/etc/my.cnf'
mycnf = public.readFile(myfile)
rep = "datadir\s*=\s*(.+)\n"
rep = "datadir\\s*=\\s*(.+)\n"
data['datadir'] = re.search(rep, mycnf).groups()[0]
rep = "port\s*=\s*([0-9]+)\s*\n"
rep = "port\\s*=\\s*([0-9]+)\\s*\n"
data['port'] = re.search(rep, mycnf).groups()[0]
except:
data['datadir'] = '/www/server/data'
@@ -1338,7 +1338,7 @@ SetLink
'The same as the current storage directory, file cannot be moved!')
public.ExecShell('/etc/init.d/mysqld stop')
public.ExecShell('\cp -arf ' + mysqlInfo['datadir'] + '/* ' + get.datadir + '/')
public.ExecShell(r'\cp -arf ' + mysqlInfo['datadir'] + '/* ' + get.datadir + '/')
public.ExecShell('chown -R mysql.mysql ' + get.datadir)
public.ExecShell('chmod -R 755 ' + get.datadir)
public.ExecShell('rm -f ' + get.datadir + '/*.pid')
@@ -1365,7 +1365,7 @@ SetLink
def SetMySQLPort(self, get):
myfile = '/etc/my.cnf'
mycnf = public.readFile(myfile)
rep = r"port\s*=\s*([0-9]+)\s*\n"
rep = "port\\s*=\\s*([0-9]+)\\s*\n"
mycnf = re.sub(rep, 'port = ' + get.port + '\n', mycnf)
public.writeFile(myfile, mycnf)
public.ExecShell('/etc/init.d/mysqld restart')
@@ -1429,19 +1429,19 @@ SetLink
mysql_cnf = re.sub(r"\nlog-bin", "\n#log-bin", mysql_cnf)
mysql_cnf = re.sub(r"\nbinlog_format", "\n#binlog_format", mysql_cnf)
if not is_off_bin_log:
if re.search("\n#\s*skip-log-bin", mysql_cnf):
mysql_cnf = re.sub("\n#\s*skip-log-bin", "\nskip-log-bin", mysql_cnf)
if re.search("\n#\\s*skip-log-bin", mysql_cnf):
mysql_cnf = re.sub("\n#\\s*skip-log-bin", "\nskip-log-bin", mysql_cnf)
else:
mysql_cnf = re.sub("\n#\s*log-bin", "\nskip-log-bin\n#log-bin", mysql_cnf)
mysql_cnf = re.sub("\n#\\s*log-bin", "\nskip-log-bin\n#log-bin", mysql_cnf)
# public.ExecShell("rm -f {}/mysql-bin.*".format(mysql_data_dir))
else: # 开启 binlog 日志
if re.search("\n#\s*log-bin", mysql_cnf):
mysql_cnf = re.sub("\n#\s*log-bin", "\nlog-bin", mysql_cnf)
if re.search("\n#\\s*log-bin", mysql_cnf):
mysql_cnf = re.sub("\n#\\s*log-bin", "\nlog-bin", mysql_cnf)
else:
mysql_cnf = re.sub("[mysqld]", "[mysqld]\nlog-bin=mysql-bin", mysql_cnf)
if re.search("\n#\s*binlog_format", mysql_cnf):
mysql_cnf = re.sub(r"\n#\s*binlog_format", "\nbinlog_format", mysql_cnf)
if re.search("\n#\\s*binlog_format", mysql_cnf):
mysql_cnf = re.sub("\n#\\s*binlog_format", "\nbinlog_format", mysql_cnf)
else:
mysql_cnf = re.sub("[mysqld]", "[mysqld]\nbinlog_format=mixed", mysql_cnf)
+1 -1
View File
@@ -178,7 +178,7 @@ class databaseBase:
if data_name in checks or len(data_name) < 1:
return public.returnMsg(False,'Database name is invalid!');
reg = "^\w+$"
reg = r"^\w+$"
if not re.match(reg, data_name):
return public.returnMsg(False,'DATABASE_NAME_ERR_T')
+8 -8
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#角色说明:
#read:允许用户读取指定数据库
@@ -105,7 +105,7 @@ class panelMongoDB():
conf = self.get_config(None)
for opt in options:
tmp = re.findall(opt + ":\s+(.+)",conf)
tmp = re.findall(opt + r":\s+(.+)",conf)
if not tmp: continue;
data[opt] = tmp[0]
@@ -177,9 +177,9 @@ class main(databaseBase):
conf = public.readFile(self.__conf_path)
if status:
conf = re.sub('authorization\s*\:\s*disabled','authorization: enabled',conf)
conf = re.sub(r'authorization\s*\:\s*disabled','authorization: enabled',conf)
else:
conf = re.sub('authorization\s*\:\s*enabled','authorization: disabled',conf)
conf = re.sub(r'authorization\s*\:\s*enabled','authorization: disabled',conf)
public.writeFile(self.__conf_path,conf)
self.restart_services()
@@ -201,7 +201,7 @@ class main(databaseBase):
if status:
if hasattr(get,'password'):
password = get['password'].strip()
if not password or not re.search("^[\w@\.]+$", password):
if not password or not re.search(r"^[\w@\.]+$", password):
return public.return_msg_gettext(False, 'Database password cannot be empty or have special characters!')
# if re.search('[\u4e00-\u9fa5]',password):
@@ -670,7 +670,7 @@ class main(databaseBase):
try:
if not newpassword:
return public.returnMsg(False, 'Modify the failure,The database[' + username + ']password cannot be empty.');
if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0:
if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0:
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
if re.search('[\u4e00-\u9fa5]',newpassword):
+12 -11
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hezhihong <bt_ahong@qq.com>
# Author: hezhihong <bt_ahong@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -154,7 +154,7 @@ class main(databaseBase,panelPgsql):
if not self.__soft_path:self.__soft_path='{}/pgsql'.format(public.get_setup_path())
conf = public.readFile('{}/data/postgresql.conf'.format(self.__soft_path))
for opt in options:
tmp = re.findall("\s+" +opt + "\s*=\s*(.+)#",conf)
tmp = re.findall(r"\s+" +opt + r"\s*=\s*(.+)#",conf)
if not tmp: continue;
data[opt] = tmp[0].strip()
if opt == 'listen_addresses':
@@ -223,7 +223,8 @@ class main(databaseBase,panelPgsql):
@return list
'''
check_result = os.system('/www/server/pgsql/bin/psql --version')
if check_result !=0 and not public.M('database_servers').where('db_type=?','pgsql').count():return []
if check_result !=0 and not public.M('database_servers').where('db_type=?','pgsql').count():
return []
return self.GetBaseCloudServer(args)
@@ -302,7 +303,7 @@ class main(databaseBase,panelPgsql):
id = get['id']
find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,accept,ps,addtime,db_type,conn_config,sid,type').find();
if not find: return public.returnMsg(False,'The specified database does not exist.')
name = get['name']
username = find['username']
@@ -323,15 +324,15 @@ class main(databaseBase,panelPgsql):
find = public.M('databases').where("id=?",(id,)).find()
if not find: return public.returnMsg(False,'Database does not exist!')
if not find['password'].strip():
return public.returnMsg(False,'The database password is empty. Set the password first.')
sql_dump = '{}/bin/pg_dump'.format(self.__soft_path)
# return sql_dump
if not os.path.isfile(sql_dump):
return public.returnMsg(False,'Lack of backup tools, please first through the software store PGSQL manager!')
back_path = session['config']['backup_path'] + '/database/pgsql/'
# return back_path
if not os.path.exists(back_path): os.makedirs(back_path)
@@ -358,7 +359,7 @@ class main(databaseBase,panelPgsql):
if os.path.getsize(backupName) < 2048:
return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.')
else:
else:
return public.returnMsg(True, 'BACKUP_SUCCESS')
def DelBackup(self,args):
@@ -397,7 +398,7 @@ class main(databaseBase,panelPgsql):
ext = tmp[len(tmp) -1]
if ext not in exts:
return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT')
sql_dump = '{}/bin/psql'.format(self.__soft_path)
if not os.path.exists(sql_dump):
return public.returnMsg(False,'Lack of recovery tool, please use software management to install PGSQL!')
+8 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
# sqlite模型
@@ -76,7 +76,7 @@ class panelRedisDB():
keys = ["bind","port","timeout","maxclients","databases","requirepass","maxmemory"]
for k in keys:
v = ""
rep = "\n%s\s+(.+)" % k
rep = "\n%s\\s+(.+)" % k
group = re.search(rep,redis_conf)
if not group:
if k == "maxmemory":
@@ -102,6 +102,7 @@ class main(databaseBase):
pass
def GetCloudServer(self,args):
'''
@name 获取远程服务器列表
@@ -109,6 +110,7 @@ class main(databaseBase):
@return list
'''
return self.GetBaseCloudServer(args)
# return public.return_message(0, 0, self.GetBaseCloudServer(args))
def AddCloudServer(self,args):
@@ -373,7 +375,8 @@ class main(databaseBase):
nlist = []
cloud_list = {}
for x in self.GetCloudServer({'type':'redis'}): cloud_list['id-' + str(x['id'])] = x
for x in self.GetCloudServer({'type':'redis'}):
cloud_list['id-' + str(x['id'])] = x
path = session['config']['backup_path'] + '/database/redis/'
if not os.path.exists(path): os.makedirs(path)
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
+11 -8
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -87,6 +87,8 @@ class main(databaseBase):
'''
@添加远程数据库
'''
return self.AddBaseCloudServer(args)
def RemoveCloudServer(self,args):
@@ -107,17 +109,18 @@ class main(databaseBase):
"""
res = self.add_base_database(args)
if not res['status']: return res
if not res['status']:
return res
data_name = res['data_name']
username = res['username']
password = res['data_pwd']
if re.match("^\d+",data_name):
if re.match(r"^\d+",data_name):
return public.returnMsg(False,'SQLServer databases cannot start with numbers!')
reg_count = 0
regs = ['[a-z]','[A-Z]','\W','[0-9]']
regs = ['[a-z]','[A-Z]',r'\W','[0-9]']
for x in regs:
if re.search(x,password): reg_count += 1
@@ -361,7 +364,7 @@ class main(databaseBase):
try:
if not newpassword:
return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.');
if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0:
if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0:
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
except :
return public.returnMsg(False, 'The database password cannot be empty or contain special characters')
@@ -402,7 +405,7 @@ class main(databaseBase):
try:
if not password:
return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.')
if len(re.search("^[\w@\.]+$", password).groups()) > 0:
if len(re.search(r"^[\w@\.]+$", password).groups()) > 0:
return public.returnMsg(False, 'saThe password cannot be empty or have special symbols')
except :
return public.returnMsg(False, 'saThe password cannot be empty or have special symbols')
+2 -2
View File
@@ -1,8 +1,8 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: 1249648969@qq.com
# -------------------------------------------------------------------
+7 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import sqlite3
@@ -390,3 +390,7 @@ class Sql():
except:
pass
def db(self,name):
# 设置数据库名称,用于判断数据库文件
self.__DB_NAME = name
return self
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import re,os,sys,public,json
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import os,sys,public,json,time
class downloadFile:
+16 -16
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2020 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: zhwen <zhw@bt.cn>
# Author: zhwen <zhw@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -24,7 +24,7 @@ class FileExecuteDeny:
def get_file_deny(self,args):
'''
# 添加某个网站禁止运行PHP
author: zhwen<zhw@bt.cn>
author: zhwen<zhw@aapanel.com>
:param args: website 网站名 str
:return:
'''
@@ -51,7 +51,7 @@ class FileExecuteDeny:
deny_name.append(tmp[-1])
result = []
for i in deny_name:
reg = '#BEGIN_DENY_{}\n\s*location\s*\~\*\s*\^(.*)\.\*.*\((.*)\)\$'.format(i.replace("|","\|"))
reg = '#BEGIN_DENY_{}\n\\s*location\\s*\\~\\*\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|"))
re_tmp = re.search(reg,conf)
if re_tmp:
deny_directory = re_tmp.groups()[0]
@@ -73,7 +73,7 @@ class FileExecuteDeny:
deny_name.append(tmp[-1])
result = []
for i in deny_name:
reg = '#BEGIN_DENY_{}\n\s*<Directory\s*\~\s*"(.*)\.\*.*\((.*)\)\$'.format(i.replace("|","\|"))
reg = '#BEGIN_DENY_{}\n\\s*<Directory\\s*\\~\\s*"(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|"))
deny_directory = re.search(reg,conf).groups()[0]
deny_suffix = re.search(reg,conf).groups()[1]
result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix})
@@ -93,7 +93,7 @@ class FileExecuteDeny:
deny_name.append(tmp[-1])
result = []
for i in deny_name:
reg = '#BEGIN_DENY_{}\n\s*rules\s*RewriteRule\s*\^(.*)\.\*.*\((.*)\)\$'.format(i.replace("|","\|"))
reg = '#BEGIN_DENY_{}\n\\s*rules\\s*RewriteRule\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|"))
deny_directory = re.search(reg, conf).groups()[0]
deny_suffix = re.search(reg,conf).groups()[1]
result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix})
@@ -102,7 +102,7 @@ class FileExecuteDeny:
def set_file_deny(self,args):
'''
# 添加某个网站禁止运行PHP
author: zhwen<zhw@bt.cn>
author: zhwen<zhw@aapanel.com>
:param args: website 网站名 str
:param args: deny_name 规则名称 str
:param args: suffix 禁止访问的后续名 str
@@ -145,7 +145,7 @@ class FileExecuteDeny:
if not conf:
return False
if not dir and not suffix:
reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name)
reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name)
conf = re.sub(reg,'',conf)
else:
if dir[0] != '/':dir = '/'+dir
@@ -168,12 +168,12 @@ class FileExecuteDeny:
if not conf:
return False
if not dir and not suffix:
reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name)
reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name)
conf = re.sub(reg,'',conf)
else:
if dir[0] != '/':dir = '/'+dir
if dir[-1] != '/':dir = dir+'/'
new = '''
new = r'''
#BEGIN_DENY_{n}
<Directory ~ "{d}.*\.({s})$">
Order allow,deny
@@ -183,7 +183,7 @@ class FileExecuteDeny:
'''.format(n=name,d=dir,s=suffix)
if '#BEGIN_DENY_{}'.format(name) in conf:
return True
conf = re.sub('#DENY\s*FILES',new+'\n #DENY FILES',conf)
conf = re.sub(r'#DENY\s*FILES',new+'\n #DENY FILES',conf)
public.writeFile(self.ap_website_conf,conf)
return True
@@ -192,17 +192,17 @@ class FileExecuteDeny:
if not conf:
return False
if not dir and not suffix:
reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\s*'.format(n=name)
reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\\s*'.format(n=name)
conf = re.sub(reg,'',conf)
else:
new = '''
new = r'''
#BEGIN_DENY_{n}
rules RewriteRule ^{d}.*\.({s})$ - [F,L]
#END_DENY_{n}
'''.format(n=name,d=dir,s=suffix)
if '#BEGIN_DENY_{}'.format(name) in conf:
return True
conf = re.sub('autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf)
conf = re.sub(r'autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf)
public.writeFile(self.ols_website_conf,conf)
return True
@@ -210,7 +210,7 @@ class FileExecuteDeny:
def del_file_deny(self,args):
'''
# 添加某个网站禁止运行PHP
author: zhwen<zhw@bt.cn>
author: zhwen<zhw@aapanel.com>
:param args: website 网站名 str
:param args: deny_name 规则名称 str
:return:
+223 -48
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env python
#coding:utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
from base64 import b64encode
import sys
@@ -124,7 +124,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
filename = public.get_vhost_path() + '/nginx/' + siteName + '.conf'
if os.path.exists(filename):
conf = public.readFile(filename)
rep = '\s*root\s+(.+);'
rep = r'\s*root\s+(.+);'
tmp1 = re.search(rep, conf)
if tmp1:
path = tmp1.groups()[0]
@@ -132,7 +132,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
filename = public.get_vhost_path() + '/apache/' + siteName + '.conf'
if os.path.exists(filename):
conf = public.readFile(filename)
rep = '\s*DocumentRoot\s*"(.+)"\s*\n'
rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n'
tmp1 = re.search(rep, conf)
if tmp1:
path = tmp1.groups()[0]
@@ -398,9 +398,86 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
for m in ms.keys():
filename = filename.replace(m,ms[m])
return filename
def files_list(self, path, search=None, my_sort='off', reverse=False):
'''
@name 遍历目录,并获取全量文件信息列表
@param path<string> 目录路径
@param search<string> 搜索关键词
@param my_sort<string> 排序字段
@param reverse<bool> 是否降序
@return tuple (int,list)
'''
nlist = []
count = 0
# 文件不存在
if not os.path.exists(path):
return count, nlist
sort_key = -1
if my_sort == 'off': # 不排序
sort_key = -1
elif my_sort == 'name': # 按文件名排序
sort_key = 0
elif my_sort == 'size': # 按文件大小排序
sort_key = 1
elif my_sort == 'mtime': # 按修改时间排序
sort_key = 2
elif my_sort == 'accept': # 按文件权限排序
sort_key = 3
elif my_sort == 'user': # 按文件所有者排序
sort_key = 4
with os.scandir(path) as it:
try:
for entry in it:
# 是否搜索
if search:
if entry.name.lower().find(search) == -1:
continue
# 是否需要获取文件信息
sort_val = 0
if sort_key == 0 or sort_key == -1:
# 通过文件名或不排序时,不获取文件信息
sort_val = 0
else:
try:
fstat = entry.stat()
if sort_key == 1:
sort_val = fstat.st_size
elif sort_key == 2:
sort_val = fstat.st_mtime
elif sort_key == 3:
sort_val = fstat.st_mode
elif sort_key == 4:
sort_val = fstat.st_uid
except:
pass
nlist.append((entry.name, sort_val))
# 计数
count += 1
except:
pass
if sort_key == 0:
# 按文件名排序
nlist = sorted(nlist, key=lambda x: x[0], reverse=reverse)
elif sort_key > 0:
# 按指定字段排序
nlist = sorted(nlist, key=lambda x: x[1], reverse=reverse)
else:
# 否则文件数量小于10000时,按文件名排序
if count < 10000:
nlist = sorted(nlist, key=lambda x: x[0], reverse=reverse)
return count, nlist
# 取文件/目录列表
def GetDir(self, get):
def GetDir(self, get: public.dict_obj):
if not hasattr(get, 'path'):
# return public.returnMsg(False,'错误的参数!')
get.path = public.get_site_path() #'/www/wwwroot'
@@ -425,7 +502,6 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
if not os.path.isdir(get.path):
return public.return_msg_gettext(False,'This is not a directory')
import pwd
dirnames = []
filenames = []
@@ -441,7 +517,16 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
# 实例化分页类
page = page.Page()
info = {}
info['count'] = self.GetFilesCount(get.path, search)
if not hasattr(get, 'reverse'): get.reverse = 'False'
if not hasattr(get, 'sort'): get.sort = 'off'
reverse = bool(get.reverse)
if get.reverse == 'False':
reverse = False
info['count'], _nlist = self.files_list(get.path, search, my_sort=get.sort, reverse=reverse)
# 改1
# info['count'] = self.GetFilesCount(get.path, search)
info['row'] = 500
if 'disk' in get:
if get.disk == 'true': info['row'] = 2000
@@ -472,13 +557,18 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
data['STORE'] = self.get_files_store(None)
data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl')
if not hasattr(get, 'reverse'): get.reverse = 'False'
if not hasattr(get, 'sort'): get.sort = 'name'
reverse = bool(get.reverse)
if get.reverse == 'False':
reverse = False
for file_info in self.__list_dir(get.path, get.sort, reverse):
filename = os.path.join(get.path, file_info[0])
# if info['count'] >= 200 and not os.path.exists('data/max_files_sort.pl'):
# get.reverse = 'False'
# reverse = False
# get.sort = ''
# _nlist = self.__default_list_dir(get.path,page.SHIFT,page.ROW)
# data['SORT'] = 0
# else:
# _nlist = self.__list_dir(get.path, get.sort, reverse)
for file_info in _nlist:
if search:
if file_info[0].lower().find(search) == -1:
continue
@@ -487,19 +577,25 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
break
if i < page.SHIFT:
continue
if not os.path.exists(filename) and not os.path.islink(filename): continue
file_info = self.__format_stat(filename, get.path)
if not file_info: continue
favorite = self.__check_favorite(filename, data['STORE'])
r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str(
file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\
+ self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\
+ favorite+';'+self.__check_share(filename)
if os.path.isdir(filename):
dirnames.append(r_file)
else:
filenames.append(r_file)
n += 1
try:
fname = file_info[0].encode('unicode_escape').decode("unicode_escape")
filename = os.path.join(get.path, fname)
if not os.path.exists(filename) and not os.path.islink(filename): continue
file_info = self.__format_stat(filename, get.path)
if not file_info: continue
favorite = self.__check_favorite(filename, data['STORE'])
r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str(
file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\
+ self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\
+ favorite+';'+self.__check_share(filename)
if os.path.isdir(filename):
dirnames.append(r_file)
else:
filenames.append(r_file)
n += 1
except:
continue
data['DIR'] = dirnames
data['FILES'] = filenames
@@ -536,8 +632,77 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
data['search_history'] = public.get_search_history('files','get_list')
public.set_dir_history('files','GetDirList',data['PATH'])
# 2023-3-6,增加融入企业级防篡改
data = self._check_tamper(data)
data = self._get_bt_sync_status_old(data)
return data
# ———————————————————
# 融合企业级防篡改 |
# ———————————————————
# 防篡改:获取文件是否在保护列表中
def _check_tamper(self, data):
try:
import PluginLoader
except:
return {}
args = public.dict_obj()
args.client_ip = public.GetClientIp()
args.fun = "check_dir_safe"
args.s = "check_dir_safe"
args.file_data = {
"base_path": data['PATH'],
"dirs": [i.split(";", 1)[0] for i in data["DIR"]],
"files": [i.split(";", 1)[0] for i in data["FILES"]]
}
data["tamper_data"] = PluginLoader.plugin_run("tamper_core", "check_dir_safe", args)
return data
# 获取文件同步状态
@staticmethod
def _get_bt_sync_status_old(data):
config_file = "{}/plugin/rsync/config4.json".format(public.get_panel_path())
if not os.path.exists(config_file):
data["bt_sync"] = {}
return data
try:
conf = json.loads(public.readFile(config_file))
except json.JSONDecodeError:
data["bt_sync"] = {}
return data
dirs = [data['PATH'] + "/" + i.split(";", 1)[0] for i in data["DIR"]]
res = [{} for _ in range(len(dirs))]
for idx, d in enumerate(dirs):
for value in conf.get("modules", []):
if value.get("path", "").rstrip("/") == d:
res[idx] = {
"type": "modules",
"name": value.get("name", ""),
"status": value.get("recv_status", True),
"path": d,
}
for idx, d in enumerate(dirs):
for value in conf.get("senders", []):
if value.get("source", "").rstrip("/") == d:
target = value.get("target_list", [{}])[0]
res[idx] = {
"type": "senders",
"name": target.get("name", ""),
"status": target.get("status", True),
"path": d,
}
data["bt_sync"] = res
return data
def get_file_ps(self,filename):
'''
@@ -548,13 +713,17 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
'''
ps_path = public.get_panel_path() + '/data/files_ps'
f_key1 = '/'.join((ps_path,public.md5(filename)))
if os.path.exists(f_key1):
return public.readFile(f_key1)
try:
f_key1 = '/'.join((ps_path,public.md5(filename)))
f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename))))
if os.path.exists(f_key2):
return public.readFile(f_key2)
if os.path.exists(f_key1):
return public.readFile(f_key1)
f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename))))
if os.path.exists(f_key2):
return public.readFile(f_key2)
except:
pass
pss = {
'/www/server/data':'MySQL data storage directory!',
@@ -600,10 +769,17 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
'/usr/local/sbin': 'System script directory',
'/usr/local/bin': 'System script directory'
}
if filename in pss: return "PS:" + pss[filename]
if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list()
if filename + '/' in self.recycle_list: return 'PS: Recycle Bin Directory'
if str(filename).endswith(".bt_split_json"):
return "PS: Split the recovery profile"
if str(filename).endswith(".bt_split"):
return "PS: Split unit file"
if filename in pss: return "PS:" + pss[filename]
try:
if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list()
except:
pass
if filename + '/' in self.recycle_list:'PS: Recycle Bin Directory'
if filename in self.recycle_list: return 'PS: Recycle Bin Directory'
return ''
@@ -930,8 +1106,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.WriteLog('TYPE_FILE', 'Successfully deleted directory [{}]!', (get.path,))
self.remove_file_ps(get)
return public.return_msg_gettext(True, ' Successfully deleted directory!')
except Exception as e:
public.print_log("DeleteDir error info :{}".format(e))
except:
return public.return_msg_gettext(False, 'Failed to delete directory!')
# 删除 空目录
@@ -1256,9 +1431,9 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
public.write_log_gettext('File manager','[{}] renamed to [{}]',(get.sfile,get.dfile))
return public.return_msg_gettext(True,'Successfully renamed!')
else:
public.write_log_gettext('File manager', 'Database moved!',
public.write_log_gettext('File manager', 'File moved!',
(get.sfile, get.dfile))
return public.return_msg_gettext(True, 'Database moved!')
return public.return_msg_gettext(True, 'File moved!')
except:
return public.return_msg_gettext(False, 'Failed to move file!')
@@ -1795,11 +1970,11 @@ session.save_handler = files'''.format(path, sess_path, sess_path)
return self.GetDirSize(get)
# 批量操作
def SetBatchData(self, get):
def SetBatchData(self, get: public.dict_obj):
if sys.version_info[0] == 2:
get.path = get.path.encode('utf-8')
if get.type == '1' or get.type == '2':
session['selected'] = get
session['selected'] = get.get_items()
return public.return_msg_gettext(True, 'Successfully marked, please click Paste All button in the target directory!')
elif get.type == '3':
for key in json.loads(get.data):
@@ -2428,7 +2603,7 @@ cd %s
pdata['password'] = get.password
if len(pdata['password']) < 4 and len(pdata['password']) > 0:
return public.return_msg_gettext(False,'The length of the extracted password cannot be less than 4 digits')
if not re.match('^\w+$',pdata['password']):
if not re.match(r'^\w+$',pdata['password']):
return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers')
if 'ps' in get: pdata['ps'] = get.ps
@@ -2455,7 +2630,7 @@ cd %s
pdata['token'] += "." + exts[-1]
if len(pdata['password']) < 4 and len(pdata['password']) > 0:
return public.return_msg_gettext(False,' Please do not enter the following special characters [ ~ ` / = ]')
if not re.match('^\w+$',pdata['password']) and pdata['password']:
if not re.match(r'^\w+$',pdata['password']) and pdata['password']:
return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers')
#更新 or 插入
token = public.M(my_table).where('filename=?',(get.filename,)).getField('token')
@@ -3254,7 +3429,7 @@ CREATE TABLE index_tb(
def restore_website(self,args):
"""
@name 恢复站点文件
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@parma file_name 备份得文件名
@parma site_id 网站id
"""
@@ -3265,8 +3440,8 @@ CREATE TABLE index_tb(
def get_progress(self,args):
"""
@name 获取进度日志
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
"""
import panel_restore
pr=panel_restore.panel_restore()
return pr.get_progress(args)
return pr.get_progress(args)
+46 -46
View File
@@ -1,46 +1,46 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 上传文件至oss
#------------------------------
from filesModel.base import filesBase
import public
class main(filesBase):
def __init__(self):
pass
def get_oss_objects(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_all_objects(get)
def download_file(self,get):
"""
@name 下载文件
@param get
file:文件路径
"""
info = self.get_soft_find(get.name)
if not info['setup']:
return public.returnMsg(False,'未安装[{}]插件'.format(info['title']))
import panelTask
task_obj = panelTask.bt_task()
task_obj.create_task('下载文件', 1, get.url, get.path + '/' + get.filename)
public.set_module_logs('files_down_to_file', 'download_file', 1)
public.WriteLog('TYPE_FILE', '从 [{}] 下载文件 [{}] 到 {}'.format(info['title'],get.filename,get.path))
return public.returnMsg(True, 'FILE_DOANLOAD')
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
kEPo6lgxLBQavG1DxRrlnEK5KohxBShsQIeNCGHkEP4=
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
XIfdJ79nObMM+vyAmKbTmw==
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
9bJD7OGVYIbkQwcqio3pMkEQ5Tepk1KAKJBdik81BOHNSVFwrMdl2gFGwZvvpYDQ
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3
Z8UsPk1Q7HtwjRd4g01ryw==
CAhn8dRUItEbEErp4w+lX9Z2hX2+feDN0U2wxQk1jf6XkRmrNeGBO3gU/TXqpTeF
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
vHI6ym+jnn06PgL99476zxO+uTTrKbsFCbogJPwJML2TG1a7NmZkzv05uK3L/Tc8
Z8UsPk1Q7HtwjRd4g01ryw==
n3tXewq2IYqiwFREjJjsxZJ3F7EUu5p3TiQaoXfFThA=
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
PRG/YRzXVD8iVR3bzEcUnsWMvR86r5liyWslQeT39Bs=
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
AN33R+yjfqqAkAsFnmP/r/WGRgGkDEv8Fnc8kjQsJ7fRnV1BKZAu/huNYnIc6I01
cbvASHjpysrsjdY5RctXmG9wund6qoaXAfnCgO+o1nY=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXHKWKUVVa/rOrR3+fgEd7tp63QHMzA34qukyTQgDFJrLVYVJL/Ye3kNmAIiExi//aTWiBsWXWTJE2/bLQwR3ie
1u+XjG/2+GSQRv6EzCaWRQ==
q4nc/jwATOMUyfSjLibfEelnou8pLYpfebj3/LvnkGg=
7fBvPEAf72Uv1YEDXbbQ3CN3MWrD+3vkr2VJCCzzEPRhwccGf00/aNA1BGgdy8WH
7fBvPEAf72Uv1YEDXbbQ3JX8fOX6IsaFNTXjLCv8fl9nBsjACyGde1V2jqcLtcHMgHmQHXLhOkH7ECCy4Qan/Eap/d5B8ETngKGRLc1dnrFJxLrR59XTXqj5a3KAPh6f
Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8XJaZJ50IVd+4cfwZkMesXHuMlT+97TT2qjLWtQKOwIMYvrQOoIlZ+noM34BKXXbE=
2+RceSNjD2iq0sRzepMyse3K/6KDWrRQWf3bA9DlS0ntu9Qw70LcA2SU6TO7qbj7+BZ00tLn7oRDAJhOp7GBQJ9AdWUuTKC0qj4H0sUep4AFfY3QegcrH7wS+cAOaYcaYNB7YC/AjQj4DFsev5mqOa3aXW4VcIMS3jP/SRer5vo=
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOHo1JRV4mvyfjFAJfZA0Y7fzjR8HN7n40vj/TKQkGxagQ==
1u+XjG/2+GSQRv6EzCaWRQ==
+342 -342
View File
@@ -1,342 +1,342 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import tarfile,shutil,gzip
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
pass
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not tarfile.is_tarfile(sfile):
return public.returnMsg(False,'Not a valid tar.gz archive file')
zip_file = tarfile.open(sfile)
data = {}
for item in zip_file.getmembers():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):]:
sub_data[d] = {
'file_size': item.size,
'filename':d,
'fullpath':f_name,
'date_time': public.format_date(times=item.mtime),
'is_dir': 0
}
if item.isdir():
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
sub_data = sub_data[d]
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(sfile + filename))
result = {}
result['status'] = True
result['data'] = ''
with tarfile.open(sfile,'r') as zip_file:
try:
zip_file.extract(filename,tmp_path)
result['data'] = public.readFile('{}/{}'.format(tmp_path,filename))
except:pass
try:
public.rmdir(tmp_path)
except:pass
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表,数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
if not tarfile.is_tarfile(sfile):
return public.returnMsg(False,'Not a valid tar.gz archive file')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
#组装原有的文件
s_list = []
src_list = {}
public.get_file_list(tmp_path,s_list)
for f in s_list:
if not os.path.isfile(f): continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in filenames:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed delete file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
public.writeFile('{}/{}'.format(tmp_path,filename),data)
#组装原有的文件
s_list = []
src_list = {}
public.get_file_list(tmp_path,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in src_list:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed modify file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表,数组格式
"""
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with tarfile.open(sfile) as zip_file:
try:
m_list = {}
f_infos = zip_file.getmembers()
for item in f_infos:
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.isdir():
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path, filename.strip('/'))}
if dir_key in m_list:
info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:]))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path, 384)
zip_file.extract(filename.strip('/'),tmp_path)
for key in m_list:
try:
for info in m_list[key]:
if os.getenv('BT_PANEL'):
shutil.copyfile(info['src'],info['dst'])
else:
shutil.copyfile('/' + info['src'],'/' + info['dst'])
except:
pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'File was decompressed successfully')
def __unzip_tmp_path(self,sfile):
'''
@name 获取临时解压路径
@param sfile 压缩包路径
@return str
'''
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
with tarfile.open(sfile) as zip_file:
try:
zip_file.extractall(tmp_path)
except: return False
return tmp_path
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
tmp_path = self.__unzip_tmp_path(sfile)
if not tmp_path: return public.returnMsg(False,'Failed edit!')
#组装新添加的文件
src_list = {}
for fname in f_list:
if os.path.isdir(fname):
s_list = []
public.get_file_list(fname,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,'')).replace('//','/')
src_list[src_file] = f
else:
src_file = '{}/{}'.format(r_path, os.path.basename(fname)).replace('//','/')
src_list[src_file] = fname
#组装原有的文件
s_list = []
public.get_file_list(tmp_path,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = f.replace(tmp_path,'').strip('/')
if src_file in src_list:
continue
src_list[src_file] = f
with tarfile.open(sfile,'w') as new_zfile:
try:
for src_file in src_list:
new_zfile.add(src_list[src_file],src_file)
except:
shutil.rmtree(tmp_path, True)
return public.returnMsg(False,'Failed add file,error:' + public.get_error_info())
shutil.rmtree(tmp_path, True)
return public.returnMsg(True,'Compressed package file modified successfully')
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.name
try:
filename = item.name.encode('cp437').decode('gbk')
except:pass
if item.isdir():
filename += '/'
return filename
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
Hf+bBTgRv9pVfall8ODpvg==
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
1u+XjG/2+GSQRv6EzCaWRQ==
K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU=
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM=
y9P3+4KXYacR8m2lL04bxyxkWpCOoKKwyE9jOO9xzDU=
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q==
KZTmaJLp+FU9X93j5Tpqtw==
0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR
yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf
akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
sV2BMz3qoSZgP7XLsV0u8Ey5TTZbfTeRXmE8Lnkq8ecwI8LY+wsKkeHx5JQmHjbJ
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWW4zkUYYb5UAUX8zS+IvdqmDjQdLy6c68fWPpLBj0d0GY6NLmGX6bobgiWRy9bUsk=
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnH39LdUi6HdEZyAx9T9sPSkCOt9wcLgOersS/HmOi2Xel
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
ei/1A+2pKZSasjYF/t4qooATwcryDpeJQCtosnRGcgHSsp9sawkQJUO/Q/xMUnXL
1u+XjG/2+GSQRv6EzCaWRQ==
4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg=
ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw==
1u+XjG/2+GSQRv6EzCaWRQ==
g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR
wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I=
VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu
VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf
VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL81I98txu56TEAiJ0hswK9
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8lOmX2nmjCcfYXY14ohcOqo/JjN+Uf5m+lGfErLLeSsw==
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3
VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ
VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdi0Te13nq3FDKoUwPWRFVhcbFGXs/hncVFmPVZNUBUZY=
VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3
VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLtxlUb0X9CVsj27gKH7yxkc
VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ==
VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k=
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo
VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW
1u+XjG/2+GSQRv6EzCaWRQ==
+plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEAKAgQMZx8+NhDilcUt2veW2ndED3g1z4Otqk5QlUsRCjvCITtLgXlADwlPZpuWSgD
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI
YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54=
D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYRo9qF9iYlR/oIixOO4YW9r5sQV57F10J/zF5jBaEFdKw==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6+8scbwlFXmHwUL6N0BWPj6dlHHDEIlkodgo1A0usjGyHhQdtr/CZJG5zDxIr66oew==
VmVrGQo2zRokW/ZuO9bN65rMxzVrOInziG0YF1uvpqBp8ZS5jWaYG3vnBxPtXcWWa9TIx0uiiZmKhmv6T3Z+yciaFXeGtzFwR28zuXWWSf6Fvx4Mh4aXTcAi9R5RGv8f
M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc=
b4OJVZe8QyIpjuTpKXDL9A==
xWoGNWjKGPfI4gq8aHoTfL6V0pkYKbeBvdfg5x1FYR8uFPtJ2x9MfdlNdGBKn+Bi
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig
KZTmaJLp+FU9X93j5Tpqtw==
dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
1u+XjG/2+GSQRv6EzCaWRQ==
sV2BMz3qoSZgP7XLsV0u8Ey5TTZbfTeRXmE8Lnkq8ecwI8LY+wsKkeHx5JQmHjbJ
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWW4zkUYYb5UAUX8zS+IvdqmDjQdLy6c68fWPpLBj0d0GY6NLmGX6bobgiWRy9bUsk=
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg==
sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds=
1u+XjG/2+GSQRv6EzCaWRQ==
/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU=
gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms=
HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU=
b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v
QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI=
1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmfmpa/q0AQoQFAFtjS1Nskr
JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA==
J1UGlXkKhKXD36OrXXhN8J2xIJzaLSbkMLWZBVYhIzKIhtDAsZ6oAssWKRx5BzNP
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO
1u+XjG/2+GSQRv6EzCaWRQ==
D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ
VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A==
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQCRjmbMCS45yRDKakJA+6lygvtX8/AEkLox/HgNQ0WReKAZvStGcdGPMqn6vgZv5Gu5msyaDAcqtS9MyGyGb9+g==
1u+XjG/2+GSQRv6EzCaWRQ==
oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK
KZTmaJLp+FU9X93j5Tpqtw==
t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8=
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg==
sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds=
IhuisKim47k91RVt8z8qtAZ7rSCYljkiJH9HzGQ5OPx8FxzBv8dhM3FQlWygMwgCxPuj/L0zzaJH7M9JLtcNyBQAqW+52jDxmeGRDtqtSp8=
1u+XjG/2+GSQRv6EzCaWRQ==
/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU=
gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms=
HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU=
b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v
QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI=
O3CUgrw2GJfB+mDjH5+NdkjK1xGXzP/MBLULODELp6gwPy9qqsLvy+O0nI8WaT4C
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA==
J1UGlXkKhKXD36OrXXhN8JAqE/N8+U09yjtfA8xA8yW9SYS4/U88+D8eMNmNqLL6
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO
1u+XjG/2+GSQRv6EzCaWRQ==
D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ
VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A==
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQBD0QaFQ6mH2ykhpeatV5MFSAvHLSQE3mXQCKlo9Vmyj7/c2aTE5OzSuRlIiCt76fbCvV4M9DgyzOc6QS8FnjVg==
1u+XjG/2+GSQRv6EzCaWRQ==
oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS
Z8UsPk1Q7HtwjRd4g01ryw==
VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw==
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
Z8UsPk1Q7HtwjRd4g01ryw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr
NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEAKAgQMZx8+NhDilcUt2veWwUPocpK+8JugxVoHXyHBSRb28kLWdpmZTTf3StM9Y2XDpZ02OI3BeuyFvxLweVX9w==
wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG
NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+
1u+XjG/2+GSQRv6EzCaWRQ==
D1uzgsuXIKL/yxNsPwFM0bn1b8lc4rkM7jKkoTPs67jZgYEdG3TOZllO4+vZp0ZM
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN67AsrXUPIcMWmsNJ6skMzcKKCpn1+SzebzO6hgphZ4RaLaD/MDC8FwcBZZJFOSWJmA==
VmVrGQo2zRokW/ZuO9bN66NLXvUSFhBjtRrUbZsPQNOrrRiOaX/5u3wb0Rfqy+pv
VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe
VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLtxlUb0X9CVsj27gKH7yxkc
VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap
VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU=
VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA==
VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+KvhObTvNhFYenGcSOQ2IfJ/VBsqInH+jm5934kDPyo5Q==
VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuTb4tOz+zOmUJvBAa9I7hKop6pGwEoB2371+83RgakZhyjMJWosIoeLReEb5GcqHNA==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkrikeB3PyYnRyHWKvzXceKTTsB9Sbgf9uM/v0isHHFakzQxpTSrdsYuODmqyQGE3Q=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknz2So+gFu+BijKlvPtsNKCIi0RkX1BFpd3GhHKBMdoB6DedipwufkrnDIRLB493gRtMiXCiHSsTBNmIXqRl2RK
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ==
VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6yaURb+J41vJInDMdNBqADJBPzFwU1DWEhzV7Igw3NGvcy7cQ4IWtwYwzl4xxnS4q7aao1GGQ/lpjefMWmVC5io=
VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfaLzRNSm7AkFFrFp+7ESwXtG1lg7SuOb/Fvk+nDWM/LTeUYMfTnzndr1z2COlogy5E3igzgZFBhcAjJ6avABsS
VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrD2Em+ygcQFnOdLaXNs8ffOLcZZMjCFzoLC6LOvXRJel9lRMBQd5MAQelNetUjr7a0=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD
VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg=
VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg==
VmVrGQo2zRokW/ZuO9bN65qgzI0QqVLALq/Lz0RhHgVj4W6v2iAhy3bpNtZbOHJ8qR+FfOTDkpmrnn3Bg7loWg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfko6jVO83Qfz+7nZDbM61ZOu8HuZ+ZNu6hMvpayaQR5SAc=
VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfkosj3Q/Uyc4ih1OKYxi2Aoat/XPMKk6ZFw9dS97mdyNCW7sbOTUzQ7vKis/hh/FWBl
VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E=
VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0=
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOF2h+KCvqR3zTaYTe9GwY3cxsP+/BBmVOckz/EpSQTZmvBKIrz2PIwNuNhzK8OCOI4=
1u+XjG/2+GSQRv6EzCaWRQ==
nfBQrFU5Tl0d1VcHhIJkAP9Tx9PwP8cRBjb6FOkkDsaLG6R4uDQDi4xcdtmOXEqB
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P3euInP55YyecjBWhnEnlB7Lw5dMNDV3iHSK0kwD9rha
yqM179SzW9HnBHozkU+IlV+BVKi+rIq6p9thxVddK2wK0J7yGt1J4fq2cywdhxRL
akHibliG6j9uvBEg96vRemf0tAA5Rm6aB+lqKw8WFi0=
KZTmaJLp+FU9X93j5Tpqtw==
IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw==
D1uzgsuXIKL/yxNsPwFM0bn1b8lc4rkM7jKkoTPs67jZgYEdG3TOZllO4+vZp0ZM
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6+8scbwlFXmHwUL6N0BWPj5rk9N0kB/j2zz5GqVVZxsc
M0ZySqkmhuHCw6olbCKv9xWw7HeiAhxWgwzOj+uORvYUGO2Wun1zPlqkYizDiyWM
1u+XjG/2+GSQRv6EzCaWRQ==
00qsNOHBbp9Q+MTSOB/jV1gEYraNSaFlJL1NYP7mKBg=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W
KZTmaJLp+FU9X93j5Tpqtw==
H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn
c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U=
Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg==
sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds=
1u+XjG/2+GSQRv6EzCaWRQ==
OKNarVhdXTeaHF7smpo1Fsg5Z6GS4ctUP7SwO9m7ADsrK59qTlLvB8EsnHNRomb5
HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU=
mZ9rrP32sP7RJKYMPGILD+78ba2l0mR6sa16eW97WcI=
O3CUgrw2GJfB+mDjH5+NdlB3aQOXnKkk0z1WAt9Jmo/evqziM8DhkT0YrYGb4COh
VmVrGQo2zRokW/ZuO9bN67lx9HSk8eNN2hBCO4HpKRk=
VmVrGQo2zRokW/ZuO9bN69ZjyuXvRyGNy9kZJ0BSUPrXcl5zPRPxpUUNlqBFMfTepJuwObNjyv5s1Bwxw+0zHA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6yiPlSW1IkAq8PnO6zy+iP3jJCfd1bhtOH5rekZFEjXi
VmVrGQo2zRokW/ZuO9bN6yIV96dETdHvOTzc1eoxsu7+haDOTWWWdxKhfAdfjn09
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
VmVrGQo2zRokW/ZuO9bN60+BaOrNhyGitXORdqvsMmwFtJw9x+0nfzpYbP/xMELoydQeOelN/AlsLE8govEqlMQrfUl1PeNk2rX8fh0ZElrak4Q4jLc+GGiQ0DLMMpnH/1J5GKH49RP7rJbs5dxgJ4tOonH1clyp6d9GTs2dKrA=
VmVrGQo2zRokW/ZuO9bN60jWe2OHiR8czCXypGRfxf3KT4lPFL0wUA7wcV6fbrUq
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN62YWnBw/cibruWabHzPuqaAcn7VyIMN4YMuZ3S9CW09ENvrO91BA4ldYfyyQpiR6Cy5R4Fei8M2XzZTUjvQbS6iXg3tLmWav6AtitMV2FQP4
VmVrGQo2zRokW/ZuO9bN608vqClPKbLpxLXcA30riX215gq1D0BrebU6pLhAhTm2
1u+XjG/2+GSQRv6EzCaWRQ==
/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU=
gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms=
b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v
QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI=
O3CUgrw2GJfB+mDjH5+NdkjK1xGXzP/MBLULODELp6gwPy9qqsLvy+O0nI8WaT4C
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA==
J1UGlXkKhKXD36OrXXhN8JAqE/N8+U09yjtfA8xA8yW9SYS4/U88+D8eMNmNqLL6
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO
1u+XjG/2+GSQRv6EzCaWRQ==
D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ
VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A==
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQIMBV8oz+JZw1lVuk9zsKubda9eiR1++L545YBfEjatGypdQPztU2DAF6OXEg3W2a3ltwtcgo9+IKhfYuqJESBw==
1u+XjG/2+GSQRv6EzCaWRQ==
oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX
LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR
akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A=
KZTmaJLp+FU9X93j5Tpqtw==
BIa0ibHnqUboaK9kYHYO4k/Xks2d0kb8EqwPCudb2sE=
b4OJVZe8QyIpjuTpKXDL9A==
PRG/YRzXVD8iVR3bzEcUnu1TFK0huFhoat7c0/vfEZdQ5ejFvZhOUgdVFA1LA3t54/DjYCmHvkQ18q4IM7keqQ==
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
6XDh38s4HWrzA9ChHtrpYV0DWWNc3chcp0di+Spm+pg=
PRG/YRzXVD8iVR3bzEcUnpfl+XcdHX8i0iNThoY4QPk=
5wsVwKLrIjIWqeTNpSVp9SZAE79lgehXxNsrtfaN8Wk=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
+289 -289
View File
@@ -1,289 +1,289 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,files,json,time
from BTPanel import cache
class main(filesBase):
__objs = ['bos']
def __init__(self):
pass
def get_logs_info(self,get):
"""
@查看日志
@param get
limit:每页显示条数
file:日志文件
"""
p = 1
limit = 200
search = None
file = get.file
if 'limit' in get: limit = int(get.limit)
if 'p' in get: limit = int(get.p)
if 'search' in get: search = get.search
if not os.path.exists(file):
return public.returnMsg(False,'Please specify file!')
res = {}
res['status'] = True
res['data'] = self.GetNumLines(file,limit,p,search)
res['md5'] = public.md5(res['data'])
res['limit'] = limit
if not cache.get(file+'_logs_info'):
public.set_module_logs('files_get_logs_info','get_logs_info')
cache.set(file+'_logs_info','1',86400)
return res
def set_log_split(self,get):
"""
@name 文件切割
@param filename 文件路径
@param stype 切割类型 day:按天切割 size:按大小切割
@param size 切割大小(stype=size必传)
"""
filename = get.filename
stype = get.stype
limit = int(get.limit)
if not stype in ['day','size']:
return public.returnMsg(False,'Cut type passing error.')
if not os.path.exists(filename):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if limit < 3:
return public.returnMsg(False,'The number of reserved copies cannot be less than 3.')
data = {'type':stype,'limit':limit,'addtime':int(time.time())}
if stype == 'size':
size = int(get.size)
if size < 1024:
return public.returnMsg(False,'Cut size cannot be empty.')
data['size'] = size
public.set_split_logs(filename,1,data)
return public.returnMsg(True,'successfully set.')
def get_log_split(self,get):
"""
@name 获取文件切割信息
@param filename 文件路径
"""
data = {}
sfile = '{}/data/cutting_log.json'.format(public.get_panel_path())
if os.path.exists(sfile):
try:
data = json.loads(public.readFile(sfile))
except:pass
return data
def get_file_ext(self,filename):
"""
@name 获取文件扩展名
@param filename
"""
ss_exts = ['.tar.gz','.tar.bz2','.tar.bz']
for s in ss_exts:
e_len = len(s)
f_len = len(filename)
if f_len < e_len: continue
if filename[-e_len:] == s:
return filename[:-e_len] ,s
if filename.find('.') == -1: return filename,''
return os.path.splitext(filename)
def copy_file_to(self, get):
"""
@name 创建文件副本
@param get
@return
"""
sfile = get.sfile
if not os.path.exists(sfile):
return public.returnMsg(False, 'FILE_NOT_EXISTS')
spath,ext = sfile,''
if os.path.isfile(get.sfile):
spath,ext = self.get_file_ext(sfile)
# public.print_log(spath)
for x in range(1,1000):
dfile = '{} - copy ({}){}'.format(spath,x,ext)
if not os.path.exists(dfile):
break
get.dfile = dfile
f_obj = files.files()
if os.path.isdir(get.sfile):
public.WriteLog("File manager","Create copy of the directory [{}]".format(sfile))
return f_obj.CopyDir(get)
import shutil
try:
shutil.copyfile(get.sfile, get.dfile)
public.WriteLog('TYPE_FILE', 'FILE_COPY_SUCCESS',
(get.sfile, get.dfile))
try:
stat = os.stat(get.sfile)
os.chmod(get.dfile,stat.st_mode)
os.chown(get.dfile, stat.st_uid, stat.st_gid)
except:pass
public.WriteLog("File manager","Create copy of the file[{}]".format(sfile))
return public.returnMsg(True, 'FILE_COPY_SUCCESS')
except:
return public.returnMsg(False, 'FILE_COPY_ERR')
def set_topping_status(self,get):
"""
@name 设置文件或目录置顶
@param get
file:文件路径
type:置顶类型
"""
sfile = get.sfile
status = int(get.status)
if not os.path.exists(sfile):
import html
sfile = html.unescape(sfile)
if not os.path.exists(sfile):
return public.returnMsg(False, 'File or directory does not exist.')
data = {}
conf_file = '{}/data/toping.json'.format(public.get_panel_path())
try :
if os.path.exists(conf_file):
data = json.loads(public.readFile(conf_file))
except:pass
if sfile in data: del data[sfile]
if status:
data[sfile] = status
public.writeFile(conf_file, json.dumps(data))
public.set_module_logs('files_set_topping_status','set_topping_status')
public.WriteLog("File manager","Modify [{}] top status".format(sfile))
return public.returnMsg(True, 'Successful set.')
def GetNumLines(self,path, num, p=1,search = None):
"""
@name 取文件指定尾行数
@param path 文件路径
@param num 取尾行数
@param p 当前页
@param search 搜索关键字
@return list
"""
pyVersion = sys.version_info[0]
max_len = 1024 * 128
try:
from html import escape
if not os.path.exists(path): return ""
start_line = (p - 1) * num
count = start_line + num
fp = open(path, 'rb')
buf = ""
fp.seek(-1, 2)
if fp.read(1) == "\n": fp.seek(-1, 2)
data = []
total_len = 0
b = True
n = 0
for i in range(count):
while True:
newline_pos = str.rfind(str(buf), "\n")
pos = fp.tell()
if newline_pos != -1:
if n >= start_line:
line = buf[newline_pos + 1:]
is_res = True
if search:
is_res = False
if line.find(search) >= 0 or re.search(search,line):
is_res = True
if is_res:
line_len = len(line)
total_len += line_len
sp_len = total_len - max_len
if sp_len > 0:
line = line[sp_len:]
try:
data.insert(0, escape(line))
except:
pass
buf = buf[:newline_pos]
n += 1
break
else:
if pos == 0:
b = False
break
to_read = min(4096, pos)
fp.seek(-to_read, 1)
t_buf = fp.read(to_read)
if pyVersion == 3:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8',errors='ignore')
except:
try:
if type(t_buf) == bytes: t_buf = t_buf.decode('gbk',errors='ignore')
except:
t_buf = str(t_buf)
buf = t_buf + buf
fp.seek(-to_read, 1)
if pos - to_read == 0:
buf = "\n" + buf
if total_len >= max_len: break
if not b: break
fp.close()
result = "\n".join(data)
if not result: raise Exception('null')
except:
result = ''
if len(result) > max_len:
result = result[-max_len:]
try:
try:
result = json.dumps(result)
return json.loads(result).strip()
except:
if pyVersion == 2:
result = result.decode('utf8', errors='ignore')
else:
result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore")
return result.strip()
except:
return ""
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
Hf+bBTgRv9pVfall8ODpvg==
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU=
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
9ewEGhR02rX1CYy9TbJShdc5zOrGhv/95JJVBawwTJI=
1u+XjG/2+GSQRv6EzCaWRQ==
QtywOSt2jU7XEVQfd46hXhlGuDAcY6mFXPkzqinx4hA=
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
P0xFZRxBgzs6EB2PV1P1Ga8KWkeEOjCa2HwBgtCLnkI=
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
/tIKPI7pxbgiB5DGE+JcX8inA0z7UW6nw0fpA/nuJtJ30NtW1IQQsUdAjwgJ+5zC
Z8UsPk1Q7HtwjRd4g01ryw==
0fJLHIpaUEUEURheAjqV+C5PbuPsfeNtAPgfcFiqP5w=
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
zxNKIz/Dwj9Harw3dUNEQJhfwzP9e0g+QEHF20IF9QloJJn499jB5lN74CMN7xKF
PRG/YRzXVD8iVR3bzEcUnoM4TQf6qaKLuCt8gJ8NfkQ=
Z8UsPk1Q7HtwjRd4g01ryw==
y+v+I4rQ8oQ+Y894tAwqPQ==
p6i8iK7HDbvmLpoFrhNyv2ns9UjKsYvBLT40sf6Ap28=
Nxw10FAZUk+m0nadC1hdyy162NCelWBcYGf8TIFoMWw=
gw7rFE+f9bVF+DtSprQxBWIvcXcHzAaIYdK9TCMnZFs=
VIHA31zdnuwVlvUv3VOlYVRNGNAMqDxg7g3P55Yig7XGJ0frgqBbhflLu9pZDsGtG7PWJFzYnbAhv0HasJD0eg==
mcQwVxjUw79rwGN6NopbQ6kcPmfht+aHpy4N4vzTb9DbhhPn/FiUW/rz0u7pkKGy
X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw==
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJjwdSnpWE4PzbAMHxIUoYAb
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUZFcA4o3wUnGm4YjdorXMeGmKOMmT0MYEr6v6Dtj7fXTQgHn4plKdmzjTMeVlEm5A=
1u+XjG/2+GSQRv6EzCaWRQ==
QeR/vJltSrUa36sdgLk/IfL4q9jp7Kh5fPIA7nHg0hc=
qkpuPeWx54kZb1C5WIa62tKtrwMAUE7xRV1hPFjc0nk=
NUDHKeYglPKQjXCsTjqHkDpPG4Ff4llamNWSKelBxqKmBtoeDOPkBptxNl9ae6xEQqyUrJT9AnSRTpSh7aXAbg==
1u+XjG/2+GSQRv6EzCaWRQ==
ezgwiw1QpepwAFNSsnvQoOJN6u8DqbLWz8qgHsO6aqB6RHHe5HToLp0+UqRpP8fb
z+VhFG0r6WY/HBghnJVPENgMUP6uURPQG10QAuXe9q8=
1u+XjG/2+GSQRv6EzCaWRQ==
o3kxTO4RwuXO7GZ9wpXG+Wq35U1WRWDZvf7dXlWNmacOOTCJLfQ4KEfT1r5nSH7h
xWoGNWjKGPfI4gq8aHoTfEof00xZubKgl7GPVFGSDVd7TSf0tyOZVdADGcIojOkDe5MWF/mzQs4D73pMqY5dZvgzuA9ObZjdKCQqlGE/ZFc=
7jSw4GB3eBRbmC1MzdtYH2nxYtjMgz8klBoQVhVoidWwT8r1LlO9NvZUaeVEEEdTW81nQdiNwJibED1dKYQBkw==
ruTTNFTdswRs4Mc+srnRkxKrfMU4HqZxGxt1RxHe024=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
0J28z5Afxj/RLIBl6j3pAgNq/oTIsu+AgqPjVKJEPvv9NLcDpzlB17tpU0lSBAVA
Z8UsPk1Q7HtwjRd4g01ryw==
HoOSkwUjMH/v4OzYfSLZHLuEmVzOc0Jcac6Q7EBdZLI=
wOl7lRII4ZgqRTj9M//0b0Hmiipql0KqOySDsulvH0Su8cx+3vVrHwSELz95Nt4O
yqM179SzW9HnBHozkU+IlZmT/v3wYMBi43GV3/6zTQaofEdG+x7npWVs++h4r59tCne/rOxDxzdL+ulBEDhdXzPWQJHGam+A14zKSyFK8tY=
yqM179SzW9HnBHozkU+IlSh6plWR6D4Za/srbzCBndvZ2MLXINNMDiNewLwvNACvbuwcvh5NzzpKTCtz6o++Uw==
Z8UsPk1Q7HtwjRd4g01ryw==
BIa0ibHnqUboaK9kYHYO4tix8ZPIpY4+eXNZQL6TNBEr37K1gp2hFIgZaVuDpKry
wUvHMMqfJEAtxgmf7WcojEsJZ/q+trcw4QZczEaX1wc=
p6i8iK7HDbvmLpoFrhNyv+jCXQ+9uuqm20drf0JQzoo=
uTEK8Ng11d3ix2pA+DD/aUSvw2QhKfq2muw9OeWLUPkFXMoY8H1+WYM2ZgNetwFM
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVoIIr4UQdxImXSR4+nP5rPMN4durEeX4gdmJz+ASa5AAk5l7XFK6IcVVpMUMCMJmI=
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJh6sAOIfkvF5oIqf+7bT65s
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
ksF2R1E+pQ3tpguSadwptSjHYbVoil2NE4uVOms8YOU=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWjtdfz4jUHOisd7iLe9SzEDuNihpqofFUHBGmLmre0CFX801z657OYko3yu9pDZ2cMrn7XCoaQHi8TBS6BfnnXRgmoO+AsowNhXyROWGVmDQ==
1u+XjG/2+GSQRv6EzCaWRQ==
JbnXIK/axHVmA2plDNFCpMWEt1OR4iOGZbYxwMVSGXaSRClwJ7H8/1TEbkxkYcSscQPjgl6VzrZ1rA8bbkzLPGLtGrShGu4zlnLbt+BiXXQ=
j9BWZnPkz9pNgv1SXK/8DQ0oIRVkw0dZu+9bbZ9Z2fk=
N5/4MVITy7EOJTlZiwcB9agKfbGr368xpyc0rhumQZEuHxhitKliD0GwkptReLUf
J1UGlXkKhKXD36OrXXhN8NU53NrHG2SKWIiwAUeD9JY=
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrQorUkV/0Z4czhLJwFbLtQgDiot4dt7MCR+IyRl/JXPGwvD97k+zv3alg0zn9SqxNU=
NhnIR3Ilo4H2su9/cTNo/Nhao8jRz1mpKHWzTHDhdaWoB5q4jnCTxvFKzgSknYQc
1u+XjG/2+GSQRv6EzCaWRQ==
Ic1WBp01nLyRxbqJnsu2p0K5AVX4XdWNyelWOIV6Ri/Zcn0aKD/Dw9aXBHAKiMi1
1u+XjG/2+GSQRv6EzCaWRQ==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOEns7mun1htPlaJ9Dut7srZPs8zLx2mnV8J2BNAbKqHDQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
WjK+gflGKYbL+4lozgCawEUyGCXGDdulnbl2dYZOPGVzG5m1Glq7FFUvGH6sdkQl
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P4RgpdUyasZdSn+oHMc2tG5Ug0ojdlQR9cuRD1kYLMhs
wOl7lRII4ZgqRTj9M//0b0Hmiipql0KqOySDsulvH0Su8cx+3vVrHwSELz95Nt4O
Z8UsPk1Q7HtwjRd4g01ryw==
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
YdUmdqAC07VhbSt1h6KCDOQhlWQ9jG4k+MaSOV9j4FHOiSK3ETa4U5I3fxOq7tLF/PYmB1e7iFQcBIVDEiWJ0hG8ZfezZD+o0fXjWq7Tix8=
WHkOzVx7seuLmxs5Hu+l/sD6YH+8F+kQZFoPxa6Xx38LU1lUEpTxHz0FS+pwLXBn
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN63t2MnnwkYHotYsrWH9vRICglWvh1CtLK/YdWGbWd1o9f1tzSxswHQJ7jeusy4I/wA==
M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc=
1u+XjG/2+GSQRv6EzCaWRQ==
+plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrcZEDr9DJDb9XjoXgIX///kKtoJQ30XXawDWakYG93Yo
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/Px0qyBBTm/Di4HeEkA6LeK0wkzA9oyOBq1r4iQdNVJFx
wOl7lRII4ZgqRTj9M//0b258VwcrBXb9+d6gAS2zZVE=
Z8UsPk1Q7HtwjRd4g01ryw==
YPfxe0G+MGXmXAi6U4BEkvyb2XevHxdc8FMThKGhtzEMysaWtPfSjbuwkZA3pYeOFKhCSFYG+dkVrsVae0IfFA==
AJ6YSai03eOKVxBS+s+A+4Cy+lk/ZeoSfixZxH7jcBI=
h6wp9SZ6w+zAVENITafY7hRljTrwoJCPY0O2fxDuqa0=
ieyNFTb+50j7HJWl7nbiRb4GCc6WHw87gawZDTQSQBAN4boyX2ECQxLm67quRnkg
gZ7BoIAABc+I8g/rYzq+fxkznXPQP3aR2IC/o8MbAWFRcDH+nxQt+h5tN0pl/k/R
gZ7BoIAABc+I8g/rYzq+fxpmPvrlSDpm2/4GV4ZL52N+f3aoouaz0DKjSpxvecRv
VmVrGQo2zRokW/ZuO9bN608SN+rl/c82Fx3gEheLtEPWenSoQZ0ErgcGs7SYPEIE
fE3zmgbfhO9ZwEda5GgwD2xm2m7B0YFtqnQCEsPic9MwVxNnLGn04EN6FjhytfIHBDJPSIwM3+fK2/ZA7HX2Eg==
9uF3R/OfNUGVQlxbTNUS55dJWWqKoFe+2B8l4N6Jc2pEZCkxDuC97C1dDY8PNwf/
1u+XjG/2+GSQRv6EzCaWRQ==
x22JLf5+9uUTDUdpgwgZ9O83E1bfgaVhykmWdJ+nZXJQ9kCBq7GuTG8v1IJzJD5h
Z8UsPk1Q7HtwjRd4g01ryw==
dar6N0tUAb9rU8/aX1b3tPPCrmt+A77OlLLc0hq85JPAMBfiIm5+TeqFwN72RdG8
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
3AcPLYoMMn4rxWOUMA1wJdOmdYprhThTGxGyDnWq840=
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDCoYsQLlWzDGUd9QUR41IHk=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW+2pW4n/W6qWQ7fvKEZcw6lORQHv34KyeA811LNUCOgw==
1u+XjG/2+GSQRv6EzCaWRQ==
gZq1mJvnqxTNLPjSpQ6bfKeb6fxkOx/+dElrFnEieCM=
WHkOzVx7seuLmxs5Hu+l/v5tNYogyNy2bAogm7myZaPcxZDZSm5ntWmgTrqmpiFB
JlxIfINDpIN74VOPKABZWw1pDjtDv1WACEXIhyxtaYCMs+iDnrosPIR34X2MwwO9+dspbEyZ5RinZUNx6/2rAA==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
NnnRSiFB8Vtr5PXra2GmCcwzujRQX5ogRv1ZA//+TDFXEzK++s5GANm+fB4wZ9b1
T6Eqs+Bi2aLwH9GlPE5AbHo5JCADBnfOf3YzkqXbPUf7ww5fPYa7xY2H8XgXCiUfUway+41gmA7KI8VTyYCtUQ==
1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033YGIXZ5LurDt9cgLUto0D+F
VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM=
1u+XjG/2+GSQRv6EzCaWRQ==
a/jv9CzWHWz0h350oJLqa5TTL4zXej8PqERC4mSIOhc=
e6XLp4vcZ1lGimtmqXOZYQEh7CM4Q/OMFSck6/Q+gT0=
WHkOzVx7seuLmxs5Hu+l/vyBEzeQThILDQKrzavtBosY4yeDSrIgmjG8rDYDt7jf
xWoGNWjKGPfI4gq8aHoTfLS/lZvfoJjLfC/marPQVLnKMOw4Z2B4PM+0VW7zItzI/6l7PfcB1fpqeh4CVpOVaAVtXUo8+KKsnSCrzxMrCgqvl15+qXkkBzeyMR1jFwGa
L0eUthVnpkGsmKFAX6d+uEWqqcpDPrCTTj+Voj6q7co3v+1bz5rI1ebO+u/PYVII
1u+XjG/2+GSQRv6EzCaWRQ==
yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU=
b4OJVZe8QyIpjuTpKXDL9A==
CUHO82G5MDpSEiqhNImyK/VmwjDkiSuGC7ZIskUb7gD65hKsx8U/XeYWPTotCZKW34T742v7dfkWr7P/TSMaHw==
xWoGNWjKGPfI4gq8aHoTfAsoDN3ZmNarxbTXL1u4oucttlV0ayU3aRT17FqHoXJQDIiBBbSbAmeppasRVhu84Q==
VmVrGQo2zRokW/ZuO9bN62lH7iQuGMYTOSVMNmHFiRJ/Sw4G+R4bn28hJN4qoweYFOMRMpD0ZDoWuodT/c5B7g==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN6/hmmducS1PDoVjLNYIkh7yyxYJu3Yggs3y3xm90fY6f
VmVrGQo2zRokW/ZuO9bN6/+pUnGYHu9kYzSmExtaF9xg/93u33jl028+UV8/J7U0fgjyTfXwYVHPB8cx9YfLBw==
VmVrGQo2zRokW/ZuO9bN62pu9LJ9Byu1GJDSPyIAK5YfjdHOvgBbuNyy8GkgPt06Hy9Wawft7wQQemmjSGO9aA==
M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc=
xWoGNWjKGPfI4gq8aHoTfLS/lZvfoJjLfC/marPQVLnKMOw4Z2B4PM+0VW7zItzIvXmsV0xjGZqPof0yohbnqV2ZK6WX/JTtF6Rsc2nFkVdqaK9KQFfwsn1rzq0uaEPH
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXAb0WFcZv4HhfILL3G9ms3qCgcSR8133ZQaREk3oJxSA==
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW+2pW4n/W6qWQ7fvKEZcw6nnotqX+Ka5wYNWQOIeQDvg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
y0vAsW15ZVdHlspkX2eEyTnrhgxq9w3tH5fYg0+3IYTw+fzqXDNLLSILTETgFx1f
Z8UsPk1Q7HtwjRd4g01ryw==
DzneBA92dU5V88ETMYFQ5zTOuyBrABt5a1RgPheterisyI8z8hB6+Xmqaxszc5OF
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
PRG/YRzXVD8iVR3bzEcUnsWMvR86r5liyWslQeT39Bs=
SadDvAEU7JBQjzqB/CmwTKt23QO8pBBBQpkoyF+cMuk=
Z8UsPk1Q7HtwjRd4g01ryw==
YdUmdqAC07VhbSt1h6KCDCoYsQLlWzDGUd9QUR41IHk=
2qQ1+UjgiO2OZgsc3Luuhjl38aA5wDGtKWrWNIfYtrBlHRw9FCPi00BufKOnIQnC
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
p9oVsBgcyiBMjDb8CcPOqB5miGjA7k/rR8HoMrfCBh8=
HiVf4YuHR4gw4rFUUeRvK5DcsI9Cfqv2qLzm9sb8unefn5AgzArrWdtqSFcj8Px9
1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033abQzol1FA4tuwFJraDvx2U
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSo1ZAQ1FHnoPT0QBpT+y//yTdMi/PIio5xW01+SMmEPbzHTSWH2AcPKinWHgjPuzZic3mlhIGEMpw0WGyqlHdR
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
96gahBYKYgdoVkNtss7VEPgCInqEgx8v+sRhs1WPpZm2HnAMO4XvsEY1xpE0BXSYmvq/MLRD+JYKbipt7HmjYeBg/h3NBuTXdf9BwGtIArY=
SY9CIZHY29IN9pslG93gZg==
O3CUgrw2GJfB+mDjH5+NdtbkdTJdRhl+51HFBnR6kaH4/6DbnYid4U0U1SHEYx1n
VmVrGQo2zRokW/ZuO9bN63t2MnnwkYHotYsrWH9vRICglWvh1CtLK/YdWGbWd1o9uxYVCWxZnh7Y3ENahYaMPQ==
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
1u+XjG/2+GSQRv6EzCaWRQ==
P8KbMTjfuhARpOL8NoffNq+3uZDJUpCiwqTVP/jFFTbQzIztXfbek7BVBaXkRncL
1u+XjG/2+GSQRv6EzCaWRQ==
UkQQaGHg2Aow1Gtc9bKBau0euapoYTSumuYNb1Ly8sk=
NhnIR3Ilo4H2su9/cTNo/LBal3Bb69aVfp/VrSaJBVopEyKAKnVV8FGgN9p834dg
IhuisKim47k91RVt8z8qtJyWLVz+KjhPuZWupljEG6xxx85dlt92sHkP0dH3RDilXI/lJNqFeQKeKpcIDtD+IA==
Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8AGlXEXTs+FitgdECPA9/lrzW8srVC0vHK9JhZxKlpY1OTyVkakThiIe7k7Hc/3yZ2FjjNKgggOZKRyD6HAzHN
2+RceSNjD2iq0sRzepMysZEtAjuWiKl7QaPq1reOZ3L3mfen1JvnD6MPvKsBpyOKkDYBCw5cZs24QV3TsSDbQcyqIrPAseuaKXpl/OArmSc=
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOERocNC1mWlogqCdtng5segh8OjwBnGkbvs4sXKYd75Mw==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
kkhBrrZfa9Lz2ARtxqQ9WISHe4SzqYX3OdpNmbR5L/VBHTenO90IETRFsXol/Jpv6kfKAQ8LAS+ijYGG5x08+Q==
Z8UsPk1Q7HtwjRd4g01ryw==
a1WCYQuoRJeK3mOCGHQIllG8L0Za7F6wo/ffbPvOV8mvOIi5W6Px2rfj6NO9ykAT
eeFiPlXUI0Fos82HYztSmZyutxdoUP9KbXnyyGYMqohKcgTCAEZmONzPn/3V/XeU
bWa5NLmMcRfP7/pL4TxwVSNHz9M1H68NEGnEXfZWNdlCgk9i6Y3xP89ubu7Qieu3
eeFiPlXUI0Fos82HYztSmZz5nVydpyguF0WdL2JyDxQ=
yqM179SzW9HnBHozkU+IlRXsPW+yWAUAvWc+nnoZsRTppRoZDJSnb51AHNRYCykW
akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018=
Z8UsPk1Q7HtwjRd4g01ryw==
UxWVPzm6Eag/KO1azIHUdx+idAdS+rfPARzR88tVbgYdpuz2ULTF+2vmnBiei7OW
Lb802TYIO8cTPX7RtIYGZ+/BDmfzYBEomwEqSDkd3VI=
b4OJVZe8QyIpjuTpKXDL9A==
I5csN8e3J/KIFkMa5t+SJATQ2qGW0wOfRPtcA5bj1z8nKyxnKr/hfHKXbqiTXRny
1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033ZLNQ2XZF4KsFXQ0AJ186dTqy0tFe9yVaa5Nvi4GzvRQQ==
UiubnxD7y7+jD/CLXy/lIzUv+EQIUnxp2+tRaByCudDYzFPn13pFELSDNAgNf0aY
wI4EEWzdSnynhqFxMCzFmvtQa54ffiTU8OKIuM+mUtbRQW9n1JFAWLuXCST3voAI
GL/yLvMwjflJNfDPl5ASjfzFHnOGqpWC8vWJqX+sGuU7UB9p8iAxBS67EStoox2D
1u+XjG/2+GSQRv6EzCaWRQ==
P5fFy0VqKNQcixZC6/gotxklJeUYBp6qUkCM0szi5po=
3HDkd2BFYLPk4K73U5D+BYdx+YPBx8edbX2mUEZUhcw=
gZ7BoIAABc+I8g/rYzq+fw0QRhyOskLowTpBMxzNkkXJfHhElpIZe5wsCdqDFCZvGpC72uDw/Cu+D1FuT4o1ew==
NhnIR3Ilo4H2su9/cTNo/AxXtCXBAtO/DWPyG38GkWo=
SXXsDW1aLF5ljPyJUoRc7t2uV1Ea/KDPsubfQRA1/g0=
i/p1vQ7ugXGBvwbjECApz/Cr9YvVAiEjIabMp4aMpkk=
zo+FVrlHkUeZ8fJX4PVhAH4W08MspSsE1ssI18UNPaA=
1u+XjG/2+GSQRv6EzCaWRQ==
wlLHv6kT3Q/RmtMBN4nDAaQcqH83V4KNJSetgkXN2mpJpLhuyLAaUNt+tKkzaLSI
VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk=
VmVrGQo2zRokW/ZuO9bN608eo5JjqlJJ+hYuirBCFcmf4cKrfv149X3GuxINE1xDDJnAj+I17VZiW+JCiwfRwA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN67FMFupuL9NnhZUvLdV1qH1jDx/7tB6qL1SG7Pqfwsin
VmVrGQo2zRokW/ZuO9bN65qbK6/I8bncNNV3Fr84lp9CCx8VYIApMdX4h8SHWyk2
VmVrGQo2zRokW/ZuO9bN6zEL40rbK+o6j49cNVbzKqOsExNYZ+R8lUnZEGfiuhXY
VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+qnnPSHBLYJiyMbFizN9aWEoBeZFZoLhXy50+Ts9Ur1/A==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN62OM8cyvjkpIHunHzlmyApZDgMPN1CpEHj7RB29QdSFu
VmVrGQo2zRokW/ZuO9bN6zDu/0cPxO8iIAxo/MyAOyPtJNDgANrLqcOpy3L5oY1k
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklSomvH/VIQjQOrkHPd2BPf
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklTf9Kt2VbDsiUekTyaR+Eqep0h0uyOXB1iMKSg430G3P3HOaEPs8YtIeaBmiEO8lwzs03jh8/eQSyBAoQdtToH
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkI9vh2RQuknkZgs1Cqb07GeK7Ac8sa2K/nX9m0GTqvYw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69jzSpHadiEPTz774Uy5h5Nmsum61eVUSkdR5LKSFtye
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknaeZX5axwNGbFayb5AUV0mdbRkjhMqcaUp+9h1iRD5Jw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklp8xpCS2Qa6hQCt5qnOrYkkwQkeIQCwQPcHEA9pzUHDg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklt0syFuEeIYE3//UfiwS/lcyJ5NAN5s2jGe4Vc5xHKeQ==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmzh7YNQJ+XcGeFsDcaBjSz
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknMDNdxIBFQcIw0RGPfFMjZjfh19zPlQyTadlR6rEMpug==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklDS7vlaZKw8aBp1UPbgthY
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmbQcBvdDeaM0tbHayRHb0Q5oDsO40qheoE5NHoJ7eApdm6gYH0+XlN/FV3HdO5VZw=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmO4Xe0u+mz5wM5yC6Q+Zhs
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmpDk5452BsZTuBSuOpo5yN
VmVrGQo2zRokW/ZuO9bN69IgslanOR1qBWJ7u9f8kApHl7m/3x/YUX5TmvRhoWWrhrEvRs3e6Vo03iY4FZoGJQ==
VmVrGQo2zRokW/ZuO9bN6yAU/fW49cRu9QdgvjUnDRs=
VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc=
VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k=
VmVrGQo2zRokW/ZuO9bN6w7SLxNKr+6YhBMvtqYtIS5B39SOQ9awYMdHGNN7g2HF
VmVrGQo2zRokW/ZuO9bN61ftGV3vPtB2WjF6YrN/0jNHYRNif5kQHZWzQEGp4Mrw
VmVrGQo2zRokW/ZuO9bN68i9AQUAyLa5Izsu2k/9DPtGLI71suphKNgMpp1nHjp3
VmVrGQo2zRokW/ZuO9bN60FmyZq9YoZep5W4V0xLs2VJw4EwR4WqghfwBKZHKkzFW5QCRYRsMStsmunHScuj3A==
VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C
VmVrGQo2zRokW/ZuO9bN67c57efC+E7a6G1EN/4VXAUA5IVAU8G/FAp8AnNhV+8pDSYVAZUG09Nqr+7c8CWyvg==
VmVrGQo2zRokW/ZuO9bN6x9SPCVoe4PZoqZ6VqYN93GZvfLrMfeSjpwDGE6Mez3z
VmVrGQo2zRokW/ZuO9bN64ApqCXcODvD57AL9oh19dsQTwsNrJDaw5mMC3iBkcYF
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn5CJpDak47Y7NTLN+SVG+JjtSLj0+GTdSHjBiYnUk9ZehN+mSf/Rh2eO5fYqxzSaFaMrHoUfHlS3DLFMFIxzNoSLKwxQP2/fXAWKceACqodA==
VmVrGQo2zRokW/ZuO9bN69/FjVndCR4PL8NhQnFk2GePSUVga2CqkdtTIa+/EIW/
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklDS7vlaZKw8aBp1UPbgthY
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmhycRnROwAaTF+9pqngLGtjJZz0E57qXffjBTU8zR5ws/8WNuh6zqeTDPmSR/EwdRf/kscgnRpkyfDhI32MBlHgF5oG/UTLtRJTD7kKhRtdQ==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmO4Xe0u+mz5wM5yC6Q+Zhs
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknbl+0QAy/9I0gZXTmE2tktHVvxDseM0i9LrUUswPEB0g==
VmVrGQo2zRokW/ZuO9bN6yZnMkqRvl1W1z1JA43ooyLsysqnBW0cSn//FgR0VFUn
VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C
VmVrGQo2zRokW/ZuO9bN66U3/6225IbLyIW0FVhZRNEyLZurMXbjR98/i0DXR7zf
VmVrGQo2zRokW/ZuO9bN6xaf2vdYGOU85+dskGN5Zti8/wz7YWu3y/jTkefW+oHw
VmVrGQo2zRokW/ZuO9bN6xkyZls2bS2c0K0h3aqZRdZrjes6+0hX3Wr6T+0mnqitWU1y3v2I2Wt4PYH0XBjT6w==
VmVrGQo2zRokW/ZuO9bN62zrDNonCkxA5BeQHJAPUOvVeRTu47AJZhJxjTJXUg6D
QPiJZBcZo3Zu5kDhrrGzbI3GQxTKN1tlSYOd4wqk4Qk=
o6QhOIN2Sc4SHELnst17uYG57hDyRaN+v5d46f3m9TtvuM1edZME3sYQm1fwKXSE
1u+XjG/2+GSQRv6EzCaWRQ==
1V2v8QerKOmubvSxgB4eTM+LVbDQYpuIwwcEKVLExe6MC8BJb9x5uoyJJlFncEM6rY6CjjYEoI1lGYiKE1nBng==
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
o6QhOIN2Sc4SHELnst17uf4l+kb1P+/cnojpDP8CeKM=
ACEEdgL/kNbx7RaZcSRxZClHBggmOTwuoukB6MgtasIiEFle6fCvd15573nsLbI9
VmVrGQo2zRokW/ZuO9bN67Pocv3BMn87TAB+S8aqZRSL3g30PRHmEkGas7a6CXo4
1u+XjG/2+GSQRv6EzCaWRQ==
b4OJVZe8QyIpjuTpKXDL9A==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN61efkqHAT/xspGlYy7KhUnGfupbfN6kadJV1FzZSIvpa
VmVrGQo2zRokW/ZuO9bN6+b0SyPGp1VLLfFreWS32CdvJQ0lilsNKGrnKkyex2l6ZoEN1nB9tzGcgrcEFt4mWg==
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN6zpJcGQDok2bzZbwZ5+2z7FMKwUs1r6SjvZQDsl9E9LF
VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgEYDxdXHdYYYJAWxRhWjvkMMBYOgdKbm+/g/JVB0KXlVgkWFQ0d19yx2HQHDjijpFM=
VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY=
VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgGfllFXAtMFaX/YohJyQyfhG9xy/eo7UEqOCihMHvYuSYyU9x8O35sNRwdldlOqO7VLthK8cTiezk0qcNGLbtsxNx/TEJZE7IDz5uyfUbO2Fw==
L0eUthVnpkGsmKFAX6d+uA6wgjrZ2VgEjfvSiyQz6oysGP6R543HWJTxMrxe6FdC
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
L0eUthVnpkGsmKFAX6d+uGu0U7q5yxq3pNh7Qp1zdP4=
+243 -243
View File
@@ -1,243 +1,243 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import zipfile,shutil
try:
from unrar import rarfile
except:
os.system('btpip install unrar')
from unrar import rarfile
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
zip_file = None
try:
zip_file = rarfile.RarFile(sfile)
except:pass
if is_close and zip_file:
zip_file.close()
return zip_file
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
zip_file = self.__check_zipfile(sfile)
if not zip_file:
return public.returnMsg(False,'NOT_ZIP_FILE')
data = {}
for item in zip_file.infolist():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):]:
tmps = item.date_time
sub_data[d] = {
'file_size': item.file_size,
'compress_size': item.compress_size,
'filename':d,
'fullpath':f_name,
'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])),
'is_dir': 0
}
if item.flag_bits == 32:
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
sub_data = sub_data[d]
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
result = {}
result['status'] = True
result['data'] = ''
with rarfile.RarFile(sfile,'r') as zip_file:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
buff = zip_file.read(item.filename)
encoding,srcBody = public.decode_data(buff)
result['encoding'] = encoding
result['data'] = srcBody
break
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表,数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
return public.returnMsg(False,'RAR archive files do not support file deletion')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
return public.returnMsg(False,'RAR archive does not support this function!')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表,数组格式
"""
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with rarfile.RarFile(sfile) as zip_file:
try:
m_list = {}
f_infos = zip_file.infolist()
f_infos = sorted(f_infos,key=lambda x:x.filename)
for item in f_infos:
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.flag_bits == 32:
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path,os.path.basename(spath))}
if dir_key in m_list:
info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:]))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
zip_file.extract(filename.strip('/').replace('/','\\'),tmp_path)
for key in m_list:
try:
# if key != 'other':
# dir_name = '{}/{}'.format(extract_path,os.path.basename(key))
# if not os.path.exists(dir_name): os.makedirs(dir_name,384)
for info in m_list[key]:
shutil.copyfile(info['src'],info['dst'])
except:pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'The file was decompressed successfully')
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
return public.returnMsg(False,'RAR archive does not support this function!')
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.filename
try:
filename = item.filename.encode('cp437').decode('gbk')
except:pass
if item.flag_bits == 32:
filename += '/'
return filename.replace('\\','/')
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
Hf+bBTgRv9pVfall8ODpvg==
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
1u+XjG/2+GSQRv6EzCaWRQ==
K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU=
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM=
KbrESL0Rr2jC6vOleUlueMUgBS8ubHMHEf/dewOmXjc=
f3AsbhS4PaN1B4cUkY9T+A==
7slA6OC5VJa1dhNh0P/HjCGJmXVn2u3xkCMlnoSxSoA=
MMZtIZZuuuWy0CLBzKekTg==
0cnYXLPu1n3c2VaQ3tVc+NKPyveC0ERGp532EiLQZU5gtpnLWSMk9qn8OtVlUlvH
7slA6OC5VJa1dhNh0P/HjCGJmXVn2u3xkCMlnoSxSoA=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q==
KZTmaJLp+FU9X93j5Tpqtw==
0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR
yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf
akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnH1EgArAJXS2MAairjwNzV9c=
b4OJVZe8QyIpjuTpKXDL9A==
wQn8ViRVbUL8OLSIgBD0IAeRdK41R3m/PKt9hFbBzr88nFZgyZVILp1bIMP5rZUJ
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
1u+XjG/2+GSQRv6EzCaWRQ==
l+sL2BKY+z3yDMAsWI51b39VWekdKmbfrOabwvVHTbGfiUobxgPhHW2OIZyeGUJ9
wQn8ViRVbUL8OLSIgBD0IO53Ya1+ckG2IX47+ykaGJU=
1u+XjG/2+GSQRv6EzCaWRQ==
MxWDzXEQjj6mgdfarjHFtAA6unJa5l9pTo0P8ga3JDU=
1u+XjG/2+GSQRv6EzCaWRQ==
x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnHy+YX2/U7Ip4Uqt3suxNhindpukzgQ1aM+PshcW/1dV1
LuO0LAjc92aYQU4tSUEvi4x4NJPWR/eKWjRfKMSRL8o=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV/41TA4054wrqxXx/Q1i8cmtFJNrxypDZNiWIVVjiYLA==
1u+XjG/2+GSQRv6EzCaWRQ==
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
ei/1A+2pKZSasjYF/t4qojBnh7Bi8Fen8Fcf8mKjT9IuWg8ZCZGedub8Eqjf8qUh
1u+XjG/2+GSQRv6EzCaWRQ==
4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg=
ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw==
1u+XjG/2+GSQRv6EzCaWRQ==
g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR
wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I=
VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu
VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf
VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL81I98txu56TEAiJ0hswK9
VmVrGQo2zRokW/ZuO9bN61e1I6gZaVwmbMEHc7jd9wFEHpeilcxXyCkd5bmWx/yK
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8LNRE5UkGbTG1UxYqNO1dyU2pay/FWt6r8mJiKZ4UU/g==
VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs4jrgwkpIqneJssR9hHHlJ3UJd9tZzLLz6C+UQLUJWWGbgxXo0UEEapI14RLzluAUk=
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3
VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ
VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdx733inB6pPVPHnS53cxkslRzdzhPLD4mmySs0XcaZo8Yb7xMuMGGlmbahCRF6eC3yiiMtXXp0R7BViTpiJ04fqWtzkElISMu7z4pkkwr23TjUMjTjCelUG9Lwgmt+EiK
VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3
VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvjL/YDIWVk4lhx62fOdGpioi6WGXoNG40BaJlgn48VjQ==
VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ==
VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k=
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo
VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW
1u+XjG/2+GSQRv6EzCaWRQ==
+plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI
YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54=
d2Ww7zhXROgo6Qq8ml3vsgVOxob/5TKf0KTWSmheRnqYA/wEs2DzrY8Dpm7Fq44lbdZ2N/U6naG4DD7karinEw==
wlLHv6kT3Q/RmtMBN4nDAdAMeIPpwO0Pgawv937x0Zw4iwEVDD1EIlJA37GxF8mZ
VmVrGQo2zRokW/ZuO9bN64/tCOCyKu41lsl+3BVlFMtjVa1DXtvHbVFGS5luzEIFtQp9eHGpneVvKz65gzXCXQ==
VmVrGQo2zRokW/ZuO9bN69Z4WTbfjflDn+PHJe4HyeBCMRIQIdsxtS/SkQUyTK46
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN64IUBVOpZieySJUi/5Xp7IP9i2hr//841YnIEqNDctKsA4PCQR7FmaZMQHs7tdC+NQ==
VmVrGQo2zRokW/ZuO9bN63KtgQxYoQMLiw/swXgEzq6F5dbUK15QUQFhJ/fqYDb2bGgnr3J8HSW6FkIPXw/QAPGbSGA6euyj6sY6jr9uD88=
VmVrGQo2zRokW/ZuO9bN60+Gj1vDNaFgHQa9qY+WYQ/+8quDVQ7mW+ZFMiCB6dK3zTXaMhAFlgEsY2XTlgH4hQ==
VmVrGQo2zRokW/ZuO9bN60dNkfoHcjUpCVMrBt5WSA9PCuYzYl0QLyyWqlMopPgY
VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig
KZTmaJLp+FU9X93j5Tpqtw==
dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
1u+XjG/2+GSQRv6EzCaWRQ==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRDaMkNWHl1CoN0rBoEBVHY67QP37rmJsbUJ2x6ftwGYtZYSf3Kk0VjMvB+nFonZq5o
1u+XjG/2+GSQRv6EzCaWRQ==
O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK
KZTmaJLp+FU9X93j5Tpqtw==
t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8=
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRD4/QxBrfqq90D/lFkSa20blyxshNDPqg3WrN+80ZCLiv67pSFMB4Brq3qmKK1ZCax
1u+XjG/2+GSQRv6EzCaWRQ==
D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS
Z8UsPk1Q7HtwjRd4g01ryw==
VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw==
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
Z8UsPk1Q7HtwjRd4g01ryw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr
NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw==
wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG
NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+
1u+XjG/2+GSQRv6EzCaWRQ==
d2Ww7zhXROgo6Qq8ml3vsgVOxob/5TKf0KTWSmheRnquRJLMVVVt2mgyaZFKv044NjiViQAAPm85lrCa2jWZ2Q==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN67AsrXUPIcMWmsNJ6skMzcKlx905HWgBAV8CDz0otLpU
VmVrGQo2zRokW/ZuO9bN64dz5HIueoVr5d2RnbNUIG5nWBmTbO/nPqZZ/WWusF8B2fWzSHjB5SEduhXLHWwpbFrG7MHMtEZh0rgim/Nvx4Q=
VmVrGQo2zRokW/ZuO9bN66NLXvUSFhBjtRrUbZsPQNOrrRiOaX/5u3wb0Rfqy+pv
VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe
VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvjL/YDIWVk4lhx62fOdGpioi6WGXoNG40BaJlgn48VjQ==
VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap
VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU=
VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA==
VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+ISRqfA+Zum4wzK337qVQa8uzWtdDX/DDCp8lweCdFZUw==
VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuTb4tOz+zOmUJvBAa9I7hKop6pGwEoB2371+83RgakZhyjMJWosIoeLReEb5GcqHNA==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkrikeB3PyYnRyHWKvzXceKTTsB9Sbgf9uM/v0isHHFakzQxpTSrdsYuODmqyQGE3Q=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknz2So+gFu+BijKlvPtsNKCIi0RkX1BFpd3GhHKBMdoB6DedipwufkrnDIRLB493gRtMiXCiHSsTBNmIXqRl2RK
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ==
VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw==
VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrD2Em+ygcQFnOdLaXNs8ffOckKjgP3gK8cG/TTtMkpW3DL4StNcG/wbkijZS1ecn2uSboPx9VWF3PIGue3h88Yd
VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD
VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg=
VmVrGQo2zRokW/ZuO9bN6yfyDpG+MDWCM+I7xrm6yMNHAsN4/aIr7LH++fbWW227
VmVrGQo2zRokW/ZuO9bN61RP3YbpVjqNga0la0+UFBO8any5NddZFrxwPV3C0M24EIcWBMjLzVvJvQ44t1iabGq/xokqYehL60ehDQbn9KxCN13a+8iVaZY1iG866Qqj
VmVrGQo2zRokW/ZuO9bN62eRNBkbzSoIoWrmVfjVYrLET/E/3Y6o68HoLFuelGsQJP2af8rBXdv1FclI3SNTly4xPKYQA8oVfyimWnwTn5ty/z6nec9yo4C3p/VcdNxW
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg==
VmVrGQo2zRokW/ZuO9bN6zS5woMmxkovFr1YPmCwVjomnwjs8yJd8vJ968XXd0m0pndaSK8+qOGykyY0WQyhZ6POo4IRkaJlDhmmPU4hQ+8=
VmVrGQo2zRokW/ZuO9bN643JcJR2kB926iqWdGz5U79RHIfKNGRE8o6dVdz5ZS7F
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGez0gsOf0QeJW43sHDXr/9AmAy18p0NZvV4WTviIGdP9rl3wBU2+XWJNGEpDCn6tY=
1u+XjG/2+GSQRv6EzCaWRQ==
mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W
KZTmaJLp+FU9X93j5Tpqtw==
H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn
c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U=
Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY=
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRD4/QxBrfqq90D/lFkSa20blyxshNDPqg3WrN+80ZCLiv67pSFMB4Brq3qmKK1ZCax
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX
LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR
akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A=
KZTmaJLp+FU9X93j5Tpqtw==
BIa0ibHnqUboaK9kYHYO4qFxMPyUysXdjYA1FqqhK4UMv4PoX1vehXtIZKdqRHbG
b4OJVZe8QyIpjuTpKXDL9A==
PRG/YRzXVD8iVR3bzEcUnlkfSwESl/MBfBcCyRS23XAS+yqB8BD2Fws8ohDlx4dxPS4gXd1cpWdmGRHNT+D6KSwXWu5PYPEs39ffK4i+qWM=
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
6XDh38s4HWrzA9ChHtrpYfoDrFj6al2d9qt+zDQaqfaH1G08yJkGG2cvEJO5JVMs
PRG/YRzXVD8iVR3bzEcUngb7XqhS9BqrxYAnnSw8i/s=
1u+XjG/2+GSQRv6EzCaWRQ==
5wsVwKLrIjIWqeTNpSVp9UQ8Pi6XZeLfFubLS2iQfjkZO92jH5vXc8qvq/7p/iKy
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
+307 -307
View File
@@ -1,307 +1,307 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os, re
from filesModel.base import filesBase
import public, json
from html import escape
class main(filesBase):
__s_class = []
def __init__(self):
for i in range(1, 100):
self.__s_class.append('f-s-%s' % i)
def get_search_status(self, get):
"""
@name 验证是否可用
"""
return public.returnMsg(True, '1')
def get_search_result(self, get):
"""
@name 搜索文件
@param get
path:搜索路径
search:搜索关键字
limit:每页显示条数
p:页码
"""
result = {}
is_dir = 0
search = []
if not 'ext' in get: get.ext = '*'
if 'search' in get: search = get.search
if 'is_dir' in get: is_dir = get.is_dir
public.set_module_logs('searchModel', 'get_search_result')
if not is_dir:
if len(search) == 0:
return public.returnMsg(False, 'Please enter search keywords!')
if not os.path.exists(get.path):
return public.returnMsg(False, 'Search directory does not exist!')
slist = self.get_search_files(get)
if is_dir: return slist
num = 0
total_num = len(slist)
if slist: public.writeSpeed('files_search', num, total_num)
for sfile in slist:
data = self.__check_file_contents(sfile, search)
if data:
result[sfile] = data
num += 1
public.writeSpeed('files_search', num, total_num)
progress = int(public.getSpeed()['progress'])
if '_ws' in get:
get._ws.send(
public.getJson({
"end": False if progress < 100 else True,
"ws_callback": get.ws_callback,
"file": sfile,
"progress": progress,
"total": total_num,
"num": num,
"type": "get_search_result"
}))
if not slist and '_ws' in get:
get._ws.send(
public.getJson({
"end": True,
"ws_callback": get.ws_callback,
"file": '',
"progress": 100,
"total": 0,
"num": 0,
"type": "get_search_result"
}))
return result
def get_search_files(self, get):
"""
@name 搜索文件
@param get
path:搜索路径
search:搜索关键字
"""
data = {}
data['is_sub'] = 0
if 'is_sub' in get:
data['is_sub'] = int(get.is_sub)
data['ext'] = []
for ext in get.ext.split(','):
if ext: data['ext'].append(ext)
data['s_time'] = 0
data['e_time'] = 4070880000
if 's_time' in get:
data['s_time'] = int(get.s_time)
if 'e_time' in get:
data['e_time'] = int(get.e_time)
data['min_size'] = 0
data['max_size'] = 1024 * 1024 * 10
if 'min_size' in get:
data['min_size'] = int(get.min_size)
if 'max_size' in get:
data['max_size'] = int(get.max_size)
data['names'] = []
if 'names' in get:
data['names'] = get.names
flist = []
self.__get_file_list(get.path, data, flist)
return flist
def __check_file_contents(self, sfile, contents):
"""
@name 验证文件内容
@param sfile:文件路径
@param contents:文件内容
"""
n = 1
result = {}
try:
for line in open(sfile, 'rb'):
try:
if type(line) == bytes: line = line.decode('utf-8')
except:
line = str(line)
rep_list = {}
_line = escape(line)
p = 0
for txt in contents:
if not txt: continue
p += 1
txt = escape(txt)
if line.find(txt) >= 0:
_line = self.__replace_contents(
_line, txt, p, rep_list)
else:
tmp = re.search('(' + txt + ')', _line, flags=re.I)
if tmp:
_line = self.__replace_contents(
_line,
tmp.groups()[0], p, rep_list)
for key in rep_list:
# public.print_log(json.dumps(rep_list))
result[n] = _line.replace(key, rep_list[key])
n += 1
except:
pass
return result
# line = line.replace("BT_SEARCH".format(p), )
def __replace_contents(self, line, txt, p, rep_list):
"""
@name 替换文件内容
@param line:文件内容
@param txt:替换内容
@param p:替换位置
"""
n_data = 'BT_SEARCH{}'.format(p)
line = line.replace(txt, n_data)
rep_list[n_data] = "<span class='{}'>{}</span>".format(
self.__s_class[p - 1], txt)
return line
def __get_file_list(self, path, data, flist):
"""
@name 获取文件列表
@param path:文件路径
@param ext:文件类型
@param s_time:开始时间
@param e_time:结束时间
@param min_size:最小文件大小
@param max_size:最大文件大小
@param flist:返回文件列表
"""
exts, s_time, e_time, min_size, max_size, names = data['ext'], data[
's_time'], data['e_time'], data['min_size'], data[
'max_size'], data['names']
for name in os.listdir(path):
sfile = os.path.join(path, name)
if os.path.isdir(sfile):
if not data['is_sub']: continue
self.__get_file_list(sfile, data, flist)
else:
#第一步:验证文件名
if not self.__check_filename(sfile=sfile, names=names):
continue
#第二步:验证后缀
if not self.__check_ext(sfile=sfile, exts=exts):
continue
#第三步:验证时间
if not self.__check_time(
sfile=sfile, s_time=s_time, e_time=e_time):
continue
#第四步:验证大小
if not self.__check_size(
sfile=sfile, min_size=min_size, max_size=max_size):
continue
flist.append(sfile)
def __check_filename(self, sfile, names):
"""
@name 验证文件名
@param sfile:文件路径
@param names:文件名
"""
try:
if len(names) == 0: return True
filename = os.path.basename(sfile)
for name in names:
if filename.find(name) >= 0:
return True
try:
if re.search(name, filename):
return True
except:
pass
except:
pass
return False
def __check_ext(self, sfile, exts):
"""
@name 验证文件后缀
@param sfile:文件路径
@param exts:文件类型
"""
try:
if "*" in exts:
return True
spath, ext = os.path.splitext(sfile)
if ext:
if ext[1:] in exts:
return True
else:
if 'no_ext' in exts:
return True
except:
pass
return False
def __check_time(self, sfile, s_time, e_time):
"""
@name 验证文件时间
@param sfile:文件路径
@param s_time:开始时间
@param e_time:结束时间
"""
try:
st_time = int(os.stat(sfile).st_mtime)
if st_time >= s_time and st_time <= e_time:
return True
except:
pass
return False
def __check_size(self, sfile, min_size, max_size):
"""
@name 验证文件大小
@param sfile:文件路径
@param min_size:最小文件大小
@param max_size:最大文件大小
"""
try:
f_size = os.path.getsize(sfile)
if f_size >= min_size and f_size <= max_size:
return True
except:
pass
return False
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
Hf+bBTgRv9pVfall8ODpvg==
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
1u+XjG/2+GSQRv6EzCaWRQ==
xO7Wpp8LRZfjlLUey8VU9w==
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
Ji07I4FXLA9HlL84f+xIzwVBYHb2UR2JW88XmYqsjV8=
FpGNKKrJzpuJOv3iM0R0pQ5MvNLfEMlC/N/hQ9UzGqI=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
TRR4OWAtNW2TMCr31HujQ786ngB4F46vN339GISDrdk=
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
9zgnmC3+5N9XK5/NRaR4SCWZ3UikXYFnKKlpz6ENZEzWUZDKLOFleg2+OHdqWpsE
32pdC9DD05OE2l0oXazDFOXcNRPwrc0ZAJ7EyyAKw9gZBJ6iOJyN12yiwNtvG/Etj4AUGv20tGUTP756+m4Fmg==
1u+XjG/2+GSQRv6EzCaWRQ==
U28wv7XxIHeRSkqXJHXzR3dYOl05WSZCrwQLHtq7mE+upUObOxjAVH+D2wuzBbzd
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrtYZbEcZaFTWzu76ev6QF+qbXnZjxFPpMEvGatyQPWPw
Z8UsPk1Q7HtwjRd4g01ryw==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOFR1+D7xHQXnSFNKqEFQ40g
1u+XjG/2+GSQRv6EzCaWRQ==
U28wv7XxIHeRSkqXJHXzR/CzOXT9fdhh18KnqL31WmtG7mgmXX+KEsVaJ9DtI5Om
Z8UsPk1Q7HtwjRd4g01ryw==
3f+yEXMJpDOPrez5ap+fN71X0yVW9y+W39rIT+wSpcE=
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
9uZN8r0CKVAbfzGtFDuvLwhOKNflpKM2ZwfKcLjWwCQ=
s2s/1ED0ebJ2NmFlE8kk0/nWNx9HXYglwLmrDDl0SJVEejxjmWbqF4tItLVHJaXz
zxNKIz/Dwj9Harw3dUNEQJhfwzP9e0g+QEHF20IF9QloJJn499jB5lN74CMN7xKF
sziRj7t6JsRtk9qEmKAeGc8r59oagHkoEVKlJap65zo=
Z8UsPk1Q7HtwjRd4g01ryw==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
EAqIF4KpaXVDvs4DDT5V96KZSat3hIJqR6dNVUxhnWo=
Nxw10FAZUk+m0nadC1hdyzNerAYqJLcLvHGGVkpkJIM=
1u+XjG/2+GSQRv6EzCaWRQ==
g9LTwGsiGd7fGczReaq0piwhORjgGOZyOpIjDfyNOXCPNawCc9H7QjiYk9PnEc8K
X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw==
R4Jn97/68Irdow38EVIRhDowEdscaTuWxhIXOfqZKT3o/TXmpW6PH4gqeWelHNA33aMiyCRKeO0D7gfs1V70Hg==
1u+XjG/2+GSQRv6EzCaWRQ==
Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs/lMRSVWryD1gVdrE+N///hctbXY1m9aLHS8lUrVSnFP5O6PHEXfEjsMokMZJkUfD0=
cbvASHjpysrsjdY5RctXmJXaYs34E4tMRz7722FvpDk=
ACEEdgL/kNbx7RaZcSRxZD3JieACM+9p2ggEO8B4CeOCL6Z+tjHPmOoWvsHltK8C
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSo1ZAQ1FHnoPT0QBpT+y//Ad3DLkc/FyKmgGPF5OiRLoeUNVCXj1GWzYsscvnb2SZxU4zI4hEOYLKQ5kvjDFyS
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYvriJFohZH5kYuBlhocmFiOq4wyQewMJhJy8xbRXglbx
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUaLU2ERbIduh0en1Wj1B8Tfd95oiMHCmcX2jALSwoq6RyVU7vVavWtQJUE9viKOUw=
1u+XjG/2+GSQRv6EzCaWRQ==
2/eGB4mNeTC2Lw/I1/B9jClbFP06By8Q6TQXjVRMC5aisNpbx4ptA/UYjC7wxXvn
DUkWJc4dP0F0T3fzlo5ovg+i5a7jjY0MEmCx8kfq2jK+QHBdk0bbr2AxZ3cQqlrr
Djb8zu0gG2jt979SRwXXjOvVYuDaNNQCNmI1OvvoD70=
H72sLFEMWD5UKG0XW5F6nGJKzW/LoRFoOz84reF2UCk=
2RF1tdl20VItbDNfdL2RkNTBOOe8pYfatuolPTwnN8LGADdQl54uUvT1DVF9e+iGV4cafz7X+gg6KtVrt/TXCPI9NWJqgOr1pzq/i1AhR10=
6Kva1wVgBZWnKH8KBbNuqu0I1u+AyN34C41R+ayvUyA=
NhnIR3Ilo4H2su9/cTNo/Fny68/2PqdXcpcFyZsnflsKTqzPOMITq9Y6CpuGnJ1XbcsahDP9y+wRMXSx0waR3Q==
Dlyx6ZDFdkn72YIxkC3bo99d0wqqUWkm5oggFt3pXuw=
VmVrGQo2zRokW/ZuO9bN6wd/KBNDReWGUABD3WNwVs62xWSEQgqeqad2Q73LEQLC
7aT066WRx/cZGdWRKj2TgzQp9AN+Q9kHfKqypeMmdG4=
xWoGNWjKGPfI4gq8aHoTfPXeKqcetXNDNllPDdGzxJIYVQirFuRW+EjhBVgYVjVDRD56A0SEtVDAKN9yNygMzg==
jlXY8vCef9GOpgUZXz0mf9DfhxAODMs6lMdvzBIM/MW1Aq1rMVSrJYB723qLrfih3YWjEp+EB+NTwJh1y6k0Yg==
84Vymj90Wzn5yYuvz1pUuNMKvUAWFp7QE+2Yweu4868=
VmVrGQo2zRokW/ZuO9bN6wEyB2Guh40Pcugoyh0QBDk=
VmVrGQo2zRokW/ZuO9bN66AnqHISKFWgaxa6i1eykZnjUYRjoLmSwgSMUPuifCRX
VmVrGQo2zRokW/ZuO9bN62/0JMqSTjLVEyn+Ej8AxwUnCoc7c66mKn79grgEeCW4lCCimbVM8qjmKRX089HBXQZTubrP4Dlhm4eMwMPvv3E=
VmVrGQo2zRokW/ZuO9bN6+E4b2xiC6ZHjT1m1FpW3M+cImaNbhy13Fhkj9q6wpbsV6pyGO3VUL36gOhAJ+Q9bA==
VmVrGQo2zRokW/ZuO9bN69SEQTu8KN9su1QzyegRt9I0k+TINrUN1jOdFTWatw/A
VmVrGQo2zRokW/ZuO9bN691C3HCcq309X4zk3aYIPPcffxT7OcwObFcDmavXvPD0
VmVrGQo2zRokW/ZuO9bN65RsFGHNPy4PRufoKux2OhO78Lnr5z+7zzZGg1YcYuSV
VmVrGQo2zRokW/ZuO9bN6x5oB/WlUCb6mmC2LMPppfSCyk+XA4K4GFn/hUbHBSy5
VmVrGQo2zRokW/ZuO9bN67Ff1ABLPDzqsQSzl/a49rnaHEAmRXtCEzOMwE/sYpXvJqC4BUWuPJeNx91EHEOGRg==
VmVrGQo2zRokW/ZuO9bN6198WF1cuKQrC1Fe1gF45Yc=
uTEK8Ng11d3ix2pA+DD/aX0ELDXTMxMbOqDs4xOCbbtjTvaikhc80beKBuWMfrmy
OO/dMlOTxBikYSnUTxYxGjU1adFuYtvDicPSrTmmd94=
VmVrGQo2zRokW/ZuO9bN6xA57qdw8/aOkwkb2GLtNsM1Gxzw/Ohom0z8ykGCiYSk
VmVrGQo2zRokW/ZuO9bN68s4dlWAY+dRAs2Ywb0QeE1L2XlwGJiaW7efF6E8T2X4
VmVrGQo2zRokW/ZuO9bN61fiC4FaUq8z+fKBUks6tGgZfs6Lma5kU54uQlswGIQVxfzffKkvm1lDl5rcXHMFFA==
VmVrGQo2zRokW/ZuO9bN6725gn6AE4KlYGXCgdldfTHvVePxt81VDJii+UIRV9So
VmVrGQo2zRokW/ZuO9bN6xP6//zUY+lIi+gH0ZiMAHxrWVK37wh0Dd0rIYDgHxyh
VmVrGQo2zRokW/ZuO9bN65Vu8PVmTF+4Dzea/bvMcEVOXNQxxERI+pswNZxPLgGG
VmVrGQo2zRokW/ZuO9bN66+Mk4GZKdNMe7NCKER0yRk=
VmVrGQo2zRokW/ZuO9bN6+3m7PBc9EdU7lP2yNhVBRJqxygYFe0FGSSnC9/qF3UeaMY1MoDseyFI9vmbOD5IgQ==
VmVrGQo2zRokW/ZuO9bN64vo1FYdzVPQhcC0bXObVxs=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
U28wv7XxIHeRSkqXJHXzR0xpTOufJNSwabqZ3Q3ilnxCv5GA0Lu1QixaJfBttSFg
Z8UsPk1Q7HtwjRd4g01ryw==
3f+yEXMJpDOPrez5ap+fN71X0yVW9y+W39rIT+wSpcE=
ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk=
9uZN8r0CKVAbfzGtFDuvLwhOKNflpKM2ZwfKcLjWwCQ=
s2s/1ED0ebJ2NmFlE8kk0/nWNx9HXYglwLmrDDl0SJVEejxjmWbqF4tItLVHJaXz
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
1u+XjG/2+GSQRv6EzCaWRQ==
1zXpiGOddO9f/7P9ia16hLkuEytAkOvat/hMfHQ4YOA=
TlcB8lXx3B+v8kN8QpOFdfk9CF2LWs/6yNZPbaaVR70=
NhnIR3Ilo4H2su9/cTNo/ADljVwvrJpst190+kbx61cg2ptEynXXqaFn0MK3A4Gi
1u+XjG/2+GSQRv6EzCaWRQ==
UVXwiL1XIF6sRJ+E2PuG4USOrEdsFvbOxW2jp01zP+M=
APhqKflZUhmgRCCocHNfF8cWLKWpGdW2cBFVB8esfaebcV8Bm5icApbL9u/SmCTp
YA93D7bVXAwMe11nxv6BP/vWRwUT7PwlT8ufhs3UDnY7vkYUBvQW6UL1K9e7/UlQ
1u+XjG/2+GSQRv6EzCaWRQ==
LOnQOFqU6PCy7YYVSPmkA0/gTFRU2H0d6T51/GWKhnQ=
3KmFFXpaFcYe2PH7o19+Yp8xAODhI/vWpqDGX4Xxav/TPftCcwlJ2GSdqZRIVGoo
R8K/Ndjw+sVUFzGFOCCgwyEgZIFazHPxG2rsbkC4Mic=
NhnIR3Ilo4H2su9/cTNo/FK+qNAG19wxGrf6AaX5vF2MYvQpe0FpvROJQN48CKmo
vdsIgxKEnw7wmXARhJ0Oc8wVF+V4koy8W2Mh858ZTYc=
NhnIR3Ilo4H2su9/cTNo/ILZghLpbr5j/IZD/K8v92HhdXliUZ6LIaLGw2w1580v
1u+XjG/2+GSQRv6EzCaWRQ==
4lN0LWBzLjgquvZLEoGU4VcBfkPRXV3AgETwRHgwx0A=
CaVVGVWiTY/j3V1bwWG579Qd9AhVS9Fosq6m4Brd0nDzkmY9JVeKm8YUIUMgLZgj
ww1k/N0gWysBJXBpYKUCHSY84nqsRk88NU0ho54XVr4=
NhnIR3Ilo4H2su9/cTNo/PIphezILghaR2B05A8VC0vv8lXu5MHRtSnDBC+dvcGcG17D53H9BitReg2GU8a9Tw==
1u+XjG/2+GSQRv6EzCaWRQ==
m4a/CtpjJ/nMVgLJWRwZXXRLpxxOovbcYxRSxZCtA6Y=
NhnIR3Ilo4H2su9/cTNo/Gu9KOwC8SCkdYU8skYoE5Jmz6ywJ/AKKeZKyRVis9YBz+4SGikFp/xZOPqocMHyyQ==
1u+XjG/2+GSQRv6EzCaWRQ==
CQqNVWmwukkhI4nkdKzMzwBsOQ+4HbTh/BOPpsEZGY8=
bpVtspxTF2OToZILoJ1fkG7rqYhfXtulnikcgwpk7TI=
NhnIR3Ilo4H2su9/cTNo/HvYNwc7FmYMnVorYtJsgM7mPAX2E472V3DWt24sC8XW
1u+XjG/2+GSQRv6EzCaWRQ==
RAcgdll7MZc9X2PbkeTvEzNyiOpuxHuwyFCiwlCwtBo=
TcUpsBX6+7VrOYYb7aAQxi+cMqvbjHfbkHkvbweeOrI08YeWNRwxkPzTCz4gVzYxXfh+l8lWk7g+IZkT/aj/bQ==
1u+XjG/2+GSQRv6EzCaWRQ==
5wsVwKLrIjIWqeTNpSVp9X49wYIz3gKTqq7jnocq95Q=
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhAwjLxzOvQmNis0aVGbhWo1EM3d+uiOHD4+CHNQr7OvntVC01SFgrSaWBjwpxSspMw==
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrrH8Q4B8051PjWqxvRD/6G+6mZgTSaRF/+5UQJBSfsOu
yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W
FFgrrvXvjrcz4wZcb8tZVMfl7bl/3so2ckZ8r41azARzhi5rZ6V/H3mosyOtQ6kI
Z8UsPk1Q7HtwjRd4g01ryw==
eUSWcSfVc151c+2QfqQTIQ==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
b4OJVZe8QyIpjuTpKXDL9A==
wlLHv6kT3Q/RmtMBN4nDAd5ePG9boZZ1puX9EApi7waJ3F0rdpZHQ+QUGogqxN5a
VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc=
VmVrGQo2zRokW/ZuO9bN684Ix5sFjdtWSuy9M2APYQQ2cloQs45uAs7g4ZITcnIAjpBDTpZ3BrLNJFEVqJ2ojctFGKKm/95hVbuFjYtJPeM=
VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY=
VmVrGQo2zRokW/ZuO9bN67Ap1Q6VmWnNlLMMqeDi80Doeyvhi/U8GBPp5HHWZTzv
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6/n7n61n7R9/9XTW7LSvopM=
VmVrGQo2zRokW/ZuO9bN6/WTynACEFT8sdNdNup1kalU+7hlVh1zylFr4591OIGa
VmVrGQo2zRokW/ZuO9bN6zPS0z48efxTbmV0uLaJMFk=
VmVrGQo2zRokW/ZuO9bN60IbS8l3rE5TrRrNdfxL+kX3Pzn+FDHqKXiYJg1KgmXF
VmVrGQo2zRokW/ZuO9bN6wAx5J8iv3RazhpXmncqnrreXiCOGawHQMNMjUs2cCVN
VmVrGQo2zRokW/ZuO9bN67Y8U6ALWboELuY6YE4ad78=
VmVrGQo2zRokW/ZuO9bN61Rjjiw/60bucMm5EHZzOcK7qzv28rP7g4n1B0Ie7pOW
VmVrGQo2zRokW/ZuO9bN6w5+dIR4Ypp9Zft93+s5eE5/yPjP1yhzf5/NmchfJr6N
VmVrGQo2zRokW/ZuO9bN623LqJlCkyXYwwCXSySz5FR7MI/uUVbwFRZ9X3HsyodA5+hN+HhvBEXoBRCHeMLs1w==
VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy6/23foxSNNR4dF+wZRb5KjO9N7VJVsNrJmtWvnUeaVjrQ==
VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k=
VmVrGQo2zRokW/ZuO9bN6x97Jv5sxc/lnlSc+X41DCbYX0L0DMlJm79khasZsmeDQfkyT4p1zevxzJpYBx1DNhDBrlR5/iGnPwmNZ1OvWwg=
VmVrGQo2zRokW/ZuO9bN6ywyqAt0WUYk0wz2RaTbkeMb61E3V3fbNF4IaFu+N7qh
VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy6+U+Hq91CX8KUds7Nh1ZOU8nWqPG53E9RBQ5qeqTEsFmw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkt5RqaKznfoAozF2brEPS6
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmb7MAq4qksNIzY1Eh1Jjt9/P2MbO42SPrEoJqJGO8pbA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN628UWklFrPzPT9a+yjbUprIuuMwW0I9DnLu6OGQ2TxNu
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+Z1kfdIPo4MYDXeXMYYWIYQQ4mrc936ju682bxyn+TqEoaQoeHhJgY1bOPJ6ik4QTAOiG9gx9IRwXfuCJtr3Ko=
VmVrGQo2zRokW/ZuO9bN66gjfjrYMn+hEmToUiACtV8=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
qx/2dn1Vpqs4SJaPnWiGqCi/4MihcwwKbYGKhk4naTAD8DbuTmd7DIaIioL9KkvbIcxbbjYAhP1Y/OUfj1mnGA==
segQHXNRrJyLOuWc8JKC3tmNT7tyg3IVKLwZ3sHcdnRU1rBJqs6RBi6qg5fjkef3qFq/cbKaLNb11SHQv37cDw==
Z8UsPk1Q7HtwjRd4g01ryw==
K0FOgK5mh0B5pEUVGx4wW2bJABs6gWPmt6Q9o3r8p6lGdFGrw5pIphcDNNDI4Pxe
bIts5UhOLbX+5vrkUi2fEpgbk+KLUNd8ANhOjt2CA8S7qBtqBqrK7rtUY4QlsSDu
St8jsnBszzKBMqXhq/bixnxiRXhroGzTpl2GmFXSnRkSxzNQsMXQGk8FOXJV6Y5r
eeFiPlXUI0Fos82HYztSmaHvIBfKy6OirpgreAVZg6s=
Z8UsPk1Q7HtwjRd4g01ryw==
4m1ZiBOyILBTSHQf//vyC0su8pHbpnlRDppIig8J5ysXeSTJP68Ri95UQiepxvh4
WNTuVs53ESvy99Z9NIHHsQczMhznKv/xDqWjNZ9BqOGfNIc1eIJDK8jae1LRmist
sYeCd0P8QwDZsXD2zsYtpwOQuFBJwQCwG7+G1aTUb7MU2KmH7QjCjojJgGohPv0RKMRm2E/yyqe7h2CjSXfM2VbeNDXk2k3QOsue23kQf9w=
32pdC9DD05OE2l0oXazDFNsU+u4bMtJzvbtUKGSOKeSn9WvZrYyoyjfX7mEYUbe3
lGY+HGJ/DlLufZHWWlj7U5mXaKcrxjwMdkooNruC2og=
1u+XjG/2+GSQRv6EzCaWRQ==
UNfQheD+zBxMB/8PVcAnPhJH0dABCf0LFLwey6F+PqLQAVJFMjnbXXReUGDMDEqKiYFfTP5la91cu1Qzw3WaLg==
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P2UruqssNk4NnKN+47U1SH36F2BxurMzko0B0uYv45I9
eeFiPlXUI0Fos82HYztSmSfbblmQRmW8NByKq0O3ixRZii1ssvDqpHtqdGo6blGV
8ehs2Cqsj3GIU4YbS+kqMopjyYZACPJP3McICcxQ7QYvefmSdk7wQxP2oL97Q4vQ
yqM179SzW9HnBHozkU+IlY0McOeVypQ/EYlVcJDFe0GlWr6WqLSH8YZJQ3Zl80pE
8ehs2Cqsj3GIU4YbS+kqMpJNmQ5fFIbSdkmF260fowevNNXJ45tpcmfBJiEEZGw6
NjU4zQ5DRyOHitK6CHLOgHN7CZ8aB+Mb5HQXfP+yO3ibTcrsbI4BMbGHj3qA0+h0
NjU4zQ5DRyOHitK6CHLOgAOFCV3qwh/1iA/qlJwZgoGUD8smj9/Bg0H/hh9Oqg6m
wOl7lRII4ZgqRTj9M//0bzhomxn10Rjo41lvXMmPQezzGWk3GyeNMpopZ/PorkQi
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
D9SFxmTjfXKVjBp30+qz3QUFQ0c/jfdN4cWLcYJvWW8cGcuYOqW+zTq/dn4uaKX5XJmBCPK7U+oFvA5D8u9eQEuYG8CwsAxp5IrQkGPd5mY=
MQu48JkHralDtNXBUYl7lV0bLxYB+cVYGPW5I5H4uBmKn+X1vLJuCeoRB5MaCEB5bkAaA21+lVtVi8XnA5uCfg==
VmVrGQo2zRokW/ZuO9bN63kQ7naUTUtv0ESWGx9rzIn/1UUCp4+jfPXGiX14BdBQ
1u+XjG/2+GSQRv6EzCaWRQ==
hxgdAHD9AaIGGBvLsEuyB8PrRl5j0RLWnVrakTTrC+wYlY21ONwMz70K497xaqGJ
HiVf4YuHR4gw4rFUUeRvK+5LSyMNmU1h13+cLC9tnC/y8cnqYfMBKdGAnQDTVZHb
1u+XjG/2+GSQRv6EzCaWRQ==
O3CUgrw2GJfB+mDjH5+NdjDdFbdZzMNFPWLs5nIbjTo1SpgjDweH2KPbFtbB1QDS
VmVrGQo2zRokW/ZuO9bN69uA6kygxk2cG8IYgdG9Pr0+kDHcaDqpPo1aFOqQ6eZcFBrY15aCLHK16jKxHqaXiw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6wcnkGR1Qyn1s8+CW5+MNtzRTbQfpmPYWYZ1Zix4ol4Or3JWa7MNuH8FVEl+/LFhBA==
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6ydgd98GwzWUciEurhqUbl5zFKBgTvp0P9ayAL5RaMdP
VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNq5a5scduUEPHPFtX2wTOnBqbjEynLbHNU89K1bs5Z0IYpgdoxUyCGS8wiq0EgiinY=
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69NIyxsu9wL3ButjWi8Vd9WFqs0F/cbYpifzswbVj1XE
VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNqDts0LCwhPoG9qpww/bbUj9JcO334yYaFKIcL8wQwB6/PsreYi8O27v2MuAOqvCx0=
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6zLSeF3gHkc70v8wH1lH7V6izJDceK8v1VqgXLP7PPFh
VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNrIogcyFIfNvtgKGel1Eosz
VmVrGQo2zRokW/ZuO9bN6wJqlk/efd+QgKBb45lQkEUJURJ6AXySYv+T873/0906R7ZDY/OqM+pHGZhlC5mTCLRAh2YSqtgcguEgy7g+lDE=
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN61W5fU8yZndDfCoUS7iF29y1q1oDpOjeucxethGOXiRJ
VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNoJZTLcbrqpw1/MqZAUh2h1
VmVrGQo2zRokW/ZuO9bN6wJqlk/efd+QgKBb45lQkEU/1xIS7OZ8UVIpUgSlst3vnPzuyqFcv172ahiYDGVFzqHNYalk2K2+ZiH00yJrmFg=
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6zHe1AdyxhaUooAL+hUKQDTEHrKgluuXbnSBJQrzZUMq
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhFEcylaBQH0IUk+OusBE3my0xp9xwF5QGoWXAy5mxBYi
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrvU3PfyPPxNosxB0yTLMifo=
yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W
bWa5NLmMcRfP7/pL4TxwVVPLg0xyCs7rLJn7q0oXZoI=
Z8UsPk1Q7HtwjRd4g01ryw==
b4OJVZe8QyIpjuTpKXDL9A==
ACEEdgL/kNbx7RaZcSRxZATKpSMTQ/cWmiIi7MZqRD3iHlJkfgQKP2ZXgOycctOR
1u+XjG/2+GSQRv6EzCaWRQ==
PRG/YRzXVD8iVR3bzEcUnm+txOUxEzRjM53h5sTJkBazFR7SuI0t8ZpzuPGzrZlm
wlLHv6kT3Q/RmtMBN4nDAaa/4pYRsvL7819NLBZ1Jsk=
VmVrGQo2zRokW/ZuO9bN6w1AyfvV+/TKcjbtKlFZIXTurM+4yW7LDThX3d58hOXr
VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt
VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc=
VmVrGQo2zRokW/ZuO9bN66vfklQEmoLc3ecqJIXu87NMH8vbZoK4tpkEq8BGvWvlPTpczQ026/C1jKPPSYdvzg==
VmVrGQo2zRokW/ZuO9bN66SC7guo6F2BUtjo1cx7Weub6mMMOadJGmLmKcw75lpb
VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY=
VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8=
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhORIPeJCp4wWVvEEOwBGJbMQ9B8g68lVqaXG7/c8sCgL
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrn/Ahs5WLg1NPCie3zGShu1/wSHjnFYLODLD25lQA5x1
yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W
8ehs2Cqsj3GIU4YbS+kqMtuucPAdJ1i3hn/BkXRR/b8uF7yzHSGAxk7z1KZFvBTC
Z8UsPk1Q7HtwjRd4g01ryw==
b4OJVZe8QyIpjuTpKXDL9A==
PWKGbNEPxxxpGpe8THO242QunNNWnvbBCn+iC2oElyc=
VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM=
1u+XjG/2+GSQRv6EzCaWRQ==
JlxIfINDpIN74VOPKABZW2ra44tSxs0ZLHEM/U3b4xykot9XDTMkG244y4YCVtPy2qfh3Sl0bgJRhkuNbAnsLw==
YA93D7bVXAwMe11nxv6BP/8iLYI7Aa2oVDOTO+f6qRg=
VmVrGQo2zRokW/ZuO9bN66Hq/Y+eYT69h50gLi7IyQOOipSIQ7Q/7rFMIt1AIZov
VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN69tIcUnkZWsvMTtwyCmn6vvN9UwZdc3KUcaB2lz6Du2A
VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8=
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhDsDXpgsqOpbKETjou9WOZPcc1elPxQh9cS821Ok3bVbCXSJ8u7r7Ro1XiEX76I/iw==
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrj777l5BTo9eCcYE6KEhGN3qR2uRts9okQ1lptsdQWtg
yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W
yqM179SzW9HnBHozkU+IlY0McOeVypQ/EYlVcJDFe0GlWr6WqLSH8YZJQ3Zl80pE
8ehs2Cqsj3GIU4YbS+kqMpJNmQ5fFIbSdkmF260fowevNNXJ45tpcmfBJiEEZGw6
Z8UsPk1Q7HtwjRd4g01ryw==
b4OJVZe8QyIpjuTpKXDL9A==
jPjjou9VNwkRtlQvS7IMwT61DiJZ1n7ABVvC8lImM1GTyercqKulHtnPhz5LMb0WCoJOixq+LHAXxH4az57sJQ==
J1UGlXkKhKXD36OrXXhN8LEiN+9Y+rPtU094/lpWCJkzL91nJbwePJFLFNxcU7a0SlIO5OZ3MwAWum4zIjkPgg==
VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8=
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhLHqHL4jLTDrGAIPR+WsUzXEFIbEmBGSwvvQLutL0dFcpH0AUPQoXrj49knrv9+d+g==
Z8UsPk1Q7HtwjRd4g01ryw==
zZQPiCGCqSDO70yiwDNUrm7HG9OhPLBhZEBfEtqJxcB/kpanL2zAPOvr56+W9KHv
yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W
NjU4zQ5DRyOHitK6CHLOgHN7CZ8aB+Mb5HQXfP+yO3ibTcrsbI4BMbGHj3qA0+h0
NjU4zQ5DRyOHitK6CHLOgAOFCV3qwh/1iA/qlJwZgoGUD8smj9/Bg0H/hh9Oqg6m
Z8UsPk1Q7HtwjRd4g01ryw==
b4OJVZe8QyIpjuTpKXDL9A==
YdTM5cWYAsOq9968GjmEIn458PkySNd5GOJg2QjTSgkgpn8VUITEbXvbBG+yQJ0k
1u+XjG/2+GSQRv6EzCaWRQ==
gZ7BoIAABc+I8g/rYzq+f9fH+SgIZsS8Qfv+e480JkPCt3tQj/PL+v5Tjao1wdEQSyGpe2xx/8OpBGm95SqEpA==
VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU=
VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8=
+287 -287
View File
@@ -1,287 +1,287 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
import copy
import random
# 获取目录大小
#------------------------------
import sys, os
import json, os, time, re
import public
from filesModel.base import filesBase
panelPath = '/www/server/panel'
os.chdir(panelPath)
class main(filesBase):
_exe_cmd = 'ncdu'
# 扫描历史
log_path = '{}/data/scan/'.format(public.get_panel_path())
# 缓存
cache_file = '{}/config/scan_disk_cache.json'.format(public.get_panel_path())
def __init__(self):
self.is_use = False
if os.path.isdir("{}/plugin/disk_analysis".format(public.get_panel_path())):
self.is_use = True
if not os.path.exists(self.log_path):
os.makedirs(self.log_path)
if not os.path.exists(self.cache_file):
public.writeFile(self.cache_file,"{}")
if os.getenv('BT_PANEL'):
self._exe_cmd = '{}/plugin/disk_analysis/ncdu'.format(panelPath)
def get_path_size(self, get):
"""
@name 根据排除目录获取路径的总大小
@param path 目标路径
"""
if self.is_use is False:
return {"code": 404, "status": False, "msg": 'Please install [Disk analysis] first !'}
path = get.path
is_refresh = get.is_refresh == "true"
real_path_dict = {} # 软连接处理
temp_path_list = []
for path in str(path).split(","):
r_path = os.path.realpath(path)
if r_path != path:
real_path_dict[r_path] = path
path = r_path
if path != "/": path = str(path).rstrip("/")
temp_path_list.append(path)
try:
cache_data = json.loads(public.readFile(self.cache_file))
except:
cache_data = {}
result = {}
path_list = []
if is_refresh is True:
path_list = temp_path_list
else:
for path in temp_path_list:
if cache_data.get(path) is not None:
result[path] = cache_data.get(path)
else:
path_list.append(path)
if path_list:
scan_path = path_list[0]
if os.path.isfile(scan_path):
scan_path = os.path.split(scan_path)[0]
for path in path_list[1:]:
while True:
if path.startswith(scan_path):
break
scan_path = os.path.split(scan_path)[0]
import string
code = "".join(random.sample(string.ascii_letters + string.digits, 8))
result_file = '{}{}'.format(self.log_path, f"temp_scan_size_{code}")
scan_time = int(time.time())
exec_shell = "{} '{}' -o '{}' ".format(self._exe_cmd, scan_path, result_file).replace('\\', '/').replace('//','/')
public.ExecShell(exec_shell)
scan_result = self.__get_log_size(result_file, path_list, scan_time, cache_data)
os.remove(result_file)
result.update(scan_result)
public.writeFile(self.cache_file, json.dumps(cache_data))
for r_path, path in real_path_dict.items():
result[path] = result[r_path]
del result[r_path]
return result
@classmethod
def __get_log_size(cls, log_file, path_list, scan_time, cache_data):
"""
@name 获取文件或目录大小
@param log_file 日志文件
"""
result = {}
for path in path_list:
result[path] = None
data = public.readFile(log_file)
data = json.loads(data)
data = data[-1]
root_path = data[0]["name"]
if root_path in path_list:
result[root_path] = data
else:
cls.__get_sub_size(data[1:], root_path, path_list, result)
for path,info in result.items():
if info is None:
continue
if isinstance(info, dict):
info["type"] = 0
info["asize"] = info.get("asize", 0)
info["dsize"] = info.get("dsize", 0)
info["dir_num"] = 0
info["file_num"] = 0
info["total_asize"] = info.get("asize", 0)
info["total_dsize"] = info.get("dsize", 0)
info["stime"] = scan_time
cls.__get_stat(path, info)
cache_data[path] = info
else:
cls.__get_dirs_size(info)
cls.__get_stat(path, info[0])
result[path] = info[0]
result[path]["stime"] = scan_time
cache_data[path] = result[path]
return result
@classmethod
def __get_sub_size(cls, data, root_path, path_list, result):
"""
@name 获取子目录数据
@param id int 记录id
@param path string 目录
"""
if len(path_list) == 0: return
for val in data:
if isinstance(val, list):
sfile = f"{root_path}/{val[0]['name']}".replace('\\', '/').replace('//', '/')
if sfile in path_list:
result[sfile] = val
path_list.remove(sfile)
if len(val) > 1:
cls.__get_sub_size(val[1:], sfile, path_list, result)
elif isinstance(val, dict):
sfile = f"{root_path}/{val['name']}".replace('\\', '/').replace('//', '/')
if sfile in path_list:
result[sfile] = val
path_list.remove(sfile)
@classmethod
def __get_dirs_size(cls, dirs_list):
"""
@param info 目录信息
@param result 结果
"""
dir_info = dirs_list[0]
dir_info["type"] = 1
dir_info["asize"] = dir_info.get("asize", 0)
dir_info["dsize"] = dir_info.get("dsize", 0)
dir_info["dirs"] = 0
dir_info["files"] = 0
dir_info["dir_num"] = 0
dir_info["file_num"] = 0
dir_info["total_asize"] = dir_info.get("asize", 0)
dir_info["total_dsize"] = dir_info.get("dsize", 0)
for info in dirs_list[1:]:
if isinstance(info, list): # 目录
dir_info["dirs"] += 1
dir_info["dir_num"] += 1
cls.__get_dirs_size(info)
temp_info = info[0]
dir_info["dir_num"] += temp_info["dir_num"]
dir_info["file_num"] += temp_info["file_num"]
dir_info["total_asize"] += temp_info["total_asize"]
dir_info["total_dsize"] += temp_info["total_dsize"]
else:
if info.get("excluded") == "pattern":
continue
dir_info["files"] += 1
dir_info["file_num"] += 1
if info.get("asize") is None: info["asize"] = 0
if info.get("dsize") is None: info["dsize"] = 0
info["type"] = 0
dir_info["total_asize"] += info["asize"]
dir_info["total_dsize"] += info["dsize"]
@classmethod
def __get_stat(cls, path, info):
if not os.path.exists(path):
info["accept"] = None
info["user"] = None
info["mtime"] = "--"
info["ps"] = None
return
stat_file = os.stat(path)
info["accept"] = oct(stat_file.st_mode)[-3:]
import pwd
try:
info["user"] = pwd.getpwuid(stat_file.st_uid).pw_name
except:
info["user"] = str(stat_file.st_uid)
info["atime"] = int(stat_file.st_atime)
info["ctime"] = int(stat_file.st_ctime)
info["mtime"] = int(stat_file.st_mtime)
info["ps"] = cls.get_file_ps(path)
@classmethod
def get_file_ps(cls,filename):
'''
@name 获取文件或目录备注
@author hwliang<2020-10-22>
@param filename<string> 文件或目录全路径
@return string
'''
ps_path = public.get_panel_path() + '/data/files_ps'
f_key1 = '/'.join((ps_path,public.md5(filename)))
if os.path.exists(f_key1):
return public.readFile(f_key1)
f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename))))
if os.path.exists(f_key2):
return public.readFile(f_key2)
pss = {
'/www/server/data': 'This is the default data directory of the MySQL database, please do not delete it!',
'/www/server/mysql': 'MySQL program directory',
'/www/server/redis': 'Redis program directory',
'/www/server/mongodb': 'MongoDB program directory',
'/www/server/nvm': 'PM2/NVM/NPM program directory',
'/www/server/pass': 'Website BasicAuth authentication password storage directory',
'/www/server/speed': 'Website acceleration data directory',
'/www/server/docker': 'Docker plugin and data directory',
'/www/server/total': 'Website monitoring report data directory',
'/www/server/btwaf': 'WAF firewall data directory',
'/www/server/pure-ftpd': 'ftp program directory',
'/www/server/phpmyadmin': 'phpMyAdmin program directory',
'/www/server/rar': 'rar expansion library directory, will lose support for RAR compressed files after deletion',
'/www/server/stop': 'The website deactivates the page directory, please do not delete it!',
'/www/server/nginx': 'Nginx program directory',
'/www/server/apache': 'Apache program directory',
'/www/server/cron': 'Scheduled task script and log directory',
'/www/server/php': 'PHP directory, all PHP version interpreters are in this directory',
'/www/server/tomcat': 'Tomcat program directory',
'/www/php_session': 'PHP-SESSION isolation directory',
'/www/server/panel': 'aaPanel program directory',
'/proc': 'system process directory',
'/dev': 'system device directory',
'/sys': 'system call directory',
'/tmp': 'system temporary file directory',
'/var/log': 'System log directory',
'/var/run': 'System running log directory',
'/var/spool': 'system queue directory',
'/var/lock': 'system lock directory',
'/var/mail': 'system mail directory',
'/mnt': 'System mount directory',
'/media': 'System multimedia directory',
'/dev/shm': 'system shared memory directory',
'/lib': 'system dynamic library directory',
'/lib64': 'system dynamic library directory',
'/lib32': 'system dynamic library directory',
'/usr/lib': 'system dynamic library directory',
'/usr/lib64': 'system dynamic library directory',
'/usr/local/lib': 'system dynamic library directory',
'/usr/local/lib64': 'system dynamic library directory',
'/usr/local/libexec': 'system dynamic library directory',
'/usr/local/sbin': 'System script directory',
'/usr/local/bin': 'System script directory'
}
if filename in pss: return "PS:" + pss[filename]
return None
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
2unYDUJ7AQka0zNDbcMACQ==
PDg4EOFKN+dK2UrpwNWHLw==
svfNqiZI0zsAZVvJZhVKxDnsn+e2c8eSwsWdNJ9ryew=
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
Sm95VfslcvrZUo7dRR9Ibg==
1in3eluyrI5kuyCXT4wxGR0BSfUj5K16kanLx7m3x4E=
1u+XjG/2+GSQRv6EzCaWRQ==
XIfdJ79nObMM+vyAmKbTmw==
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
1u+XjG/2+GSQRv6EzCaWRQ==
VD9zfcC25A/8QOaHOhC/08+yyCMXeBqRf4/ga7jFrTkT7C7mOGjvtRvT5O1nKry9
P8yT4+bsOs+HQJp87m1F/reOtBiqflci0dOv1dBJ5CE=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
02raNMOqWaNt7xMESPC3lwgVxysXSXHuDQdM35j2P78=
20lLzEXEs+ueAd+bOAB+91r8jzaQl96Rr4UooYVAhek=
9UNCW/IYJm3y0yDOfWJu1hDSEjrLqqinLz9ENpiz7Bp7U6FvhbSHpw2fG7yfwZm2o8XmdX7QFCP6PAgeC3qwLA==
5C9WmrmzTMoEG9WnQlXcow==
7dl/3jjStHv3YKwNaOrbP+Oo5xU5m7MdajxYoJc85S92TEuqkIbRI+ohiQ6vWWtZzZM2CO+G/Tt7tZ9oQFyeGBfi+jfxFGgmnYMxeUi1JaJixQo/7XHqj5ninApCrSrr
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
OV1bY3WjlWolVVGpf7K0XuXsoXQHr87ax8nvHq6xsHk=
WHkOzVx7seuLmxs5Hu+l/t+vqO/0m+n9UCf5LD77H/SwGtkYMMmsF4cVmuhhatEVLh7WBABwVLMyOLWUBB+RadwWiICXHtE1kzcSxcgplF89E4ZHUf3MK6MmlUz6ew99
32pdC9DD05OE2l0oXazDFBSGVTBsjpcBhCs4oxG+0yc=
wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9jMBZ/1Gnw5H1SVochJBBx+
NqhMbY8+EQI8zhTG6Zh2I8eIwd9vy9oCMj+HlVpAR/l7CFW4GiuliNJFtFnBA50C
wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9iDQP8kO4z+Nh9k9xWY+COM/KxrWvWZ4Fal5fq021KjFA==
xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+nolHQ42C9PtbiSn1F6OlCtRmnEtWURclz1HQwAElCBzw==
NHEVGZTYwXqY5vJLoK2Wy88Jwk3YGlNccZ+9V9zZnU+eb80uFKXdlJTW19PQSswv
32pdC9DD05OE2l0oXazDFCUyxYReX+Pd+b7Itml48JbsM7EEkGoW/LLDCJHqhTgLDQkOzUwuHM/Ow28K/8sDHIyBKBfp8UiP7Spf6jDWw6w=
1u+XjG/2+GSQRv6EzCaWRQ==
doXPckCdzE7wjT67BB6bRaf5yMsyg97qMshMoQ7p5vyoCIqIGhDYBeIX7pK+6SIf
Z8UsPk1Q7HtwjRd4g01ryw==
2e9aPy7fN0yHEJSt/vm4HRBZEsq1/zwflCBuSjOe7lpc8+97QgE8TmPh9W3s4bjTFqkssBBzUB3kSkpHlHBB8A==
eeFiPlXUI0Fos82HYztSmdKUUr+D7SPt33f2BsZZi0+Z/4tXriyUkCyekpFOTLlk
Z8UsPk1Q7HtwjRd4g01ryw==
MzOj4ZiBOJDNVP8vi/lFQNbYGt1FVk3Is6YVV6wzt87cMUguwHtxV3/Xy4LTsg2e
L0eUthVnpkGsmKFAX6d+uEP20LPBDczIBUCH+eDLR4XWtYJ6VE0yMGgVZeHE55+Q/qVjwg2ycnFdmwmE1t4pQK7rMwd5XeH6SKf0j0LJj1rcjuZWiUfgbRgMQOHfmWeVoHXJd8Gdujzjmu8XVaU3Yg==
5zgvKM85O5RcGwttJ2rWIPdwxXoXLnOjmhL6DYLhvpA=
R7vEIxyjiiVKR30PQVdBXTc3ZCpAzCM1mvOBtDg5HBdxFY32n+U4Vd4HmL3xGJ0J
1u+XjG/2+GSQRv6EzCaWRQ==
ziQs1GyPUQ7zPh2xP+c4C9jWdniwpr48mYVqFpshpfPIMeXmT5sEphPHgd7xjv51
ygXqV8JYtXUVMKjwSB6Bx/HL/6ZwOKweTtEx9fTOy34=
RDZxR0eU/0YxwhYtovyGeVmHi5rJUJIHwjySNLmeLsS/YgiTjCQe80TqXYEeRodv
diZ40h9D+5PatJtYF1tU0duwNTXVBazmSgO2LE1bzRxihfbnQEKkYw7WpzZBa6M4
UbJuac0dxLiN+5ocS3w2vVbjWbzmARN1Q6nZRO+hN/U=
VmVrGQo2zRokW/ZuO9bN66bxma0YBg8TGr7zrmdMIaysrwI2tOjz2KGqNWCW4fyv
VmVrGQo2zRokW/ZuO9bN64fCWnvBG95BbHi3cQzmuAc=
n/Vg/an64od1JqfYD8zjtnTgUj5eucdPqy9I4MCTvJdvKMaMV+njWEGl8CrMjCbman94UdMu8eZchGcaRr1oYw==
A7tBmpCaSLjSrSFXFx4rD5DetX8LfpTB33kFqMYK0Od6ZH8Gs/Y11F09RX46+ICL
1u+XjG/2+GSQRv6EzCaWRQ==
b4OJVZe8QyIpjuTpKXDL9A==
7jSw4GB3eBRbmC1MzdtYH7AtgesWi6JQAvIQf6OzE3nseJy0QnGK6G6JTZ8m4DdlPAzSNTALqHevuq2kA19SCpPCP9OhqpP+gq0PaqyDu7o=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
7jSw4GB3eBRbmC1MzdtYH8hFhUduGUWsUH3mRmUlIWM=
1u+XjG/2+GSQRv6EzCaWRQ==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
1u+XjG/2+GSQRv6EzCaWRQ==
7S/W1w5l8hNVTCb+pjBU6IQBp7oTWR1btD/WKY+MFgg=
nI4TFpKsgYpjAeaQko6KTiS9GERA58xL2uQkIWwz+8U=
9uZN8r0CKVAbfzGtFDuvLxswrINnjWvMwuMvv7mQFLJ/lDrB0Yzi1jeGmHGjb9sx
l2FJPs4YkAmmok1ulDRuSA==
wlLHv6kT3Q/RmtMBN4nDAf5jouZDDJebuZx0e+GqD0kRo3kF/7jgrUa9LjtTIi7k
VmVrGQo2zRokW/ZuO9bN63WXNO21aDfpqHWqTMbiAspwMoEKToJ9evkuxTBcHidMf81BXfmdf+zT9d8k7rZRQQ==
VmVrGQo2zRokW/ZuO9bN6xFqwj56iFFIvII+sEi91i3uqenZ1RukB/iwA5+BqIZ4sn4SaQnBu9P1A4Q+K1sZtQ==
VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY=
VmVrGQo2zRokW/ZuO9bN6zviB9IGvmdqavzf213hyy5ybw9UFAPNCxKWXRAzML9G
1u+XjG/2+GSQRv6EzCaWRQ==
WJAfjl0WrD5Jtca2LHLz1bmB1KYomdySnhMTj+CfBFQ=
X9N2KsRYiOx20Z7QjlcKwPlYTqzzndgDM6lpBv04BpdP5Pg3w1osXEPQndKx0rQg
O3CUgrw2GJfB+mDjH5+NdjXyt5QeWCsfAO3Xba+4ChG/q7Y+X5y6P+nc7wDmMC42
VmVrGQo2zRokW/ZuO9bN62VgOORuMBXqkUitwmHVIrDnse5ewM1R/I5hzCpTRGjRPjKi5jw4W/gnKcBGD9YBwg==
wlLHv6kT3Q/RmtMBN4nDAb3TgOhh+jcLpHwNqZ402ohizeRChhJH0mnfBvfdrTFZ
VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk=
VmVrGQo2zRokW/ZuO9bN6423Hrz4GlH+nD6sJ78NwiKP8lp8FUcPhY9VQRWbCG1fwt6D/Suz+3Mw4fD7HRaoaw==
VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc=
VmVrGQo2zRokW/ZuO9bN69ZI8jvRenlEUWjnEq2NCPyDOWUWUPrjde05ZcnIjqs3pWRoX/dFNNNs2i/oTDcoYA==
p9oVsBgcyiBMjDb8CcPOqP1vx7YoaEO9HCXBZO2TNso=
Ds0U5vkLcQbSiWM0OBg+t4r9+nkADB6+pTfs6BnR2ZxqURGwpq1kta+TPw+SFYxBQFmX+7Si0OBWn9a+xTaiWnluFS3e3tzgqB5tisWnRARhFcyQwPYfaZhQ4qC+koZE
o6QhOIN2Sc4SHELnst17uev8ILgDsE8F6Lr4r3FiO5kX4kPxG5GWPpKuy8MG+3Pp5ak0QKFbZROCqcYHAtfrS5iit3HYIQ4qteW8Mhs5eEUKNbIOTh/MQvOi25rbe0W/
X9N2KsRYiOx20Z7QjlcKwJ/jTfZo3KZRc7Jt0Yxl4gCUzaVNOZtVdc33LG13QSPt
dAlD/BDiyhUNR82MoKY54wOQA9Y9TRrid6IAzqKFFYlKwy9skQ3yRe3T15WYhesi1YWjlAGeJABXbkoJuF6IIk7XVJH7co5nkqJxZSD49Qxzny6hNig6oVk/d6uY6w1QCaRfrBs5nTcMZExJVAB7WmDhGKieI8G2bZn9lROj5A4=
xWoGNWjKGPfI4gq8aHoTfM/GWNK1ohgau/P6Yy5hh4NxJd/Z/S8GTdLTsA9JjQHV
X9N2KsRYiOx20Z7QjlcKwI4qbj7KfnKOrIFv7Z4lLtGUYNfIhUqPnWQkQbUEaS3fINZ8F83Vu3A+GF1Unidx3ROcHOnR+dfswdftnV7DDxk2JxMxGEO/rdyz4HhDx1HR
gC7pmugJ3/w5trYRkDiKMZu2feMB3hGfqRh2TQebwmSMQbtzCVrS+b4WQ4vQR9dB
o6QhOIN2Sc4SHELnst17uZEi6P73uFYiKT8ZfdKaq74uZIFYFu8J6CF85QIftAeI
xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+kuoHiBgr/0Fv8qNCoSuQiv1gF3L0eu9kJUZmaEmUpN5x4xDhiRRncANufGyvnI5q4=
5eMaCO4xAK2QW4QfX+0YtKbMF1jS3cxAAkXneuE7di9ABuvCVnZ4SdZbeTDi2qoaSmKkDI5m7fh2F112P2pzKA==
o6QhOIN2Sc4SHELnst17udxbOsHgV61axCR3P3wfex8g46ZP1Sdx4i+CYHMTr6rl
pZu1Giln7AYpCOrLbI27d9Gmita51EFD9OUkFL56AbY=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
+qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk=
k7Mgm/6UlihxNIWiDi7/FQWsPKdojVfNRBOtHAgYqheCYcsU4cUBPL/6I95dDtlT9USt45h9FK4d7rl/Yz+V2cXy5SkFT71kfnrU3MZE6nE=
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P3oJY/mwDRD7KWNhsy6/Y2mb4RNPwLuEAcMxo+t3fDjM
bIts5UhOLbX+5vrkUi2fEgVzsPbCG1s61jTt5yW6ACsP1ywsg+01aUDIv4DP01qd
Z8UsPk1Q7HtwjRd4g01ryw==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
RDZxR0eU/0YxwhYtovyGeVX2L0QGVwLQX8TIwLObEVs=
o6QhOIN2Sc4SHELnst17uQC18YJWaV/6CEkrqfg0jS20MlFgiNUSShqPpoyfxdJZ
HNr5/wGY+7coHgowTe94Ll4RZO1qTURSl37kSeltNLP9Vb6jtwiW6YkMWV99p4O4
gEJ/mwYAYmzZtF+bOEtqdkC8SHP+P7fOYSe7UxEq8rmitn+SghIfUQayzs5YBbRY
nWJfxfGKXK3M33nhNtnE4rhyN+G2GEkqT49HwMRlHEY=
Vh41Si6Af7eSg+/W8dfTM7yg2G8U6j8xv+R+xKztdB06IZseCUPYnwY/NdWIvEVs
emk1Y/Al74+4sZ82aqgV0lhGJemXCaG1r29Gpu+zhWmouexK5AYktd5VzhuOMg2z
o6QhOIN2Sc4SHELnst17ufzdxJH6EiDMn2kcCw/f00XLHkMwjtUD6soxHwPjfI56
l2FJPs4YkAmmok1ulDRuSA==
+Zjg8k4ZNVjB46NJnSn8ErakeJYqLEw9DhX8QKCz44c1fatMLaKfX4kADtdWsVo0m3DBdTfktt2+zvPnSnSwlvIi2oJRgthe7eFBbm0/pMs=
RDZxR0eU/0YxwhYtovyGeVzRqxe/x+r1aScdsXNjbZEDJM/n9+iJeJ6fjq5jg85x
1D18KWr2hdVsBcdZx1OaPQlScmRtGt3pyfQ0+O6DoOA=
VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig=
1D18KWr2hdVsBcdZx1OaPWpmi5eWzbYhJFCY/NY8C/Oa7c/XoIMLf5OfmxNwBC4p
VmVrGQo2zRokW/ZuO9bN69Xj6ooDVrRi+Qx0VVbxxuGwXblBGt+UMa3iQsUS+MLX
VmVrGQo2zRokW/ZuO9bN6z0Wv8dJZfITZaz7/LBnekh8eKiykXRkJuTroXNsk4sHsjMzx3ehYnBiRsLxQ3l1Ew==
VmVrGQo2zRokW/ZuO9bN65vHbxdhaeboWCi24FUvfUKxwpfWxuzCii3Vfl81U5n04eCJ/I9xuyAFs+a+utQoLA==
VmVrGQo2zRokW/ZuO9bN66hhLXcZ90iz+YURPNpCdCsAm2RnkTlvB3/dCAYY43QH
VmVrGQo2zRokW/ZuO9bN68uGmvNP6wueRXu6UnefD+wH+Yr2ww3IH83h4hC1VuyV
VmVrGQo2zRokW/ZuO9bN61sfi9NDWbb3IEw7V243XdGQhqhFSYbmb3HIkuCdE2VLLMjOBsOxdHyxz3Pp+dE5gA==
VmVrGQo2zRokW/ZuO9bN6/jrSn0BMJUjTRmq3s4EygRutAv5minFbaCDNSpGOnuGOGbAGxzYhr5FfgbC2LAzaA==
VmVrGQo2zRokW/ZuO9bN66KELFqGTvIFlqiTgxQshiEJtYy+NLL3drj2rKA5FLSA
VmVrGQo2zRokW/ZuO9bN69qUnlBYp99FW3NU5/GK+PzoHh9gUCjYvnxOqd2JqPw/
VmVrGQo2zRokW/ZuO9bN69yVdWZ+lX5d/HxJ8n7Oe9lsFn7ww5+jazR8dqP2+S/V
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN6+bUoxQpnmE0JVwLD8EwzQ/H7dsGXPHJ9cqQZTkojMQ+
VmVrGQo2zRokW/ZuO9bN69qUnlBYp99FW3NU5/GK+PyS8CBUEJbhmFgtsc9tZNZa
VmVrGQo2zRokW/ZuO9bN6+EOVn5y2jsFMxK6Zwn+wS/mDi05KQxr36Au3RyQiAZt
VmVrGQo2zRokW/ZuO9bN645GzHl1E1lX49ZzqICVdNd+qq6XjowUV36TmrdcDLlJRAtaa5meR0QErawnLRsIbw==
VmVrGQo2zRokW/ZuO9bN69yVdWZ+lX5d/HxJ8n7Oe9kengQKSCiIkCs6PDQVWcCGpn3+rLZxeaOALBnRhwQmjw==
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
+qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk=
Zj8NVVe0emTHliAvlpCAerp3iS8Ldzanll9i5zjVEzHQg8mhLB3GjOH9WUhexCJRTK10ie4Li3ku0ivs8PwncfL0CsJ9wuEyiySAnQ+YkT4=
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/PxQtnmghDNkLHWxwLL5Pfd80TPKT3Fq0L6v1zn15TQIi
LikBEoRjt8wwWzUZNw+jJgWwZ72bx/3Bg25s1ZrnmbQ=
eeFiPlXUI0Fos82HYztSmQX4Tn/GgRQusZFpFlvVnwGj3wnNVeLP8HbDx9FBGXTK
Z8UsPk1Q7HtwjRd4g01ryw==
ERFPcpFJ5x6x1iUKDQh3uuNBeWAGk63K78XGpIjhm2+rBpfY1aEG51pD1dP3DCm1
mDvBoIQgD/LTzVDzjae4UJqJvNLzaOzwnnIlpmmAT9w=
1D18KWr2hdVsBcdZx1OaPS9U/g0ilrDQencOJy/eayhgybOUx8AzmnF0FRkjYK4+
VmVrGQo2zRokW/ZuO9bN68DMZ7Jr2bE3tmVz/lTLfwFI/TgA0E/RsREqTEyLRZTtM7CfvidC3a+UoB6eKaAipGC8lyEDiDjU2OcuQXtivS1abYS5vnbaL+qjgg9xvxZW
VmVrGQo2zRokW/ZuO9bN6zAeYkrMwd7SuWC1RgnERpd/CtxQ8iNGHrTJa/V2vhqa
VmVrGQo2zRokW/ZuO9bN64O9/l1Cw+LCUDbA/A3sef0aZa1fkxQn/BGfLBYcyIz6
VmVrGQo2zRokW/ZuO9bN60zutLkqghuDP33toq5yXb5QoAWEq42gUIT7CcbtapHA
VmVrGQo2zRokW/ZuO9bN615f1QfyDTLI7xBDK4WokeoZ/LXykw7EJtsaIC8dNCUB
VmVrGQo2zRokW/ZuO9bN60/t3y383iQehg4Rf7JHddlsYUFMmzhkXzzyUyBqcUubPvH2W0j6argePbntySyUzU3AV893eEQa7Yym533kWfk=
F9Sftf1PoN5P+lgvc+r10GOWQ9m5fvKDb6g+UBNuAqJgppHlIonewfzaqeYuks8R
VmVrGQo2zRokW/ZuO9bN68DMZ7Jr2bE3tmVz/lTLfwGYa8KblejDwqMGy0HS7E4EgxqdWnOJ4dMlPPf1V/WbB45LajcY2+ifZj2xLXa9OfFHv0/Gwu03ahXrFxuCROiu
VmVrGQo2zRokW/ZuO9bN6zAeYkrMwd7SuWC1RgnERpd/CtxQ8iNGHrTJa/V2vhqa
VmVrGQo2zRokW/ZuO9bN64O9/l1Cw+LCUDbA/A3sef0aZa1fkxQn/BGfLBYcyIz6
VmVrGQo2zRokW/ZuO9bN60zutLkqghuDP33toq5yXb5QoAWEq42gUIT7CcbtapHA
1u+XjG/2+GSQRv6EzCaWRQ==
+qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk=
uvcMgENha6hOb5ExkFYzxifcHMl1I6jrVqfUC4eNgIoj4KJOBKY4nAmoO9a/O2O4
Z8UsPk1Q7HtwjRd4g01ryw==
LikBEoRjt8wwWzUZNw+jJnTofnNhv2W4ginDjUT/BhyY1a6MiWTuSLmwGyGxJyoG
OOMIt/WiNRdlMd3fGCaVnH8L1KXFwR+AJLHaxenwu6I=
Z8UsPk1Q7HtwjRd4g01ryw==
mLDy5md6q2DkMFpvLtFlrbgr2CZgO+U3exRgHQl+WcSH91VecYi0XBGIdUBcdsZg
mLDy5md6q2DkMFpvLtFlrQOIllMJ+wGUeIFDucuWhfM=
mLDy5md6q2DkMFpvLtFlrU7D4Mf1ddl1j/aFZsBH/L00FFBrPDB3T673ZTtvJXi7T4ttNDvQwxI44e41cn9A6A==
mLDy5md6q2DkMFpvLtFlravQqYs0c/bzbQZd9QN3+nxowiHk/rK8+h8Eqv5Ky8lOiVVSR1FIS/LaTiW4is7mBg==
mLDy5md6q2DkMFpvLtFlrbzw3oheqXbD2qwAZMcuA6Q=
mLDy5md6q2DkMFpvLtFlrWGBrB/jcgDQIi0rEIX5vN8=
mLDy5md6q2DkMFpvLtFlrZ1FI9J4zyIUAMeW/0xk+lP3z9xCfVcSg+XRPXHC2cuC
mLDy5md6q2DkMFpvLtFlrZCAR5DUc2ey3Q5f9VfOQLDhu05EqWt2hD1A7vDKSpvg
mLDy5md6q2DkMFpvLtFlrVE4nQqlZKqeRHuhcjYG9cBcbIb6mWq6nCQfXdYqVLBuaNiK7wnpdzENXVQGO4xxag==
mLDy5md6q2DkMFpvLtFlrQsEeGnN4vdParE9CDgiAK4tpdIWqozh7FV+BqtNBoddCimScVPthPo5boSZ2aCWKQ==
6P8mKfouOK1hwXy3MrrCzRw2UxbgP+Mth9DRZFMbu9aRbVUdtXbkT66ZlvG5+Djo
1D18KWr2hdVsBcdZx1OaPWpmi5eWzbYhJFCY/NY8C/MQbhQT7DlATgfnJmvUUqXY/dtM6WhYa2J1aR47iGCprQ==
VmVrGQo2zRokW/ZuO9bN6wPqF0gJwvcSllqTssBN3IQFplxd+VVRZRZSvCVDzHK4
VmVrGQo2zRokW/ZuO9bN61DdfsWGlVNr7xf50OuAfKCWMv/w8v4ixbracqKZTk/4
VmVrGQo2zRokW/ZuO9bN6+bUoxQpnmE0JVwLD8EwzQ/H7dsGXPHJ9cqQZTkojMQ+
VmVrGQo2zRokW/ZuO9bN62MG6pIvkYn7nEiOjeoc64MrwokZLOOcTxaO8jmQiVHe
VmVrGQo2zRokW/ZuO9bN61DdfsWGlVNr7xf50OuAfKAMMGpuitrqnk3bkbSIR8tK74tenFOnrkxVhSzwMR4nkQ==
VmVrGQo2zRokW/ZuO9bN6/Ly/04eT22h1xyl4dSvQSMNqp7Qyw2t5SG7+K+1JJ0tG30ZCAYaAQLvLpQPeNMYHQ==
VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncDdczYfwtpU5zW2MXjhy/z24GxeAx3QYpL0FUnYH9XQhhs1p0qlJik4yELSlk3g+Jw=
VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncB97WLhqixtZQpLQVJjO8F9rmnhbOS/OmY6IJG+17UdTrJrlEUKFErdk4ikKnb3BL8=
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN65CBdpRIOtptu///2YRyptDJuSvuNBDGWMFm3MShUYhGV2dg4J7PVbUNMjSG7+hBZw==
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
VmVrGQo2zRokW/ZuO9bN6xRaRh2h2yn72BLBhrbk40FNmSd9pOkjInUT3PKeP00k
VmVrGQo2zRokW/ZuO9bN6/Ly/04eT22h1xyl4dSvQSNU5MWz1FGNCr4JsFoMFY0M
VmVrGQo2zRokW/ZuO9bN60HvClbwMpABlSn73UhadQPjjrFMVzyd6bmtZhfVjiZU0huAoInXX/Q18uefitNtu7dcNYig6Kw0BYoxUhMMT0E=
VmVrGQo2zRokW/ZuO9bN61igX6t9WmvQd//qUjyBL6Batd2BDC4WPD9BlNf7ZSqbQhMB7M2MNINGZFx+z9nGKVdGMF8NhqzIBLeQIOPk9kQ=
VmVrGQo2zRokW/ZuO9bN69Xj6ooDVrRi+Qx0VVbxxuGwXblBGt+UMa3iQsUS+MLX
VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncBha4twiqqqB+Lkuo23an3nEDeccqba1p3sRs3HYCm9Zw==
VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncBg1CWm6ODKEO48OETuF/c/M7gx4RLnKkvGw4yWkxfmUw==
1u+XjG/2+GSQRv6EzCaWRQ==
+qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk=
6tCwW2TgXgdok4IQCfIwE64+sANSz5E9miXDw6QJ0r+y7ra3wf2izxuhjiJCK0VR
wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+58c1jICgdZ/yfiiPJOB8osn
iZaIsa48RXfE+uf/yF/rQB5Pyu2LLCSwLBPzS3UQNqB6gGSsuwVrKFDeT7S7ipIY
iZaIsa48RXfE+uf/yF/rQJ1gz5XqVdLjUgxnZLiZ0kxneHQUMBy4uvSK/N8oOaAN
iZaIsa48RXfE+uf/yF/rQNi9uyLdvBZCesZvX8KcrGbG22Xqw+geSPuZqdcR8y/X
iZaIsa48RXfE+uf/yF/rQGg6DytIW0WefmXAsCDYL0s=
L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk=
45hn3FAK6/Q5+BKz66I4FYGZrfj46julOMe5ClqgRDsgDKqc5g62TlKmklK79LSu
1u+XjG/2+GSQRv6EzCaWRQ==
a3HCOwVwgUUsIXy0NI30N1OnTRGwPCMCQnd24LPbvMgw/R3ae9al5MVMPI50MW70reHrwrOjYxRGfyTEKtkxrw==
q4nc/jwATOMUyfSjLibfEQ+nVsCkuVXEdAcBKpgjqkY=
b4OJVZe8QyIpjuTpKXDL9A==
iZaIsa48RXfE+uf/yF/rQOvVhu/ngkyor/C5n/UzkBlCx3epFa1yEIpBPWIrtKEsgWftLDpGjIPx7n5zFnTscEhgReIowtD1ib7ZaN1X1YU=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
iZaIsa48RXfE+uf/yF/rQAZ+EA0c51yFjsX9CPHlh/5ktj0ZrD7uCaj/wgnzHlOCeQCMjWvzIk/DQlZByMiq+Q==
XoHMeaTuJPZ1ZwkBYGxPGNO/fZdFrQeGdb4hk4VRpV0VW7fg9Snb4rb537T74XCuh+3WZ9XaioboPMJTeFhjmA==
RCRD0rTmvzRnTOGXlaehMNmLKF59VFrNXo8fzNysbhTBKccco5cEPB8/dJX6dFqJWYqn10Vvxc/CzhwbE8EJnA==
I9ZqFYKrj9zbehR1kVWMoO1GI6M1qqtG2eBy4D/nldc6VNZO14SHN/sd2voQ0WEXWYIgTZTr4rDtrGXNBr4IBQ==
p50nNlFbWWSM5Jl8TEsk4TnLz+h+3cBHymV4PzApmXUNqbCVGjhE1qvOMYMtCX6R
1u+XjG/2+GSQRv6EzCaWRQ==
+qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk=
J24jXQQti6wQv5pCoF2ZrcLSPgZELT2A2fhuhSpqDo6BY87dEmAzodysqNgyKvzt
KZTmaJLp+FU9X93j5Tpqtw==
hpr9H7QXrORqNT7P49jOYzeYsCbBz/1birJj/JpatlFkFxq27b1MCr3POwO46E7P
YThduOXrtrzATLnwayQWnaA2rNH65m9eJvXKw5GWAAUtcZdLSGJWwaU999R64Cgx
7JQQDTi0T2idhFjao4jEpTcu0vyrf72ks6Zg9LXjcLTZ8NE7V7tyehMaTKgkL7Idboxtgmjsd+qtDVj49PH4pQ==
h2Amc56dmaRo3inGZxN9N3ZLtg8BK5nYtLYDA42Z8AY=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YPE3JL0PMUUnZO0HIdJJMQIGfUXEkt1C3jRrwzATAL//GACiXsL3H69yk+yUtmy1bN8AwykNHE9d/OHOYjevUg==
DarJ6ebtq241LBXqUNSmb8Hia5oTaY/pb+ay91woMws0FeL7ToyJ9YGj7DCzO+EoR/kGoHazwB7LUQuT5rt+Tw==
WHkOzVx7seuLmxs5Hu+l/kOzZi2ouy/XqACgrRboMuAkPXB/7WhDgtDQxPBuCGk/
L0eUthVnpkGsmKFAX6d+uDbeqTXIh0y0jrTQHkGxk00ztM8l4dexLnzgjvrsNYuo
1u+XjG/2+GSQRv6EzCaWRQ==
xRFNEm+nIUjh4cyIzan96kWy6IA9PfznyfSNsYXjhcyekoNWgDiMNKfLwh7IzzFp6unM4EoEl230CfvpYsKmgM609XzA4rC8JK4FwC/3GZM=
WHkOzVx7seuLmxs5Hu+l/tfRUTtAad8Cb/6s2BEj2MF5sacS9liWC0L9lFX0T/8D
L0eUthVnpkGsmKFAX6d+uDbeqTXIh0y0jrTQHkGxk02YaDJD923/KMmkdZImosxQ
1u+XjG/2+GSQRv6EzCaWRQ==
uqDej/AgJHcP58Evn06kx+BRYuP+Rv/P3yyKDCiEank=
M6xDbqnqlGC4Hwvm9iO9WpYNmQzB5Emv8bNh+GjwifhyGxJaxrM3Nu55hGkrzp3Na0UgKMMZhQFDdB1qBnzZM4GXlShJqy10Xh8Ng3hvM6XmMaUNRpE1sspYrkvl/ok8txPZkGD8+6CimGoeq/gHc3IooOAxEh16uaQ9V4oWzew=
M6xDbqnqlGC4Hwvm9iO9WqiWh7KxdPf1TCqsUz96BP2LPZ5/6OAavVK3NQ5oRvG2WRCLP8CLNwYIKWfO7LKcMQ==
M6xDbqnqlGC4Hwvm9iO9Wscmcj6WdKWpwKUcX7+6wD9qlaetybTVnC7NRyEMXQaUruJxuLP7EQajaE97ybYV+A==
M6xDbqnqlGC4Hwvm9iO9WmrzpW7xqvFOkXRrquG/LdmkWI44+PZZb3h8jfRCzfXDDJpmmDVFpMslk/lOV8ORKMKf4lFQfW6IN3gOezMVNdk=
M6xDbqnqlGC4Hwvm9iO9Wrluw3FaFu0rQlI5v3W4Gahe5M3IH/7altsfw4aQIzFTFEp+kqHOFqW9zknlolTgr2aDwLbq3LeRkrF/cccTrDo=
M6xDbqnqlGC4Hwvm9iO9Wui7tfyp3EhrtndI0VQ+0bDRrlG5yESM9XqTcbJlcOwgCZixtz2l/jHsCLjZop31bNpXX4KYhl+/OzIp1VoECupI8G0NECKhXZGxwFP67OGP
M6xDbqnqlGC4Hwvm9iO9WoTR3NAG7MGBrcMrYbTxwvg2d5zTs9QiVoeexs7vgrp1tLfB/Bf9Au9wHlmv00K5zdOfVB4vDxKGSGToV0vyVNU=
M6xDbqnqlGC4Hwvm9iO9Wsk/YBXEvbh9Uvmnh0QJSiFhxQ69X5Iboy1Wk54I9i1KGX689Qf7r00dKSrjZWYdjLOCaQVypVZpz26m19HQouI=
M6xDbqnqlGC4Hwvm9iO9WkK3xOAmzw0wgWg759KvLhH+T5S/PN6qHRHRQ9T3LKPDzTOSNkmC8BZsFn/KJ+t2aLi6nBteKpQt56QmNdl4dqs=
M6xDbqnqlGC4Hwvm9iO9WicgU1xjseoMDlsA3ET1//BCkdCo5I3potyMnxOy8NaEhbkT/s1OThM0Dsk3VBA/QNrh8++5Afxt13QBvjjNDXI=
M6xDbqnqlGC4Hwvm9iO9WgMokrgL8MxEJjEYatsrUV+m4TFjPvxx1gKKyfrM5ibGqjVXQhxhwIodZHTc6JZeUg==
M6xDbqnqlGC4Hwvm9iO9Wr1kU9VI/yudDc/eHU/ewRmVwLQ8jsS8G8r/7F9bWy1TnWfhsRCr+7KbyaufVfaXfcW7CUaFOxd/zmAVrMdCGCc=
M6xDbqnqlGC4Hwvm9iO9WgnywlPbX5KSPk43KpzNKONPq30CaJS4nUs0mMkP5+hbSivbSe4CLkF0KKyRNsQATD8ju8dPAj+f107ThzNYRGhbxWww+AVK7muD42wODMJWmw/m4J/JHWAWU3/ibv9MTKX/YkfOX0bmEcp9LHx2Qxw=
M6xDbqnqlGC4Hwvm9iO9WmJCBrWMX5jYxKbKR4/u41xMqvIPvD3LZfDUhKcoaKuCpfnCZ+EipXVt8I72Nc/dYrkGyEFkdOLMBXhB0XKMLR6STMFXt34k9atd9Qw9x9lNnqTZZXN2lErP5TZWivV35w==
M6xDbqnqlGC4Hwvm9iO9Wqld6rg2Lx98CYBge5Z6YW6hSCzeqqkJgURG552IJQtYStVrim5S05TrSOKTWH7I0Q==
M6xDbqnqlGC4Hwvm9iO9WtVd4iOTlcBudzv96785RaYKs1fhppqqdhgtHLqtXL7XvQgtx4SyuONpafq5eZA/ig==
M6xDbqnqlGC4Hwvm9iO9WgHNFBZvpw4Ykrar5o7UITLqUwDKmFnO9r5qPTi6XzxGGIRw44iLmxNRh6SGGLj5FfIqITod7iAWgEPz9f8TRmg=
M6xDbqnqlGC4Hwvm9iO9WuZW+fuUZB8WpAoWyO5L3TVNxM59XEoFvdP/J5Mq7mp3CFXj+BrERDZdtku4jEnITQzW/Dt7uaUWhWDMFwB/e23rItPvfJnVza4yTyKuwcrG09UsVKbA37u7vX9Q8N+YMQ==
M6xDbqnqlGC4Hwvm9iO9Wve61oMllAKK7aIpkSBv6ZwWnrC4aacWBYjk6HyqSYHL3Hq0XfzMZLHvhQDh2db3gQ==
M6xDbqnqlGC4Hwvm9iO9WoVS+2ab/4yGLNZfC++bUsOORjTX6oF3vbSWEKIYHfC+YN+4SdEA/Th8xOpeytLoDDgQyYgcj2oY5OlwO/zmBYo=
M6xDbqnqlGC4Hwvm9iO9WjKEpRdLTg7WVPHPtUqJqomzwXwNg8PGbqA/U5P5s2Ph9e4kTSbmMaVhig1rkgK80A==
ThMMHJmbSWymjnzPL8su8sBHn5UE8cpNu9WLRZiCzbQKCkCL03sgs+DNRoTYeavL9RZ2I8MdhFqyZIgb2dUmGw==
7sp6hnTw2OtZMRbJTXg9rCuLplGGh0CaZ+h81UyYXoTfxQz5dfr9h/PfGPK3/x1X
V3FyDbaPyt/GrdnaVxxGP5QP34MBqfVhEo9rUYClIQ8GQn13ZWbZ1M6lAwu32F6B
aTu3nEEJ7aNZbzv6PgxsAMIq5Ov2mq1jFSdTHQbA4AjQAxNj/KCefFN/tFo+IuhQiYS29bChBFdJoonMS2dVyA==
UaZ+UhuuDk5ogKtL0+DbtF7ZZMj2uXAUIl9TJ345Nxmn7StJaC+PsiBVbPv50MBUIixchc/TaeXRoVZZIe8nmg==
UaZ+UhuuDk5ogKtL0+DbtO8lJySRwyixd8VH5w6ddE5sq42a0K/FaZh7d6UNKygbk02lvC2lEd8zp1b07VqucA==
UaZ+UhuuDk5ogKtL0+DbtEs4/ygchHdZq2caT5P7ZAY/kfYVA9rE+SmVnD6+LpWybbHzyvSm6PZuNWfaxF7GPw==
UaZ+UhuuDk5ogKtL0+DbtMpwh8AvPkdmTABoemWOBnMsvPbmXsy2D3mw7ujaSN+uVEXDHvmeZaWIoPTaqnsQVw==
UaZ+UhuuDk5ogKtL0+DbtN/ExXZO6r+1Hcx6QYA2uMFerjP1PMKvvs4KUFhJZHFNY9UPjmpmdJT54wPfO3Unnw==
hQ7Zur2CVZat0LHXIoOqQEKciq0mRp3cUUC7gBemh69qrx4U+SnMEWhzDrNvYhgm
OcTtZEQzjmqOEoLAu9WIDBnMum7894CWxyFLkwGu5IwtyjTHaWA2GKONXV4r9vTbg7+1p4tBESO9qkBU74HTEw==
7sp6hnTw2OtZMRbJTXg9rCpKNGJwajbjYG7HLQCEEEXdDjiGpseZYp9Crn852qiTgxwj5jsQKnu7vZaHhMdVCw==
aeUTuKRk4Cv0a6/hj5z/Azbob5vVUjA8C9zwLz131NFQ9+8QpUnpNMjZSkEDyzqvNJOEE7EErl0S1qQyJp0S5A==
aeUTuKRk4Cv0a6/hj5z/A0ufTREaztlpquv0pmAqO4eQBh/OIVHNrMu+G8nKcxX6NRI3YvUe7O2X95abzQ0iTA==
aeUTuKRk4Cv0a6/hj5z/A66msItV6S9si1UO/Jz4XEtSerCl4q49UQ9M8vbWbqsSRL7I2Hi6gUZNJBpgAByHeg==
Yh5IC9IfOdzCK3e5XDQpq7zb2TsOXHn+3J5O2euHIXfq/mGF5OhOVbuFMPjXnQ+M0Dg5Ohnqz2pi0eTNKIzU3g==
Yh5IC9IfOdzCK3e5XDQpq+2odTci4K1zbNd8NJCQULk+QCw5/atmY2M9xvTShAOrmRgT9J1cZGUyLM7hRG6T9Q==
Yh5IC9IfOdzCK3e5XDQpqxA3qd3TcYSVTzrAlmiWJldxDn1jD1ym4yTsgSajOLNbDK+wnMHISwB8YRnXcqofniSCYnGaRqMvj3MURlsXp1w=
Yh5IC9IfOdzCK3e5XDQpq4DKOluGK9CtCgb/1zx0eeCNnM5UGmgXLCeepHrrYTn7VFMw8Fm+mwdLVExOl2tjpZOppE4njUrAe4tqn0RXteY=
Yh5IC9IfOdzCK3e5XDQpq5Ifj6xfZnDox3MD5utXFuixhN8RmZlHKUvwYnDADhnNXPn0TzBaUVp8y3F1IayBhZc9me9kQSHvBQ7soWODUbs=
Yh5IC9IfOdzCK3e5XDQpqzRhjv/5t8mv7bNNVXSZA0n4tEX8ATMW6aOFzRYG1NzH8pTBJQ/1EU81Zjuy4E0Uaw==
Yh5IC9IfOdzCK3e5XDQpq5aQfSbh2TPEVX96QEtEYaDiuEyvpOigBao3+w795EB9JATH6Wr2GIoRnqzXQgdrIA==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
661hZf7vhUQ+50okfwfTXw==
fE3zmgbfhO9ZwEda5GgwD0R+fpJ7JqroVfr4B5FO/AhYM3WK1zBVVzIzdn4lt3dirmMTx0U2aKsHZMW89EKgpg==
aPdLEV6HY2SBr9CPW/3NlfBbk//L4rv3UKu5v766u0A=
+170 -170
View File
@@ -1,170 +1,170 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
# 上传文件至oss
#------------------------------
import os
from filesModel.base import filesBase
import public,smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formataddr
class main(filesBase):
def __init__(self):
pass
def get_oss_objects(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_all_objects(get)
def get_file_list(self,get):
"""
@name 获取可上传的对象存储
"""
return self.get_base_objects(get)
def check_email_config(self,get):
"""
@name 检测邮箱是否配置
"""
import config
c_obj = config.config()
mail_config = c_obj.get_msg_configs(get)['mail']
return mail_config
def send_to_email(self,get):
"""
@name 发送文件到邮件
@flist list 文件列表
@msg string 邮件正文
@to string 邮件接收人,多个逗号隔开
"""
import config
c_obj = config.config()
try:
mail_config = c_obj.get_msg_configs(get)['mail']['data']
if not mail_config :
return public.returnMsg(False,'未正确配置邮箱信息。')
if not mail_config['send']['qq_mail']:
return public.returnMsg(False,'未正确配置邮箱信息。')
except:
return public.returnMsg(False,'未正确配置邮箱信息。')
msg = get.msg
receive_list = get.to_email.split(',')
if len(receive_list) <= 0:
return public.returnMsg(False,'发送失败,接收者不能为空.')
#附件文件
flist = []
if 'flist' in get: flist = get.flist
result = {}
result['status'] = True
result['list'] = {}
for email in receive_list:
slist = {}
try:
data = MIMEMultipart()
data['From'] = formataddr([mail_config['send']['qq_mail'], mail_config['send']['qq_mail']])
data['To'] = formataddr([mail_config['send']['qq_mail'], email.strip()])
data['Subject'] = '宝塔面板消息通知'
if int(mail_config['send']['port']) == 465:
server = smtplib.SMTP_SSL(str(mail_config['send']['hosts']), str(mail_config['send']['port']))
else:
server = smtplib.SMTP(str(mail_config['send']['hosts']), str(mail_config['send']['port']))
data.attach(MIMEText(msg, 'html', 'utf-8'))
slist['error'] = {}
#添加附件
for filename in flist:
if not os.path.exists(filename):
slist['error'][filename] = '文件不存在'
continue
#超过50M无法发送
if os.path.getsize(filename) > 50 * 1024 *1024:
slist['error'][filename] = '文件大于50M'
continue
#中文无法发送
if public.check_chinese(filename):
slist['error'][filename] = '文件名包含中文,发送失败.'
continue
att1 = MIMEText(open(filename, 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
att1["Content-Disposition"] = 'attachment; filename="' + os.path.basename(filename) + '"'
data.attach(att1)
server.login(mail_config['send']['qq_mail'], mail_config['send']['qq_stmp_pwd'])
server.sendmail(mail_config['send']['qq_mail'], [email.strip(), ], data.as_string())
server.quit()
slist['status'] = True
except :
slist = '发送失败,' + public.get_error_info()
result['list'][email] = slist
public.set_module_logs('files_send_to_email', 'send_to_email', 1)
return result
def upload_file(self,args):
"""
@name 上传文件到指定的对象存储
"""
name = args.name
filename = args.filename
bucket = args.object_name.rstrip('/')
if not os.path.exists(filename):
return public.returnMsg(False,'FILE_NOT_EXIST')
info = self.get_soft_find(name)
if not info['setup']:
return public.returnMsg(False,'未安装[{}]插件'.format(info['title']))
sfile = '{path}/plugin/{name}/{name}_main.py'.format(path=public.get_panel_path(),name=name)
if public.readFile(sfile).find('upload_to') == -1:
return public.returnMsg(False,'暂不支持该操作,请将[{}]插件升级到最新版'.format(info['title']))
#创建任务
import panelTask
task_obj = panelTask.bt_task()
msg = '上传文件{}到{}'.format(filename,info['title'])
exec_shell = 'btpython -u {spath} upload_to {file} {bucket}/{filename}'.format(spath=sfile,file=filename,bucket=bucket,filename=os.path.basename(filename))
task_obj.create_task(msg, 0, exec_shell)
public.set_module_logs('files_upload_to_file', 'upload_file', 1)
public.WriteLog('TYPE_FILE', msg)
return public.returnMsg(True, '已添加到上传队列.')
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
kEPo6lgxLBQavG1DxRrlnEK5KohxBShsQIeNCGHkEP4=
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
dTLQ8Wr3wPn7w6pte1B1YA==
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
bPP96Leq4/6bN59hBCaYlI8pXwmlcWoHnMhS0JRYMuI=
1u+XjG/2+GSQRv6EzCaWRQ==
NbgOlQKOVjgcHXr2EWkJqQWzgWH6Vq8RJ/AUSzSss8IVUY2HzghyE71ZaXPBTLGv
NbgOlQKOVjgcHXr2EWkJqeTgbDhQ7kNNY/wz8y2GqwZTfVZH1snrz7MBnfOT91tS
E9KwiiXGE37hdLvk/mY+UIznMbuZ/AbAoo9N1G0YvoLz61bIHX5Q0KQJryQ9TSuJ
1u+XjG/2+GSQRv6EzCaWRQ==
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
9bJD7OGVYIbkQwcqio3pMkEQ5Tepk1KAKJBdik81BOHNSVFwrMdl2gFGwZvvpYDQ
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3
Z8UsPk1Q7HtwjRd4g01ryw==
lt0ONPc5ulpyg8e5cXCrlcNbN4AJdkj3/jMJ6CSe6OnifSM9+lA1O2PeRoAR/q0t
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrcF30x6jIElrdtnLwh7xjCTUby5LexzwAUmLxkZ6OZp9
Z8UsPk1Q7HtwjRd4g01ryw==
NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3
Z8UsPk1Q7HtwjRd4g01ryw==
CAhn8dRUItEbEErp4w+lX9NUcXl4jGRSijSFq7galTkLvLnalDlhEukOn7KLDmPy
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
mz/fdzicrSUPVLp6o7PwCSAYjWkN2zpyX6N0H3XQ+Re/2H8C5nEqNaf5oXzlLd5r
Z8UsPk1Q7HtwjRd4g01ryw==
0LFyB+q/4AUBkCrX53xYJZNkGwqwopNQ7HR7+KY/2yjfRImCHWTpijZdrdo/d8l8
Z8UsPk1Q7HtwjRd4g01ryw==
KbLSYqE/CR9TL3ZELtBVg8UiZep5zt9moZHOJ5ffCHo=
1u+XjG/2+GSQRv6EzCaWRQ==
iTSUWsaHz8xtfxXo170oO/0vr9reANk6j9ykbM5WiCYFw3OvMYMGyJ3KgPGMrF0H
PXCtqxQPPqk7Lcqhz/3AoGaqmPHjN4rP7NSesY70x/92ifCLZ8Okvt9nT27s6jtrmbW9pJXGLgLNf+3meuER5A==
1u+XjG/2+GSQRv6EzCaWRQ==
FEZSdYhAV26PfOeirhfda9fPkPkpgcLIU1bSxCG9qHs=
1u+XjG/2+GSQRv6EzCaWRQ==
DJtqiO8w/dKi7HwLds9peNr7IF6LPtUssSL3IuNOYGLlHjOwFmfwe9OK3nxxRRgN
Z8UsPk1Q7HtwjRd4g01ryw==
a1WCYQuoRJeK3mOCGHQIliYA3yrJMlMcLCMVYxV0uObw5q3Q+Zn4+NYyZBHBqFEE
+kvn6XYS2Dcvt6Ptafb+egXt7PQiX4xuk1EJQthbw8LHHFksw4XlK/hEZYEN6MEz
Nl6FAwmBymcnkmQ2v1saoWguYo5PAJb10t78p9wNmQ967hJsaE/VyX2nuWwy3sK2
JEQfGov5ldbBWaI3aZWXDNAOlBZZiYaTkM41OkhTw7DxlEAMKtlD3FDpetB/Ra/HrMZWBT2ISI5K4C7FovZIWg==
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
KbLSYqE/CR9TL3ZELtBVg8UiZep5zt9moZHOJ5ffCHo=
iTSUWsaHz8xtfxXo170oO/0vr9reANk6j9ykbM5WiCYFw3OvMYMGyJ3KgPGMrF0H
1u+XjG/2+GSQRv6EzCaWRQ==
b4OJVZe8QyIpjuTpKXDL9A==
HNqEbxya75q97PmRdMU8Fq0RpPGtQqmyQcXNysvYcIzmzwtTL2CB6nQWsuCPipUki6fsBjosP5fyCX/lXfWtP2aHIN8g6JRrW4TZbYQNKv4=
1V2v8QerKOmubvSxgB4eTATuLP/YkzXuFS9s9v29gfLs7HzcbbjmTiso+zmsCZ3S
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrRQLiV7W1lz/PImX//PWNLkFIuv7VUgU3+a6PwWzgXXaB4nsglac/VuhWclPIvVlgOIAaNg2YXbEjwkZqivaweq
1u+XjG/2+GSQRv6EzCaWRQ==
1V2v8QerKOmubvSxgB4eTJ841yfu7l5arGk2XF/kO8mJfg3iuq4dIggjGJ90dwx8t4CE+4HLudLp33Sh9joOLg==
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrRQLiV7W1lz/PImX//PWNLkFIuv7VUgU3+a6PwWzgXXaB4nsglac/VuhWclPIvVlgOIAaNg2YXbEjwkZqivaweq
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWg3MlyzvlsDpJ2tFZppFlDIgMeiVDQ2HRCla7COtNJPjhTSrDI29qVP4xDeSndmdE=
1u+XjG/2+GSQRv6EzCaWRQ==
uIl0RcuWgRCPZivR9HE5K6P0KAPh0+c0Ci+5o0mjZKg=
KHVPPgRdeAM1K9qeWQ6AiqCJ1c6DyhB9VCKPe+SB8o/S5eT66VcxZqE1Abf23JR5
gApoKqs53F0hB+0nTxFBUK0sDmJFt2pcRuqhtiHPJ+voEfz/6grsNrnqbl6KL3aq
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVf/86mCDVHuaSE/lwim7QrFi5TEaeF6XshC8H9LAxRJInVUSAqtWixvfFY8aKz+m1a1RsFEpCSFGYL1Q+qZONa
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
gHZvnjbK0+c2DWpdjJTIKjYmDJs6pOpneqykoeukYiI=
RAcgdll7MZc9X2PbkeTvEzNyiOpuxHuwyFCiwlCwtBo=
NgkU9z71J5Awi50+35yVrVFRMlqzLfxgW8oRH+8xszId0swAa3xYf+cQPC3GoRX8
1u+XjG/2+GSQRv6EzCaWRQ==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI
YSlit6erKXylrjSZnvyW8Dke2RKJMhjXyxnQzpVZgOM=
Ys4BPYSOoHeIx+ql4jJylD2VLnaOQOBr+k3Ytq5owd23kxsNazPnVuATSOUovBrq
bWoUv86VLMtyRQlo14Ahu3xfaGKmZUw7MPoURoMwHGI=
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN68uMQcDaeKFBnSol/Upw31Zbihi4vGENQHlL+dj6DCvT
VmVrGQo2zRokW/ZuO9bN61HRAGek/auc8b4JvIZlfzGQ/b8xNkzKFp1KEGLxgbuwAYkeWczxvXCQWc0loQzSHyq5aP4vAsBP8KIObacZsHW2l0eK/68p8LwM/KD04HbjIFDKWSdnqDmGI5E/oEv2+A==
VmVrGQo2zRokW/ZuO9bN6/0hFCJypSh6x2UIBkV7VvXsU+cO8B7S9xx1VVZWBeu0eYDfkAI5IGvcY5NDn0MqTPYuqy5Bl3zY8pT547cBMFbo51+sa93m7/eHEKuy5GXE
VmVrGQo2zRokW/ZuO9bN6wHsvvxh4guxHr80r9yaINtJrvRK6UdvNL8JUA1IT/spxWK1U/dlzvPRDq60WZSm+w==
VmVrGQo2zRokW/ZuO9bN6xd9hVVPG/da3jOneq2RZCcce997vOo+rQywLRgLWiWuNr25+4z1FY+siXTO3eViyQ==
VmVrGQo2zRokW/ZuO9bN6/CxiXZULdhjyOp1jTEufd+jVXQuLPUTInFNcnpN1I4wKnR+4QeoyJLsqr/3r41c5otdZB5U+KnTPi6U0yHupXO5fT2t9XsGdz3dQ6SMRN8yg9FL1qHp4ThJ4cz/lg4X6o4uGI+UWHWKQ4ipM/V7GcM=
VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY=
VmVrGQo2zRokW/ZuO9bN6/CxiXZULdhjyOp1jTEufd8fOViMKvV/4EBLt4qPjTM3P8imZTOW0FDMijb9jSuw0FVDpqK7f2sMpzWe9pEkHJdj7JuaHPwQym1UOAXnaGko///EYYbAEydMFZn+m+korg==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN63WsPkEtKdp9rQFEV0mc0Bp0wD1zMIehj9MyHLROd0aILFdAc3MAmaemnxPAUiPekw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6zsXuunPbx71ngX6BaQenPNbA+Mg7i1nazF95o2HQtU5
VmVrGQo2zRokW/ZuO9bN6xTkk9WeCniuLJmLBef2m+U=
VmVrGQo2zRokW/ZuO9bN65Mk3nfYCJEpzMz8EnuHOsgreh18KDp4UtHG8fOCTUhO
VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiKbQbJoCKHWLyJn/Vx814Ro/Ee5KiD6gqXP3nh6dppi/w==
VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV4tIbbM82aET5PVAdbZXXdUO/Z4wJ1V7uPGDDu8Cb4YmQ=
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6x8dPCIFym0qQ4B6l0auqv3VSU3SG/i9eUcnpJrb1L+X
VmVrGQo2zRokW/ZuO9bN6ziVGg/SZJQ2a2ljdz3XhcVn97lFqgG8ziUZ47BfMFG9UXbT2kS6y93cABsoh/aDUSuKrmA11kGt+hTSze7FVyk=
VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV4jBljvu51ZnW8hS8WXSpu7pv1/kMj8Ak/oIB5g6oKubA=
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+Hp+GvFtSASnTbptRcIg2lSnsLRR4J22UX96CUCq+KR
VmVrGQo2zRokW/ZuO9bN64qagL+89Aq4rR/hcuQ6/QaZLgJxtVesokbhCUzfxCwfKcyVGoV3zyER2vrKLj8C4A==
VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV40d40RaKmxMWpfhvj8ZyruQ/Q/AdiPvG5CMZanDD6YE98WLACskd36bpmVVS7VPAS
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6xUIvvF7vKpIldg0eDQ2GLdixkmDxxbU+OXh2/259dgakzCXh2oDJ0AfTKNrUbZ5d31ZrBNmK8mELWMQzPcKOzPm5KFJw1Mu/Hkb1SkNQs1d
VmVrGQo2zRokW/ZuO9bN69P4p4urRz6LNH8T7D85tUmvT6fU8K2BiEON885R0KsYZZwTue89GlNh5Rj+HJ/0l8p8Xk8+ZEyH9zTSoSVrZuM=
VmVrGQo2zRokW/ZuO9bN69P4p4urRz6LNH8T7D85tUnJY2h08qZtVSMViVJ80aOFEB7NOmEnSXBDfiqhTqlpxqE7hd3sxipptIDNtiux052BIE9Ab59zGW7HA4ampTWIfcrnofD4U94ciAzYjH7cMQ==
VmVrGQo2zRokW/ZuO9bN68SNBDbgryQsl/pB0VGbLvqWKmuDEMs3IPB8IEm2OG3x
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+UgxYwTsTpiHdnLypOQ/kZoFZH3aRRa3jGRdR795Y1mA6vXFA8gZ85TG3MqCavIWvAJnmCjVt23ZkwLlEM33Qocr5vG2sgJeT2xmYYZ/iICcOfrgbcyhaRW17sPNkIRlw==
VmVrGQo2zRokW/ZuO9bN6yYgZ33nPjoy4tOZmNaX7mQaiu/N7s/Mw1Ei0aMstGjWDlGg9LgiJMyZKxaW8bZyIjtyH8MUjBVG9J4w3mR65uirBBRQcKVp1rVnwRuAvlAOObnzQPL0B/HA/xlJ0/WDfA==
VmVrGQo2zRokW/ZuO9bN60vhRuPY/LJZF8HROzC0dFI=
VmVrGQo2zRokW/ZuO9bN6/6qy3lEE/QMSwWTRtFJoaMcGV0W2eYWCTCr8Hkveve9
M0ZySqkmhuHCw6olbCKv99p0Om1sGOCLOPcvWBGiKm4=
VmVrGQo2zRokW/ZuO9bN6wgdH+0DZQge5QIlnRbbyGVIwpFYFAZXcG4ciFkbD8xGTtzX6dDhJzsy6JEW1M67sQDPORs3az4NjR88iM8Lcnk=
1u+XjG/2+GSQRv6EzCaWRQ==
o6QhOIN2Sc4SHELnst17uQbQrK2Z34ZMKBCsEar9NNzRcJW4KMeQ4yhsPKTJaw1j
Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs9jY9CEkJ+mtN1RRkcQDmMZqWpTNzdr+Hy3p75kNCSdGSik+k6MbKPBA27LVqGIPTg=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
jGPLgFRMa/gLI5pn0C8oszLqa5isI224m2QOfckbxLELIVt69gRzqdxrFQlcGXIa
Z8UsPk1Q7HtwjRd4g01ryw==
n3tXewq2IYqiwFREjJjsxQv5FBS89YO3jVo+BgMdPGUBLkZx5+ZpOpx7LWu6PFKo8b9SqRNn6c5JZVzlsIkp+A==
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
xsCYviGksjEQaKpL56on/X7AnEB6uKwmFpBflqwZHQ4=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
a7gbuXC0s2yINkxKhLLBokQAN48UxYxa6jCVIMQ9FeJoGd91ZEhhcfiymqUdYGqf
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJh6sAOIfkvF5oIqf+7bT65s
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQ/iPzQ+uFBITVsVoGpItD5Q==
1u+XjG/2+GSQRv6EzCaWRQ==
AN33R+yjfqqAkAsFnmP/r/WGRgGkDEv8Fnc8kjQsJ7c32mbK3YgEYOi8MYXpOqJy
cbvASHjpysrsjdY5RctXmG9wund6qoaXAfnCgO+o1nY=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXHKWKUVVa/rOrR3+fgEd7tp63QHMzA34qukyTQgDFJrLVYVJL/Ye3kNmAIiExi//aTWiBsWXWTJE2/bLQwR3ie
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDGWrMrUVsu+cay2sf0G/q6n9TVdK0jEcTi9z8Mi4uhlIHBQoCZCJNLutARg3AcC9GZkJHoO5OtM5wGrnT579QQ/33LGKQp33LdowTPKOj8mg+5XvwqiamHNJ4b6qkc35ZA==
TQRmC0vOvJ1P+lfyS3Os4fdA9rnIPjSDuS8zcTDjbSEVtc81+Y4AUEIsuAgPRMzVhLwEUrXGM6DCeZ1rhkVK8w==
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUzJgj0ZVdf8DkqZrnrpLRzKvaQBgq7m7DBF8Wb/aWuxeFqQz2lv84Je+M0d4O8q9Ux2G7KTGW8VqvvCK6513pJrAuADi3mTtgVX9Y0JiEdnx9iCVypIiCmLG0BR48sQeM=
1u+XjG/2+GSQRv6EzCaWRQ==
y9uLKqgxuwiJJuaISH8LNlJR2o2Cr2uNLeYPxMDYl/0=
q4nc/jwATOMUyfSjLibfEelnou8pLYpfebj3/LvnkGg=
7fBvPEAf72Uv1YEDXbbQ3CN3MWrD+3vkr2VJCCzzEPRhwccGf00/aNA1BGgdy8WH
59Mj0OVU6q9AFARXxvxfN1dW2G3zJI+4zsdXTWh4XwFG18UDXnpLmLIfLIZ41jcpe1yBIt+f+JDrMrCbBNf/9Xy836/xgiba8x96DZU3Wlg=
pcGeDqO6jWjADfc8Oyd0b2PTNAiWtNjPy+4ZuJym09tcrllO4bi9TB9YXcFHDN8ulWbcZDzkd3vYSjeW6cb+7a07iV7nvIzEjupMBYIINGsixBBRFVjl2m3gGdMptbNGHA9dcrK5ewH6POHu2UFt2t23FwcWAqOHzR3sL67HAErpzje2T7IZlttmY5tQ/RPJ4QDTGGJ3YSogtm9/Z9ZSs7wMVRGX8M2Eia5bIBLZuxk=
7fBvPEAf72Uv1YEDXbbQ3Kl5asOP7Hk9Z+oJF57QC0c//DIVOxrgJlRJ07hlZuLdNPXnVpbKF/o/zidPewH4xQ==
1u+XjG/2+GSQRv6EzCaWRQ==
Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8ZqmZBUF1OtUXbF0cnfKWhyLN47Y9VJ4gMAghwAahsZHCVXCB6/L26wmeLJb9nC4o=
2+RceSNjD2iq0sRzepMyse3K/6KDWrRQWf3bA9DlS0lC8P1AVP/ryfVnqkju1FOT
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOEvWExeU8Y5S6VzOcEFIF/zQLgeURpx14jzr1s57LjK94c4cdiNmekvORipQcUMeV4=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
+327 -327
View File
@@ -1,327 +1,327 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
#-------------------------------------------------------------------
#
#------------------------------
import os,sys,re
from filesModel.base import filesBase
import public,json
import zipfile,shutil
from pathlib import Path
class main(filesBase):
def __init__(self):
pass
def __check_zipfile(self,sfile,is_close = False):
'''
@name 检查文件是否为zip文件
@param sfile 文件路径
@return bool
'''
zip_file = None
try:
zip_file = zipfile.ZipFile(sfile)
except:pass
if is_close and zip_file:
zip_file.close()
return zip_file
def get_zip_files(self,args):
'''
@name 获取压缩包内文件列表
@param args['path'] 压缩包路径
@return list
'''
sfile = args.sfile
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
zip_file = self.__check_zipfile(sfile)
if not zip_file:
return public.returnMsg(False,'NOT_ZIP_FILE')
data = {}
for item in zip_file.infolist():
sub_data = data
f_name = self.__get_zip_filename(item)
f_dirs = f_name.split('/')
d_idx = 0
for d in f_dirs:
if not d: continue
if not d in sub_data:
if d == f_name[-len(d):] and d_idx == len(f_dirs) - 1:
tmps = item.date_time
sub_data[d] = {
'file_size': item.file_size,
'compress_size': item.compress_size,
'compress_type': item.compress_type,
'filename':d,
'fullpath':f_name,
'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])),
'is_dir': 0
}
if item.is_dir():
sub_data[d]['is_dir'] = 1
else:
sub_data[d] = {}
d_idx += 1
sub_data = sub_data[d]
zip_file.close()
return data
def get_fileinfo_by(self,args):
'''
@name 获取压缩包内文件信息
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@return dict
'''
sfile = args.sfile
filename = args.filename
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
result = {}
result['status'] = True
result['data'] = ''
with zipfile.ZipFile(sfile,'r') as zip_file:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
buff = zip_file.read(item.filename)
encoding,srcBody = public.decode_data(buff)
result['encoding'] = encoding
result['data'] = srcBody
break
return result
def delete_zip_file(self,args):
'''
@name 删除压缩包内文件
@param args['path'] 压缩包路径
@param args['filenames'] 文件名列表,数组格式
@return dict
'''
sfile = args.sfile
filenames = args.filenames
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile:
for item in zip_file.infolist():
filename = self.__get_zip_filename(item)
if filename in filenames:
continue
src_name = item.filename
item.filename = filename
new_zfile.writestr(item,zip_file.read(src_name))
shutil.move(sfile + '.tmp',sfile)
return public.returnMsg(True,'File deleted successfully')
def write_zip_file(self,args):
'''
@name 写入压缩包内文件
@param args['path'] 压缩包路径
@param args['filename'] 文件名
@param args['data'] 写入数据
@return dict
'''
sfile = args.sfile
filename = args.filename
data = args.data
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile:
for item in zip_file.infolist():
z_filename = self.__get_zip_filename(item)
if z_filename == filename:
continue
new_zfile.writestr(item,zip_file.read(item.filename))
new_zfile.writestr(filename, data=data, compress_type=zipfile.ZIP_DEFLATED)
shutil.move(sfile + '.tmp',sfile)
return public.returnMsg(True,'File written successfully')
def extract_byfiles(self,args):
"""
@name 解压部分文件
@param args['path'] 压缩包路径
@param args['extract_path'] 解压路径
@param args['filenames'] 文件名列表,数组格式
"""
zip_path = ''
if 'zip_path' in args: zip_path = args.zip_path
sfile = args.sfile
filenames = args.filenames
extract_path = args.extract_path
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
if not os.path.exists(extract_path):
os.makedirs(extract_path,384)
tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32)))
if not os.path.exists(tmp_path):
os.makedirs(tmp_path,384)
with zipfile.ZipFile(sfile) as zip_file:
try:
m_list = {}
for item in zip_file.infolist():
filename = self.__get_zip_filename(item)
if filename in filenames:
spath = os.path.join(tmp_path,filename).strip('/')
if item.is_dir():
m_list[spath] = []
else:
if not 'other' in m_list:
m_list['other'] = []
dir_key = os.path.dirname(spath)
info = {'src':spath,'dst':'{}/{}'.format(extract_path,filename.strip('/'))}
if zip_path:
info['dst'] = '{}/{}'.format(extract_path,filename.replace(zip_path,'').strip('/'))
s_path = os.path.dirname(info['dst'])
if not os.path.exists(s_path): os.makedirs(s_path,384)
if dir_key in m_list:
m_list[dir_key].append(info)
else:
m_list['other'].append(info)
item.filename = filename
zip_file.extract(item,tmp_path)
for key in m_list:
try:
for info in m_list[key]:
if os.getenv('BT_PANEL'):
shutil.copyfile(info['src'],info['dst'])
else:
shutil.copyfile('/' + info['src'],'/' + info['dst'])
except:
pass
shutil.rmtree(tmp_path, True)
except:
return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info())
return public.returnMsg(True,'The file was decompressed successfully')
def add_zip_file(self,args):
'''
@name 添加文件到压缩包
@param args['r_path'] 跟路径
@param args['filename'] 文件名
@param args['f_list'] 写入数据
@return dict
'''
sfile = args.sfile
r_path = args.r_path
f_list = args.f_list
if not os.path.exists(sfile):
return public.returnMsg(False,'FILE_NOT_EXISTS')
#追加原路径
src_list = {}
for fname in f_list:
if os.path.isdir(fname):
s_list = []
public.get_file_list(fname,s_list)
for f in s_list:
if os.path.isdir(f):
continue
src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,''))
src_list[src_file] = f
else:
src_file = r_path + '/' + os.path.basename(fname)
src_list[src_file] = fname
tmp_path = sfile + '.tmp'
if os.path.exists(tmp_path):
os.remove(tmp_path)
with zipfile.ZipFile(sfile,'r') as zip_file:
with zipfile.ZipFile(tmp_path,'w',zipfile.ZIP_DEFLATED) as new_zfile:
try:
#过滤旧文件
for item in zip_file.namelist():
if item in src_list:
continue
new_zfile.writestr(item,zip_file.read(item))
#追加新文件
for src_file in src_list:
new_zfile.write(src_list[src_file],src_file)
except:
return public.returnMsg(False,'Failed add file,error:' + public.get_error_info())
shutil.move(tmp_path,sfile)
return public.returnMsg(True,'Compressed package file modified successfully')
# def __get_zip_filename(self,item):
# '''
# @name 获取压缩包文件名
# @param item 压缩包文件对象
# @return string
# '''
# path = item.filename
# try:
# path_name = path.decode('utf-8')
# except:
# path_name = path.encode('cp437').decode('gbk')
# path_name = path_name.encode('utf-8').decode('utf-8')
# return path_name
def __get_zip_filename(self,item):
'''
@name 获取压缩包文件名
@param item 压缩包文件对象
@return string
'''
filename = item.filename
try:
filename = item.filename.encode('cp437').decode('gbk')
except:pass
return filename
QRASP55VO/1DQ98p1csw9A==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
dBZyCsfrbwqvA0sbdGrIGg==
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI=
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5
I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M=
1u+XjG/2+GSQRv6EzCaWRQ==
Hf+bBTgRv9pVfall8ODpvg==
I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0
1u+XjG/2+GSQRv6EzCaWRQ==
K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU=
X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2
hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM=
KbrESL0Rr2jC6vOleUlueMUgBS8ubHMHEf/dewOmXjc=
yUFFATi+ZVxTJRU9zfoit8QIFboIm1z3wG2Pz/1LIy0=
CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao=
1u+XjG/2+GSQRv6EzCaWRQ==
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
6c3cWGGlsEf4q4E+EjPFyg==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q==
KZTmaJLp+FU9X93j5Tpqtw==
0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR
yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf
akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnH1EgArAJXS2MAairjwNzV9c=
b4OJVZe8QyIpjuTpKXDL9A==
wQn8ViRVbUL8OLSIgBD0ID21VtRI2rJC6jqthFugWvWVef56vW1JZ4xiXoQ3GDDo
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
1u+XjG/2+GSQRv6EzCaWRQ==
l+sL2BKY+z3yDMAsWI51b39VWekdKmbfrOabwvVHTbGfiUobxgPhHW2OIZyeGUJ9
wQn8ViRVbUL8OLSIgBD0IO53Ya1+ckG2IX47+ykaGJU=
1u+XjG/2+GSQRv6EzCaWRQ==
MxWDzXEQjj6mgdfarjHFtAA6unJa5l9pTo0P8ga3JDU=
1u+XjG/2+GSQRv6EzCaWRQ==
x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnHy+YX2/U7Ip4Uqt3suxNhindpukzgQ1aM+PshcW/1dV1
LuO0LAjc92aYQU4tSUEvi4x4NJPWR/eKWjRfKMSRL8o=
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV/41TA4054wrqxXx/Q1i8cmtFJNrxypDZNiWIVVjiYLA==
1u+XjG/2+GSQRv6EzCaWRQ==
JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY=
ei/1A+2pKZSasjYF/t4qojBnh7Bi8Fen8Fcf8mKjT9IuWg8ZCZGedub8Eqjf8qUh
4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg=
ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw==
1u+XjG/2+GSQRv6EzCaWRQ==
g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR
1u+XjG/2+GSQRv6EzCaWRQ==
wkESzX/Mo7zNrX6wCsHIMgfYkIJy+Dcvs4tK//49fTc=
wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I=
VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu
VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf
VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL6pyjjGewhvRJVBn8x2mvRw+9QouFIpzWY1J2Ub+ZqxCSNlCNDuqqN6YMHyK4kxCg=
VmVrGQo2zRokW/ZuO9bN61e1I6gZaVwmbMEHc7jd9wFEHpeilcxXyCkd5bmWx/yK
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8LNRE5UkGbTG1UxYqNO1dyU2pay/FWt6r8mJiKZ4UU/g==
VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs4jrgwkpIqneJssR9hHHlJ3UJd9tZzLLz6C+UQLUJWWGbgxXo0UEEapI14RLzluAUk=
VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs6tF3Tt2+gNwTBcUtu+YYQKSt4+UQPUctZ0/+hfCqv3GxDub0pA8qeNN8AxvOE+5lk=
VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3
VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ
VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdx733inB6pPVPHnS53cxkslRzdzhPLD4mmySs0XcaZo8Yb7xMuMGGlmbahCRF6eC3yiiMtXXp0R7BViTpiJ04fqWtzkElISMu7z4pkkwr23TjUMjTjCelUG9Lwgmt+EiK
VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3
VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvxN3810q5P2uCqBbefHbrK
VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ==
VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k=
VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo
VmVrGQo2zRokW/ZuO9bN65rWaakWr3uiXA345yT9jOU=
VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW
1u+XjG/2+GSQRv6EzCaWRQ==
1VHvxkEcO4espNMpWHmnHx0/qyT1YxdKfIVIaA4qitI=
+plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI
YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54=
6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA==
wlLHv6kT3Q/RmtMBN4nDAdAMeIPpwO0Pgawv937x0Zw4iwEVDD1EIlJA37GxF8mZ
VmVrGQo2zRokW/ZuO9bN64/tCOCyKu41lsl+3BVlFMtjVa1DXtvHbVFGS5luzEIFtQp9eHGpneVvKz65gzXCXQ==
VmVrGQo2zRokW/ZuO9bN69Z4WTbfjflDn+PHJe4HyeBCMRIQIdsxtS/SkQUyTK46
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN64IUBVOpZieySJUi/5Xp7IP9i2hr//841YnIEqNDctKsA4PCQR7FmaZMQHs7tdC+NQ==
VmVrGQo2zRokW/ZuO9bN63KtgQxYoQMLiw/swXgEzq6F5dbUK15QUQFhJ/fqYDb2bGgnr3J8HSW6FkIPXw/QAPGbSGA6euyj6sY6jr9uD88=
VmVrGQo2zRokW/ZuO9bN60+Gj1vDNaFgHQa9qY+WYQ/+8quDVQ7mW+ZFMiCB6dK3zTXaMhAFlgEsY2XTlgH4hQ==
VmVrGQo2zRokW/ZuO9bN60dNkfoHcjUpCVMrBt5WSA9PCuYzYl0QLyyWqlMopPgY
VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY=
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig
KZTmaJLp+FU9X93j5Tpqtw==
dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA==
1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIcrfmFscSLitBH8rHfUTRshPd3qsDNW2TEFVB/BherqRUPAAaKNdUCadxkxouQ603/UPQa2mdlVSAGn2R+CvNrg
VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw==
VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
VmVrGQo2zRokW/ZuO9bN6xrJVNh48JOJRHCQYI4xAH6seRNi5O7iARUSsCPjTbt2
VmVrGQo2zRokW/ZuO9bN6/GmqY1U6uFDyEbXCwKITV7VrKjU57dBsJG4vNk6MQiq
VmVrGQo2zRokW/ZuO9bN657WYPgEitoQAm574YXmF9lUB867LK9es4C6vHCx401D0rMP+Q/+dVAUlvxCaivz4QRggUPK+VUMIqZGEHb1ljU=
u8vW5y2SRttIHP9hhx+fqksl4XiiUx+QjIOk2+hITXxAagiDFp3uE2PeHg2zFP6D
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOH6nbHs2WDQfzsBPtsBHumfOcU290Wam9UwXjcW8zT/eaFb4gRZNo5rRuNz1TQ6cJU=
1u+XjG/2+GSQRv6EzCaWRQ==
O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK
KZTmaJLp+FU9X93j5Tpqtw==
t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t
XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA==
1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIcrfmFscSLitBH8rHfUTRshPd3qsDNW2TEFVB/BherqRUPAAaKNdUCadxkxouQ603/UPQa2mdlVSAGn2R+CvNrg
VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw==
VmVrGQo2zRokW/ZuO9bN6xIOVX/jmHApY1vYi7p62efnJA3RwDkPnY0p8K402cIDtEidZgORRD2w1kwOy8ktqQ==
VmVrGQo2zRokW/ZuO9bN655gCVY8shnXyf3BTAhOCEseA+FmLUdf2zTZRuj4Gshw
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN657WYPgEitoQAm574YXmF9lUB867LK9es4C6vHCx401DoXSgI87+dv1IXTMOs5pogfgyGxM8q3ngzmVKGd4oLv0=
VmVrGQo2zRokW/ZuO9bN61dxACaOXPUs+kKrrvH8m6V6uiphjbaKMJwgGptqRuCR9Cj1NHc2Yj+FdsrtTHaQoM49Sy0pv/Om5EdLdtFFjJ5lg/dZmMsubirsi45j9AT4
1u+XjG/2+GSQRv6EzCaWRQ==
u8vW5y2SRttIHP9hhx+fqksl4XiiUx+QjIOk2+hITXxAagiDFp3uE2PeHg2zFP6D
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOFPUBPOYID993hmow4PfQhItCgegxeWNdVD4M5OUfiqV7yqGqj3L0yy/roD2hGd6ho=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS
Z8UsPk1Q7HtwjRd4g01ryw==
VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo
c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH
c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw==
c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y=
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
0NowFhvxcpa35RgS0fkY9ejQNksclvfpv75ehCpzJxQ=
rTZ4GehPWBQfXf82D1FIzy9S8wkwIsZ3K9dIGaM8SqbP43GsEZhJZUm22nJ+2gPqOOxkmco8qaHhW3SBF16WUg==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au
77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr
NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw==
wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG
NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+
1u+XjG/2+GSQRv6EzCaWRQ==
6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82iJ2tHfDbjNfVtFa8KsTVYDHjxHgDB1l5TRkSasuLGs5g==
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw=
VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw==
VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe
VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k=
VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvxN3810q5P2uCqBbefHbrK
VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap
VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU=
VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA==
VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+Kht3IBHRneKuZIpe6ZGcaJTVPTytE4Bqe8/Zl36QIobQ==
VmVrGQo2zRokW/ZuO9bN6xSxF3eqlbsmTCkJFu+pH46xsXmb4t0jdQQcKfcGkEaM
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuQR8Ewb2c166ROgirDfRPbjt18mJAcMOqb3NN1zON9EtMy2glRMsNpgCk7fMSHxR7K6cqkD4EtrpnRF/O2N/bUo=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6yaURb+J41vJInDMdNBqADJBPzFwU1DWEhzV7Igw3NGvcy7cQ4IWtwYwzl4xxnS4q7aao1GGQ/lpjefMWmVC5io=
VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfaLzRNSm7AkFFrFp+7ESwXtG1lg7SuOb/Fvk+nDWM/LdSTEbaou9dv1kw9992cO1wzuimxPOSJKkAKKU5hd+6f
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ==
VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN68jWU1VqsROJtSXOaVTpeKWZDZ536km13XBycpNTV7Nhu/w7ITcYY+83JHaD4GV4Ww==
VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrDBThH0/mxaArrt9hDAJ1SFmBLkHrD34oMhy7mWtOnyYg==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD
VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg=
VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg==
VmVrGQo2zRokW/ZuO9bN65qgzI0QqVLALq/Lz0RhHgVj4W6v2iAhy3bpNtZbOHJ8qR+FfOTDkpmrnn3Bg7loWg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfko6jVO83Qfz+7nZDbM61ZOu8HuZ+ZNu6hMvpayaQR5SAc=
VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfkosj3Q/Uyc4ih1OKYxi2Aoat/XPMKk6ZFw9dS97mdyNCW7sbOTUzQ7vKis/hh/FWBl
VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E=
VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGez0gsOf0QeJW43sHDXr/9AmAy18p0NZvV4WTviIGdP9rl3wBU2+XWJNGEpDCn6tY=
1u+XjG/2+GSQRv6EzCaWRQ==
mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W
KZTmaJLp+FU9X93j5Tpqtw==
H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn
c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/
c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p
c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp
akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo=
g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U=
Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY=
wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA==
1u+XjG/2+GSQRv6EzCaWRQ==
nZKk1bbhIicHthktrQ3ySmJb3nXruK0WPj1O/nD1Op8=
HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU=
mZ9rrP32sP7RJKYMPGILD+78ba2l0mR6sa16eW97WcI=
O3CUgrw2GJfB+mDjH5+NdlB3aQOXnKkk0z1WAt9Jmo/evqziM8DhkT0YrYGb4COh
VmVrGQo2zRokW/ZuO9bN67lx9HSk8eNN2hBCO4HpKRk=
VmVrGQo2zRokW/ZuO9bN69ZjyuXvRyGNy9kZJ0BSUPrXcl5zPRPxpUUNlqBFMfTepJuwObNjyv5s1Bwxw+0zHA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6yiPlSW1IkAq8PnO6zy+iP3jJCfd1bhtOH5rekZFEjXi
VmVrGQo2zRokW/ZuO9bN6yIV96dETdHvOTzc1eoxsu7+haDOTWWWdxKhfAdfjn09
VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb
VmVrGQo2zRokW/ZuO9bN60+BaOrNhyGitXORdqvsMmwFtJw9x+0nfzpYbP/xMELoydQeOelN/AlsLE8govEqlMQrfUl1PeNk2rX8fh0ZElrak4Q4jLc+GGiQ0DLMMpnHYH/V4LeOzo0DpkYIcND3BA==
VmVrGQo2zRokW/ZuO9bN60jWe2OHiR8czCXypGRfxf3KT4lPFL0wUA7wcV6fbrUq
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN6xYvvPJ68rqA+nqtjvksoecuaI1I6HrJYyQuBkEcxg38GjzrZVp9q6JUUtfh0kiUIfnKe6wTljkMngNv5DLroHo=
VmVrGQo2zRokW/ZuO9bN608vqClPKbLpxLXcA30riX215gq1D0BrebU6pLhAhTm2
1u+XjG/2+GSQRv6EzCaWRQ==
IZj4TYq2HlScfSdRkI+QP1DXXDz3reXEAHM/ysmIbguf7VfJ/dsB+EE4b/l5XAWj
WHkOzVx7seuLmxs5Hu+l/tg64oMMngzQdC6c5uFYkJSfhDT5Ar0GKZd268ltWLHQ
gC7pmugJ3/w5trYRkDiKMWt651l7qmaQDKAvk9X5I0aA2tjC5DMB6fPkHfzLvY+S
1u+XjG/2+GSQRv6EzCaWRQ==
6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA==
1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIdJVXu7JYnBUb+iQtMPqcsojEatZy5pDdbDBezG5DA1qLdz7Wh2R5B0piUodv86TpthC/OIBMMyceg05hLT05OZ
VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc=
VmVrGQo2zRokW/ZuO9bN63uaPsI9SGIYFo4qu6fQZHL9GGcTmbqkN3kKfV4iG89B
VmVrGQo2zRokW/ZuO9bN6364+6D+dT33Zk25tJjqlVgtoL29ZI9bEfBKQ6cfJNwStfozG9mThVS88sNzbEnfeg==
VmVrGQo2zRokW/ZuO9bN69uIuNlhHDMeqLOAnCu5uPWT8evwqXAwcfDwsmWoxBJB
VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP
VmVrGQo2zRokW/ZuO9bN60D33f2rnzf/qOtpO3Ttt+1LbZoGyDvOeulSnp2R0ilck0EERjIpT/ktNM4srJBraTngKI5B04/LBl3323wp0nE=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69ypGNbHyZMpYxMtQ3L+IMga3+inL7nbSVVsRnlPcc0U
VmVrGQo2zRokW/ZuO9bN64Ads2ODFtiVcSlOcVp7F8BTH9nWa2Xc95X7mNQD1kCu
VmVrGQo2zRokW/ZuO9bN60D33f2rnzf/qOtpO3Ttt+12NIPaRofJCp377XaLKfiybd59Ihi7pD1c3XhLcDanb1TM1C1QH4BPTOHZUBJeWHk=
VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY=
VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUikKCfZfG5oV8DWImIHip5MlpGXTmv9J1XV3V25fcmp5aa8KtmY5GAfonB0YuJLBm3Qyg6ipnG5U8MSK0eJ5IXtPW102IBhcOrIDStubnpSgQ==
1u+XjG/2+GSQRv6EzCaWRQ==
u8vW5y2SRttIHP9hhx+fqmx0hSbFOYRP5+aWkTy93ZFnIE/WAAXjKYo8eEgVBv85
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
Y9+a9leOyTyP83EF6Ql2IDgu816zIZ8AFaS4gCHUHX0nvp4HPB59n314/EkGNhAM
dZw+CO0w3i/S02Fo8A/bXw==
Z9W5TG7rzK3ZhbnWjdfJ37W7cUH0ZUZ4etA9G1eKq/5LlOl/dP9tIF16dAZSZX24
kjVV/zwZdFNcXb0/4WIo43yY3WhctfxqYlJeIJT/Zt4raVptLNgdCJ85x2QCj7en
v9zi26p7nzNT8ihOFEIGAPHAc1CmBd9eN42Km9GL48E=
dZw+CO0w3i/S02Fo8A/bXw==
OMrLNahybxYaZATa0EbsUraPl9ZQXb7EqnmkiDHNafM=
GMvSy/NpCRnN8GukyNJXGQ==
ZurH5ZOEBjKhuKKsOJuaZcaZjwgsQXIB71JtU6gEl5anuc8gN8IUF/VRflvF49gf
tb7N17ccXzCvhV9hZH479H/o3ElcTFJ2wxaYkBX0qHE=
ZurH5ZOEBjKhuKKsOJuaZT9OROQzGwZwPFsfOlMOqlW/n1s9EF2CXY7nIzE2BeWQUoQiVGUxtmYToskldhUSJg==
ZurH5ZOEBjKhuKKsOJuaZZOj4gtB0ZybAayCdpeA+rzvR2xX3QPBG1G+d2mABcg83+eEtNBqMdpVGWRkdFdjju+Fp/5HLdmKRV5bbO3vpgI=
xF/vKN+xQbwK2H7vBii2bVr0r4cs4Y9Wc0/WOSJt5a4=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo
KZTmaJLp+FU9X93j5Tpqtw==
NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX
LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR
akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A=
KZTmaJLp+FU9X93j5Tpqtw==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
BIa0ibHnqUboaK9kYHYO4qFxMPyUysXdjYA1FqqhK4UMv4PoX1vehXtIZKdqRHbG
b4OJVZe8QyIpjuTpKXDL9A==
PRG/YRzXVD8iVR3bzEcUnlkfSwESl/MBfBcCyRS23XAS+yqB8BD2Fws8ohDlx4dxPS4gXd1cpWdmGRHNT+D6KSwXWu5PYPEs39ffK4i+qWM=
usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM=
5wsVwKLrIjIWqeTNpSVp9SZAE79lgehXxNsrtfaN8Wk=
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
+78
View File
@@ -0,0 +1,78 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# 系统防火墙模型 - 底层基类
# ------------------------------
import sys
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
class Base(object):
def __init__(self):
pass
# 2024/3/22 下午 3:18 通用返回
def _result(self, status: bool, msg: str) -> dict:
'''
@name 通用返回
@author wzz <2024/3/22 下午 3:19>
@param status: True/False
msg: 提示信息
@return dict{"status":True/False,"msg":"提示信息"}
'''
return {"status": status, "msg": msg}
# 2024/3/22 下午 4:55 检查是否设置了net.ipv4.ip_forward = 1,没有则设置
def check_ip_forward(self) -> dict:
'''
@name 检查是否设置了net.ipv4.ip_forward = 1,没有则设置
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("sysctl net.ipv4.ip_forward")
if "net.ipv4.ip_forward = 1" not in stdout:
# 2024/3/22 下午 4:56 永久设置
stdout, stderr = public.ExecShell("echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf")
if stderr:
return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr))
stdout, stderr = public.ExecShell("sysctl -p")
if stderr:
return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr))
return self._result(True, "设置net.ipv4.ip_forward成功")
return self._result(True, "net.ipv4.ip_forward已经设置")
# 2024/3/18 上午 11:35 处理192.168.1.100-192.168.1.200这种ip范围
# 返回192.168.1.100,192.168.1.101,192.168.1...,192.168.1.200列表
def handle_ip_range(self, ip):
'''
@name 处理192.168.1.100-192.168.1.200这种ip范围的ip列表
@author wzz <2024/3/19 下午 4:58>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
ip_range = ip.split("-")
ip_start = ip_range[0]
ip_end = ip_range[1]
ip_start = ip_start.split(".")
ip_end = ip_end.split(".")
ip_start = [int(i) for i in ip_start]
ip_end = [int(i) for i in ip_end]
ip_list = []
for i in range(ip_start[0], ip_end[0] + 1):
for j in range(ip_start[1], ip_end[1] + 1):
for k in range(ip_start[2], ip_end[2] + 1):
for l in range(ip_start[3], ip_end[3] + 1):
ip_list.append("{}.{}.{}.{}".format(i, j, k, l))
return ip_list
+750
View File
@@ -0,0 +1,750 @@
#!/www/server/panel/pyenv/bin/python3.7
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# 系统防火墙模型 - firewalld封装库
# ------------------------------
import os
import sys
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
from firewallModel.app.appBase import Base
class Firewalld(Base):
def __init__(self):
super().__init__()
self.cmd_str = self._set_cmd_str()
def _set_cmd_str(self) -> str:
return "firewall-cmd"
# 2024/3/20 下午 12:00 获取防火墙状态
def status(self) -> bool:
'''
@name 获取防火墙状态
@author wzz <2024/3/20 下午 12:01>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
stdout, stderr = public.ExecShell("systemctl is-active firewalld")
if "not running" in stdout:
return False
return True
except Exception as e:
return False
# 2024/3/20 下午 12:00 获取防火墙版本号
def version(self) -> str:
'''
@name 获取防火墙版本号
@author wzz <2024/3/20 下午 12:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("firewall-cmd --version")
if "FirewallD is not running" in stdout:
return "Firewalld 没有启动,请先启动再试"
if stderr:
return "获取firewalld版本失败, err: {}".format(stderr)
return stdout.strip()
# 2024/3/20 下午 12:08 启动防火墙
def start(self) -> dict:
'''
@name 启动防火墙
@author wzz <2024/3/20 下午 12:08>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("systemctl start firewalld")
if stderr:
return self._result(False, "启动防火墙失败, err: {}".format(stderr))
return self._result(True, "启动防火墙成功")
# 2024/3/20 下午 12:10 停止防火墙
def stop(self) -> dict:
'''
@name 停止防火墙
@author wzz <2024/3/20 下午 12:10>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("systemctl stop firewalld")
if stderr:
return self._result(False, "停止防火墙失败, err: {}".format(stderr))
return self._result(True, "停止防火墙成功")
# 2024/3/20 下午 12:11 重启防火墙
def restart(self) -> dict:
'''
@name 重启防火墙
@author wzz <2024/3/20 下午 12:11>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("systemctl restart firewalld")
if stderr:
return self._result(False, "重启防火墙失败, err: {}".format(stderr))
return self._result(True, "重启防火墙成功")
# 2024/3/20 下午 12:11 重载防火墙
def reload(self) -> dict:
'''
@name 重载防火墙
@author wzz <2024/3/20 下午 12:11>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("firewall-cmd --reload")
if stderr:
return self._result(False, "重载防火墙失败, err: {}".format(stderr))
return self._result(True, "重载防火墙成功")
# 2024/3/20 下午 12:12 获取所有防火墙端口列表
def list_port(self) -> list:
'''
@name 获取所有防火墙端口列表
@author wzz <2024/3/20 下午 12:12>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self.parse_public_zone()["ports"] + self.list_output_port()
# 2024/3/20 下午 12:12 获取防火墙端口INPUT列表
def list_input_port(self) -> list:
'''
@name 获取防火墙端口列表
@author wzz <2024/3/20 下午 12:12>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self.parse_public_zone()["ports"]
# 2024/3/22 上午 11:28 获取所有OUTPUT的direct 端口规则
def list_output_port(self) -> list:
'''
@name 获取所有OUTPUT的direct 端口规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
list_direct_rules = self.parse_direct_xml()["ports"]
datas = []
for rule in list_direct_rules:
if rule.get("Chain") == "OUTPUT":
datas.append(rule)
return datas
# 2024/3/20 下午 12:21 获取防火墙的rule的ip规则列表
def list_address(self) -> list:
'''
@name 获取防火墙的rule的ip规则列表
@author wzz <2024/3/20 下午 2:45>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self.parse_public_zone()["rules"] + self.list_output_address()
# 2024/3/20 下午 12:21 获取防火墙的rule input的ip规则列表
def list_input_address(self) -> list:
'''
@name 获取防火墙的rule input的ip规则列表
@author wzz <2024/3/20 下午 2:45>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self.parse_public_zone()["rules"]
# 2024/3/22 下午 4:07 获取所有OUTPUT的direct ip规则
def list_output_address(self) -> list:
'''
@name 获取所有OUTPUT的direct ip规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
list_direct_rules = self.parse_direct_xml()["rules"]
datas = []
for rule in list_direct_rules:
if rule.get("Chain") == "OUTPUT":
datas.append(rule)
return datas
# 2024/3/20 下午 5:34 添加或删除防火墙端口
def input_port(self, info: dict, operation: str) -> dict:
'''
@name 添加或删除防火墙端口
@author wzz <2024/3/20 下午 5:34>
@param info:{"Port": args[2], "Protocol": args[3]}
operation: add/remove
@return dict{"status":True/False,"msg":"提示信息"}
'''
if operation not in ["add", "remove"]:
return self._result(False, "不支持的操作: {}".format(operation))
# 2024/3/25 下午 6:00 处理tcp/udp双协议的端口
if info['Protocol'].find("/") != -1:
stdout, stderr = public.ExecShell(
"{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent"
.format(
cmd_str=self.cmd_str,
operation=operation,
port=info['Port'],
prot="tcp"
)
)
if stderr:
return self._result(False, "设置端口失败, err: {}".format(stderr))
stdout, stderr = public.ExecShell(
"{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent"
.format(
cmd_str=self.cmd_str,
operation=operation,
port=info['Port'],
prot="udp"
)
)
if stderr:
return self._result(False, "设置端口失败, err: {}".format(stderr))
else:
# 2024/3/25 下午 6:00 处理单协议的端口
stdout, stderr = public.ExecShell(
"{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent"
.format(
cmd_str=self.cmd_str,
operation=operation,
port=info['Port'],
prot=info['Protocol']
)
)
if stderr:
return self._result(False, "设置端口失败, err: {}".format(stderr))
return self._result(True, "设置入站端口成功")
# 2024/3/20 下午 6:02 设置output的防火墙端口规则
def output_port(self, info: dict, operation: str) -> dict:
'''
@name 设置output的防火墙端口规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
if operation not in ["add", "remove"]:
return self._result(False, "不支持的操作: {}".format(operation))
if info['Strategy'] == "accept":
info['Strategy'] = "ACCEPT"
elif info['Strategy'] == "drop":
info['Strategy'] = "DROP"
elif info['Strategy'] == "reject":
info['Strategy'] = "REJECT"
else:
return self._result(False, "不支持的策略: {}".format(info['Strategy']))
info['Port'] = info['Port'].replace("-", ":")
if "/" in info['Protocol']:
info['Protocol'] = info['Protocol'].split("/")
for pp in info['Protocol']:
if not pp in ["tcp", "udp"]:
return self._result(False, "设置出站端口失败, err: 协议不支持 {}".format(pp))
stdout, stderr = public.ExecShell(
"{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}"
.format(
cmd_str=self.cmd_str,
operation=operation,
priority=info['Priority'],
prot=pp,
port=info['Port'],
strategy=info['Strategy']
)
)
if stderr:
return self._result(False, "设置出站端口失败, err: {}".format(stderr))
else:
stdout, stderr = public.ExecShell(
"{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}"
.format(
cmd_str=self.cmd_str,
operation=operation,
priority=info['Priority'],
prot=info['Protocol'],
port=info['Port'],
strategy=info['Strategy']
)
)
if stderr:
return self._result(False, "设置出站端口失败, err: {}".format(stderr))
return self._result(True, "设置出站端口成功")
def set_rich_rule(self, info: dict, operation: str) -> dict:
'''
@name 添加或删除复杂规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
rule_str = "rule family={}".format(info['Family'].lower())
if "Address" in info and info["Address"] != "all":
rule_str += " source address={}".format(info['Address'])
if info.get("Port"):
rule_str += " port port={}".format(info['Port'])
if info.get("Protocol"):
rule_str += " protocol={}".format(info['Protocol'])
rule_str += " {}".format(info['Strategy'])
stdout, stderr = public.ExecShell(
"{} --zone=public --{}-rich-rule='{}' --permanent"
.format(self.cmd_str, operation, rule_str))
if stderr:
return self._result(False, "设置规则:{} 失败, err: {}".format(operation, rule_str, stderr))
return self._result(True, "设置规则成功".format(operation))
# 2024/3/22 上午 11:35 添加或删除复杂规则
def rich_rules(self, info: dict, operation: str) -> dict:
'''
@name 添加或删除复杂规则
@author wzz <2024/3/22 上午 11:35>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
if operation not in ["add", "remove"]:
return self._result(False, "不支持的规则操作: {}".format(operation))
if "Protocol" in info and info["Protocol"] == "all":
info["Protocol"] = "tcp/udp"
if "Protocol" in info and info['Protocol'].find("/") != -1:
result_list = []
for protocol in info['Protocol'].split("/"):
info['Protocol'] = protocol
result_list.append(self.set_rich_rule(info, operation))
return {"status": True, "msg": result_list}
else:
return self.set_rich_rule(info, operation)
# 2024/3/24 下午 10:43 设置output的防火墙ip规则
def output_rich_rules(self, info: dict, operation: str) -> dict:
'''
@name 设置output的防火墙ip规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
if operation not in ["add", "remove"]:
return self._result(False, "不支持的操作: {}".format(operation))
if info['Strategy'] == "accept":
info['Strategy'] = "ACCEPT"
elif info['Strategy'] == "drop":
info['Strategy'] = "DROP"
elif info['Strategy'] == "reject":
info['Strategy'] = "REJECT"
else:
return self._result(False, "不支持的策略: {}".format(info['Strategy']))
rich_rules = self.cmd_str + " --permanent --direct --{0}-rule ipv4 filter OUTPUT".format(operation)
if "Priority" in info:
rich_rules += " {}".format(info["Priority"])
if "Address" in info:
rich_rules += " -d {}".format(info["Address"])
if "Protocol" in info:
rich_rules += " -p {}".format(info["Protocol"])
if "Port" in info:
info["Port"] = info["Port"].replace("-", ":")
rich_rules += " --dport {}".format(info["Port"])
if "Strategy" in info:
rich_rules += " -j {}".format(info["Strategy"])
stdout, stderr = public.ExecShell(rich_rules)
if "success" not in stdout and stderr:
return self._result(False, "设置出站地址失败, err: {}".format(stderr))
if "NOT_ENABLED" in stderr:
return self._result(False, "规则不存在")
return self._result(True, "设置出站地址成功")
# 2024/3/22 下午 12:22 解析public区域的防火墙规则
def parse_public_zone(self) -> dict:
'''
@name 解析public区域的防火墙规则
@author wzz <2024/3/22 下午 12:22>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"services": services, "ports": ports, "rules": rules} rules是ip规则
'''
try:
import xml.etree.ElementTree as ET
file_path = "/etc/firewalld/zones/public.xml"
if not os.path.exists(file_path):
return {"services": [], "ports": [], "rules": [], "forward_ports": []}
services = []
ports = []
rules = []
forward_ports = []
tree = ET.parse(file_path)
root = tree.getroot()
for elem in root:
# 2024/3/22 下午 3:01 服务规则
if elem.tag == "service":
services.append(elem.attrib['name'])
# 2024/3/22 下午 3:01 端口规则
elif elem.tag == "port":
port = {
"Protocol": elem.attrib["protocol"],
"Port": elem.attrib["port"],
"Strategy": "accept",
"Family": "ipv4",
"Address": "all",
"Chain": "INPUT",
}
ports.append(port)
# 2024/3/22 下午 3:01 复杂的规则配置
elif elem.tag == "rule":
rule = {"Family": elem.attrib["family"] if "family" in elem.attrib else "ipv4"}
for subelem in elem:
rule["Strategy"] = "accept"
if subelem.tag == "source":
if "address" in subelem.attrib:
rule["Address"] = subelem.attrib["address"]
else:
continue
rule["Address"] = "all" if rule["Address"] == "Anywhere" else rule["Address"]
elif subelem.tag == "port":
rule["port"] = {"protocol": subelem.attrib["protocol"], "port": subelem.attrib["port"]}
elif subelem.tag == "drop":
rule["Strategy"] = "drop"
elif subelem.tag == "accept":
rule["Strategy"] = "accept"
elif subelem.tag == "forward-port":
rule["forward-port"] = {
"protocol": subelem.attrib["protocol"],
"S_Port": subelem.attrib["port"],
"T_Address": subelem.attrib["to-addr"],
"T_Port": subelem.attrib["to-port"],
}
# 2024/3/22 下午 3:02 如果端口在里面,就放到端口规则列表中,否则就是ip规则
if "port" in rule:
ports.append({
"Protocol": rule["port"]["protocol"] if "protocol" in rule else "tcp",
"Port": rule["port"]["port"],
"Strategy": rule["Strategy"] if "Strategy" in rule else "accept",
"Family": rule["Family"] if "Family" in rule else "ipv4",
"Address": rule["Address"] if "Address" in rule else "all",
"Chain": "INPUT",
})
# 2024/3/25 下午 5:01 处理带源ip的端口转发规则
elif "forward-port" in rule:
forward_ports.append({
"type": "port_forward",
"number": len(forward_ports) + 1,
"Protocol": rule["forward-port"]["protocol"],
"S_Address": rule["Address"],
"S_Port": rule["forward-port"]["S_Port"],
"T_Address": rule["forward-port"]["T_Address"],
"T_Port": rule["forward-port"]["T_Port"],
})
else:
if "Address" not in rule:
continue
rule["Chain"] = "INPUT"
rules.append(rule)
# 2024/3/25 下午 2:57 端口转发规则
elif elem.tag == "forward-port":
port = {
"type": "port_forward",
"number": len(forward_ports) + 1,
"Protocol": elem.attrib["protocol"] if "protocol" in elem.attrib else "tcp",
"S_Address": "",
"S_Port": elem.attrib["port"] if "port" in elem.attrib else "",
"T_Address": elem.attrib["to-addr"] if "to-addr" in elem.attrib else "",
"T_Port": elem.attrib["to-port"] if "to-port" in elem.attrib else "",
}
forward_ports.append(port)
return {"services": services, "ports": ports, "rules": rules, "forward_ports": forward_ports}
except Exception as e:
return {"services": [], "ports": [], "rules": [], "forward_ports": []}
# 2024/3/22 下午 2:32 解析direct.xml的防火墙规则
def parse_direct_xml(self) -> dict:
'''
@name 解析direct.xml的防火墙规则
@author wzz <2024/3/22 下午 2:32>
@param "data":{"参数名":""} <数据类型> 参数描述
@return list[dict{}...]
'''
try:
import xml.etree.ElementTree as ET
file_path = "/etc/firewalld/direct.xml"
if not os.path.exists(file_path):
return {"ports": [], "rules": []}
ports = []
rules = []
tree = ET.parse(file_path)
root = tree.getroot()
for elem in root:
if elem.tag == "rule":
protocol = "tcp"
port = ""
strategy = ""
address = ""
elem_t = elem.text.split(" ")
# 2024/3/22 下午 4:14 解析 Options 得到端口,策略,地址,协议
for i in elem_t:
if i == "-p":
protocol = elem_t[elem_t.index(i) + 1] # 如果找到匹配项,结果为索引+1的值,-p tcp,值为tcp
elif i == "--dport":
port = elem_t[elem_t.index(i) + 1]
elif i == "-j":
strategy = elem_t[elem_t.index(i) + 1]
elif i == "-d":
address = elem_t[elem_t.index(i) + 1]
rule = {
"Family": elem.attrib["ipv"],
"Chain": elem.attrib["chain"],
"Strategy": strategy.lower(),
"Address": address if address != "" else "all",
# "Options": elem.text
}
# 2024/3/22 下午 4:13 如果端口不为空,就是端口规则
if port != "":
rule["Port"] = port
rule["Protocol"] = protocol
ports.append(rule)
# 2024/3/22 下午 4:14 如果端口为空,就是ip规则
else:
rules.append(rule)
return {"ports": ports, "rules": rules}
except Exception as e:
return {"ports": [], "rules": []}
# 2024/3/22 下午 4:54 检查是否开启了masquerade,没有则开启
def check_masquerade(self) -> dict:
'''
@name 检查是否开启了masquerade,没有则开启
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
stdout, stderr = public.ExecShell("firewall-cmd --query-masquerade")
if "no" in stdout:
stdout, stderr = public.ExecShell("firewall-cmd --add-masquerade")
if stderr:
return self._result(False, "开启masquerade失败, err: {}".format(stderr))
return self._result(True, "开启masquerade成功")
return self._result(True, "masquerade已经开启")
# 2024/3/22 下午 4:57 设置端口转发
def port_forward(self, info: dict, operation: str) -> dict:
'''
@name 设置端口转发
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
if operation not in ["add", "remove"]:
return self._result(False, "不支持的操作: {}".format(operation))
if operation == "add":
check_masquerade = self.check_masquerade()
if not check_masquerade["status"]:
return check_masquerade
# 2024/3/25 下午 6:07 处理有源地址的情况
if "S_Address" in info and info["S_Address"] != "":
# 2024/3/25 下午 6:05 处理tcp/udp双协议的情况
if info['Protocol'].find("/") != -1:
rich_rules = self.cmd_str + " --zone=public"
rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"tcp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format(
operation,
info['Family'],
info['S_Address'],
info['S_Port'],
info['T_Port'],
info['T_Address'],
)
stdout, stderr = public.ExecShell(rich_rules)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
rich_rules = self.cmd_str + " --zone=public"
rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"udp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format(
operation,
info['Family'],
info['S_Address'],
info['S_Port'],
info['T_Port'],
info['T_Address'],
)
stdout, stderr = public.ExecShell(rich_rules)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
# 2024/3/25 下午 6:05 处理单协议的情况
else:
rich_rules = self.cmd_str + " --zone=public"
rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"{4}\" to-port=\"{5}\" to-addr=\"{6}\"'".format(
operation,
info['Family'],
info['S_Address'],
info['S_Port'],
info['Protocol'],
info['T_Port'],
info['T_Address'],
)
rich_rules += " --permanent"
stdout, stderr = public.ExecShell(rich_rules)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
# 2024/3/25 下午 6:08 处理没有源地址的情况
else:
# 2024/3/25 下午 6:05 处理tcp/udp双协议的情况
if info['Protocol'].find("/") != -1:
stdout, stderr = public.ExecShell(
"{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent"
.format(self.cmd_str, operation, info['S_Port'], "udp", info['T_Port'], info['T_Address'])
)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
stdout, stderr = public.ExecShell(
"{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent"
.format(self.cmd_str, operation, info['S_Port'], "tcp", info['T_Port'], info['T_Address'])
)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
# 2024/3/25 下午 6:09 处理单协议的情况
else:
stdout, stderr = public.ExecShell(
"{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent"
.format(self.cmd_str, operation, info['S_Port'], info['Protocol'], info['T_Port'], info['T_Address'])
)
if "success" not in stdout and stderr:
if "ALREADY_ENABLED" in stderr:
return self._result(True, "端口转发规则已经存在")
return self._result(False, "设置端口转发失败, err: {}".format(stderr))
return self._result(True, "设置端口转发成功")
# 2024/3/25 下午 2:37 获取所有端口转发规则
def list_port_forward(self) -> list:
'''
@name 获取所有端口转发规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return list[dict{}...]
'''
return self.parse_public_zone()["forward_ports"]
if __name__ == '__main__':
args = sys.argv
firewall = Firewalld()
Firewalld_status = firewall.status()
if len(args) < 2:
print("Welcome to the Firewalld command-line interface!")
print("Firewall status is :", Firewalld_status)
print("Firewall version: ", firewall.version())
if Firewalld_status == "not running":
print("Firewalld未启动,请启动Firewalld后再执行命令!")
print("启动命令: start")
print()
sys.exit(1)
print("Usage: ")
print("status: Get the status of the firewall.")
print("version: Get the version of the firewall.")
print("start: Start the firewall.")
print("stop: Stop the firewall.")
print("restart: Restart the firewall.")
print("reload: Reload the firewall.")
print("list_input_port: List all input ports.")
print("list_input_address: List all input address rules.")
print("input_port: Add or remove input port.")
print("output_port: Add or remove output port.")
print("list_output_port: List all output ports.")
print("list_output_address: List all output address rules.")
print("rich_rules: Add or remove rich rules.")
sys.exit(1)
if args[1] == "status":
print("Firewall status is :", Firewalld_status)
elif args[1] == "version":
print("Firewall version: ", firewall.version())
elif args[1] == "start":
print(firewall.start())
elif args[1] == "stop":
print(firewall.stop())
elif args[1] == "restart":
print(firewall.restart())
elif args[1] == "reload":
print(firewall.reload())
elif args[1] == "list_input_port":
print(firewall.list_input_port())
elif args[1] == "list_input_address":
print(firewall.list_input_address())
elif args[1] == "input_port":
if len(args) < 4:
print("Usage: input_port Port Protocol")
sys.exit(1)
print(firewall.input_port({"Port": args[2], "Protocol": args[3]}, args[4]))
elif args[1] == "output_port":
if len(args) < 6:
print("Usage: output_port Port Protocol Strategy Priority")
sys.exit(1)
print(firewall.output_port({"Port": args[2], "Protocol": args[3], "Strategy": args[4], "Priority": args[5]}, args[6]))
elif args[1] == "list_output_port":
print(firewall.list_output_port())
elif args[1] == "list_output_address":
print(firewall.list_output_address())
elif args[1] == "rich_rules":
if len(args) < 4:
print("Usage: rich_rules Family Address Port Protocol Strategy")
sys.exit(1)
print(firewall.rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6]}, args[7]))
elif args[1] == "output_rich_rules":
if len(args) < 6:
print("Usage: output_rich_rules Family Address Port Protocol Strategy Priority")
sys.exit(1)
print(firewall.output_rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6], "Priority": args[7]}, args[8]))
elif args[1] == "port_forward":
if len(args) < 7:
print("Usage: port_forward Port Protocol ToPort ToAddr")
sys.exit(1)
print(firewall.port_forward({"Port": args[2], "Protocol": args[3], "ToPort": args[4], "ToAddr": args[5]}, args[6]))
else:
print("Command not found!")
sys.exit(1)
+939
View File
@@ -0,0 +1,939 @@
#!/www/server/panel/pyenv/bin/python3.7
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# 系统防火墙模型 - iptables封装库
# ------------------------------
import re
import subprocess
import os
import sys
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
from firewallModel.app.appBase import Base
# import re
class Iptables(Base):
def __init__(self):
self.cmd_str = self._set_cmd_str()
self.protocol = {
"6": "tcp",
"17": "udp",
"0": "all"
}
def _set_cmd_str(self):
return "iptables"
# 2024/3/19 下午 5:00 获取系统防火墙的运行状态
def status(self):
'''
@name 获取系统防火墙的运行状态
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return "running"
# 2024/3/19 下午 5:00 获取系统防火墙的版本号
def version(self):
'''
@name 获取系统防火墙的版本号
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = public.ExecShell("iptables -v 2>&1|awk '{print $2}'|head -1")[0].replace("\n", "")
if result == "":
return "未知的iptables版本"
return result
except Exception as e:
return "未知版本"
# 2024/3/19 下午 5:00 启动防火墙
def start(self):
'''
@name 启动防火墙
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self._result(True, "当前系统防火墙为iptables,不支持设置状态")
# 2024/3/19 下午 5:00 停止防火墙
def stop(self):
'''
@name 停止防火墙
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self._result(True, "当前系统防火墙为iptables,不支持停止")
# 2024/3/19 下午 4:59 重启防火墙
def restart(self):
'''
@name 重启防火墙
@author wzz <2024/3/19 下午 4:59>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self._result(True, "当前系统防火墙为iptables,不支持重启")
# 2024/3/19 下午 4:59 重载防火墙
def reload(self):
'''
@name 重载防火墙
@author wzz <2024/3/19 下午 4:59>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self._result(True, "当前系统防火墙为iptables,不支持重载")
# 2024/3/19 下午 3:36 检查表名是否合法
def check_table_name(self, table_name):
'''
@name 检查表名是否合法
@param "table_name": "filter/nat/mangle/raw/security"
@return dict{"status":True/False,"msg":"提示信息"}
'''
table_names = ['filter', 'nat', 'mangle', 'raw', 'security']
if table_name not in table_names:
return False
return True
# 2024/3/19 下午 3:55 解析规则列表输出,返回规则列表字典
def parse_rules(self, stdout):
'''
@name 解析规则列表输出,返回规则列表字典
@author wzz <2024/3/19 下午 3:53>
字段含义:
"number": 规则编号,对应规则在链中的顺序。
"chain": 规则所属的链的名称。
"pkts": 规则匹配的数据包数量。
"bytes": 规则匹配的数据包字节数。
"target": 规则的目标动作,表示数据包匹配到该规则后应该执行的操作。
"prot": 规则适用的协议类型。
"opt": 规则的选项,包括规则中使用的匹配条件或特定选项。
"in": 规则匹配的数据包的输入接口。
"out": 规则匹配的数据包的输出接口。
"source": 规则匹配的数据包的源地址。
"destination": 规则匹配的数据包的目标地址。
"options": 规则的其他选项或说明,通常是规则中的注释或附加信息。
protocol(port协议头中数字对应的协议类型):
0: 表示所有协议
1: ICMP(Internet 控制消息协议)
6: TCP(传输控制协议)
17: UDP(用户数据报协议)
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
lines = stdout.strip().split('\n')
rules = []
current_chain = None
for line in lines:
if line.startswith("Chain"):
current_chain = line.split()[1]
elif (line.startswith("target") or line.strip() == "" or "source" in line or
"Warning: iptables-legacy tables present" in line):
# 过滤表头,空行,警告
continue
else:
rule_info = line.split()
rule = {
"number": rule_info[0],
"chain": current_chain,
"pkts": rule_info[1],
"bytes": rule_info[2],
"target": rule_info[3],
"prot": rule_info[4],
"opt": rule_info[5],
"in": rule_info[6],
"out": rule_info[7],
"source": rule_info[8],
"destination": rule_info[9],
"options": " ".join(rule_info[10:]).strip()
}
rules.append(rule)
return rules
# 2024/3/19 下午 3:02 列出指定表的指定链的规则
def list_rules(self, parm):
'''
@name 列出指定表的指定链的规则
@author wzz <2024/3/19 下午 3:02>
@param
@return
'''
try:
if not self.check_table_name(parm['table']):
return "错误: 不支持的表名."
stdout = subprocess.check_output(
[self.cmd_str, '-t', parm['table'], '-L', parm['chain_name'], '-nv', '--line-numbers'],
stderr=subprocess.STDOUT, universal_newlines=True
)
return self.parse_rules(stdout)
except Exception as e:
return []
# 2024/4/29 下午12:16 列出iptables中所有INPUT和OUTPUT的端口规则
def list_port(self):
'''
@name 列出iptables中所有INPUT和OUTPUT的端口规则
@return [{
"Protocol": "tcp",
"Port": "8888",
"Strategy": "accept",
"Family": "ipv4",
"Address": "all",
"Chain": "INPUT"
}]
'''
try:
list_port = self.list_input_port() + self.list_output_port()
for i in list_port:
i["Strategy"] = i["Strategy"].lower()
return list_port
except Exception as e:
return []
# 2024/4/29 下午2:39 列出防火墙中所有的INPUT端口规则
def list_input_port(self):
'''
@name 列出防火墙中所有的INPUT端口规则
@return [{
"Protocol": "tcp",
"Port": "8888",
"Strategy": "accept",
"Family": "ipv4",
"Address": "all",
"Chain": "INPUT"
}]
'''
try:
list_port = self.get_chain_port("INPUT")
for i in list_port:
i["Strategy"] = i["Strategy"].lower()
return list_port
except Exception as e:
return []
# 2024/4/29 下午2:39 列出防火墙中所有的OUTPUT端口规则
def list_output_port(self):
'''
@name 列出防火墙中所有的OUTPUT端口规则
@return [{
"Protocol": "tcp",
"Port": "8888",
"Strategy": "accept",
"Family": "ipv4",
"Address": "all",
"Chain": "OUTPUT"
}]
'''
try:
list_port = self.get_chain_port("OUTPUT")
for i in list_port:
i["Strategy"] = i["Strategy"].lower()
return list_port
except Exception as e:
return []
# 2024/4/29 下午3:28 根据链来获取端口规则,暂时只支持INPUT/OUTPUT链
def get_chain_port(self, chain):
'''
@name 根据链来获取端口规则
@author wzz <2024/4/29 下午3:29>
@param chain = INPUT/OUTPUT
@return [{
"Protocol": "tcp",
"Port": "8888",
"Strategy": "accept",
"Family": "ipv4",
"Address": "all",
"Chain": "OUTPUT"
}]
'''
if chain not in ["INPUT", "OUTPUT"]:
return []
try:
stdout = self.get_chain_data(chain)
if stdout == "":
return []
lines = stdout.strip().split('\n')
rules = []
for line in lines:
if line.startswith("Chain"):
continue
if not "dpt:" in line and not "multiport sports" in line:
continue
rule_info = line.split()
if rule_info[0] == "num":
continue
if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]:
continue
if not rule_info[4] in self.protocol:
continue
if not "dpt" in rule_info[-1] and not "-" in rule_info[-1] and not ":" in rule_info[-1]:
continue
if ":" in rule_info[-1] and not "dpt" in rule_info[-1]:
Port = rule_info[-1]
elif "-" in rule_info[-1]:
Port = rule_info[-5].split(":")[1]
else:
Port = rule_info[-1].split(":")[1]
if "source IP range" in line and "multiport sports" in line:
Address = rule_info[-4]
elif not "0.0.0.0/0" in rule_info[8]:
Address = rule_info[8]
elif "-" in rule_info[-1]:
Address = rule_info[-1]
else:
Address = "all"
rule = {
"Protocol": self.protocol[rule_info[4]],
"Port": Port,
"Strategy": rule_info[3],
"Family": "ipv4",
"Address": Address,
"Chain": chain,
}
rules.append(rule)
return rules
except Exception as e:
return []
# 2024/4/29 下午3:28 根据链来获取IP规则,暂时只支持INPUT/OUTPUT链
def get_chain_ip(self, chain):
'''
@name 根据链来获取端口规则
@author wzz <2024/4/29 下午3:29>
@param chain = INPUT/OUTPUT
@return [
{
"Family": "ipv4",
"Address": "192.168.1.190",
"Strategy": "accept",
"Chain": "INPUT"
}
]
'''
if chain not in ["INPUT", "OUTPUT"]:
return []
try:
stdout = self.get_chain_data(chain)
if stdout == "":
return []
lines = stdout.strip().split('\n')
rules = []
for line in lines:
if line.startswith("Chain"):
continue
if "dpt:" in line or "multiport sports" in line:
continue
rule_info = line.split()
if rule_info[0] == "num":
continue
if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]:
continue
if not rule_info[4] in self.protocol:
continue
Address = ""
if not "0.0.0.0/0" in rule_info[8]:
Address = rule_info[8]
elif "0.0.0.0/0" in rule_info[8] and "-" in rule_info[-1]:
Address = rule_info[-1]
if Address == "":
continue
rule = {
"Family": "ipv4",
"Address": Address,
"Strategy": rule_info[3],
"Chain": chain,
}
rules.append(rule)
return rules
except Exception as e:
return []
# 2024/4/29 下午4:01 获取指定链的数据
def get_chain_data(self, chain):
'''
@name 获取指定链的数据
@author wzz <2024/4/29 下午4:01>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
cmd = "{} -t filter -L {} -nv --line-numbers".format(self.cmd_str, chain)
stdout, stderr = public.ExecShell(cmd)
return stdout
except Exception as e:
return ""
# 2024/4/29 下午2:46 列出防火墙中所有的INPUT和OUTPUT的ip规则
def list_address(self):
'''
@name 列出防火墙中所有的ip规则
@author wzz <2024/4/29 下午2:47>
@param "data":{"参数名":""} <数据类型> 参数描述
@return
'''
try:
list_address = self.get_chain_ip("INPUT") + self.get_chain_ip("OUTPUT")
for i in list_address:
i["Strategy"] = i["Strategy"].lower()
return list_address
except Exception as e:
return []
# 2024/4/29 下午2:48 列出防火墙中所有input的ip规则
def list_input_address(self):
'''
@name 列出防火墙中所有input的ip规则
@author wzz <2024/4/29 下午2:48>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
list_address = self.get_chain_ip("INPUT")
for i in list_address:
i["Strategy"] = i["Strategy"].lower()
return list_address
except Exception as e:
return []
# 2024/4/29 下午2:49 列出防火墙中所有output的ip规则
def list_output_address(self):
'''
@name 列出防火墙中所有output的ip规则
@author wzz <2024/4/29 下午2:49>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
list_address = self.get_chain_ip("OUTPUT")
for i in list_address:
i["Strategy"] = i["Strategy"].lower()
return list_address
except Exception as e:
return []
# 2024/4/29 下午2:49 添加INPUT端口规则
def input_port(self, info, operation):
'''
@name 添加INPUT端口规则
@author wzz <2024/4/29 下午2:50>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return self.set_chain_port(info, operation, "INPUT")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/4/29 下午2:50 设置output端口策略
def output_port(self, info, operation):
'''
@name 设置output端口策略
@author wzz <2024/4/29 下午2:50>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
return self.set_chain_port(info, operation, "OUTPUT")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/4/29 下午4:49 添加/删除指定链的端口规则
def set_chain_port(self, info, operation, chain):
'''
@name 添加/删除指定链的端口规则
@author wzz <2024/4/29 下午4:49>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not chain in ["INPUT", "OUTPUT"]:
return self._result(False, "设置端口规则失败:{}".format("不支持的链类型"))
if info['Protocol'] not in ["tcp", "udp"]:
return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型"))
if info["Strategy"] == "accept":
info["Strategy"] = "ACCEPT"
elif info["Strategy"] == "drop":
info["Strategy"] = "DROP"
elif info["Strategy"] == "reject":
info["Strategy"] = "REJECT"
else:
return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型"))
if operation == "add":
operation = "-I"
elif operation == "remove":
operation = "-D"
rule = "{} -t filter {} {} -p {} --dport {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Protocol'],
info['Port'],
info['Strategy']
)
stdout, stderr = public.ExecShell(rule)
if stderr:
return self._result(False, "设置端口规则失败:{}".format(stderr))
return self._result(True, "设置端口规则成功")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/4/29 下午5:01 添加/删除指定链的复杂端口规则
def set_chain_rich_port(self, info, operation, chain):
'''
@name 添加/删除指定链的复杂端口规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not chain in ["INPUT", "OUTPUT"]:
return self._result(False, "设置端口规则失败:{}".format("不支持的链类型"))
if "Address" in info and info["Address"] == "":
info["Address"] = "all"
if "Address" in info and public.is_ipv6(info['Address']):
return self._result(False, "设置端口规则失败:{}".format("不支持的IPV6地址"))
if info['Protocol'] not in ["tcp", "udp"]:
return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型"))
if info["Strategy"] == "accept":
info["Strategy"] = "ACCEPT"
elif info["Strategy"] == "drop":
info["Strategy"] = "DROP"
elif info["Strategy"] == "reject":
info["Strategy"] = "REJECT"
else:
return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型"))
if operation == "add":
operation = "-I"
elif operation == "remove":
operation = "-D"
info['Port'] = info['Port'].replace("-", ":")
info["Address"] = info["Address"].replace(":", "-")
if ":" in info['Port'] or "-" in info['Port']:
if ":" in info["Address"] or "-" in info["Address"]:
# iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -p tcp -m multiport --sports 8000:9000 -j ACCEPT
rule = "{} -t filter {} {} -m iprange --src-range {} -p {} -m multiport --sports {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Address'],
info['Protocol'],
info['Port'],
info['Strategy']
)
else:
# iptables -t filter -I INPUT -p tcp -m multiport --sports 8000:9000 -s 192.168.1.100 -j ACCEPT
rule = "{} -t filter {} {} -p {} -m multiport --sports {} -s {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Protocol'],
info['Port'],
info['Address'],
info['Strategy']
)
else:
if ":" in info["Address"] or "-" in info["Address"]:
# iptables -t filter -I OUTPUT -p tcp --dport 22333 -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT
rule = "{} -t filter {} {} -p {} --dport {} -m iprange --src-range {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Protocol'],
info['Port'],
info['Address'],
info['Strategy']
)
else:
# iptables -t filter -I OUTPUT -p tcp --dport 22333 -s 192.168.1.0/24 -j ACCEPT
rule = "{} -t filter {} {} -p {} --dport {} -s {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Protocol'],
info['Port'],
info['Address'],
info['Strategy']
)
stdout, stderr = public.ExecShell(rule)
if stderr:
return self._result(False, "设置端口规则失败:{}".format(stderr))
return self._result(True, "设置端口规则成功")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/4/29 下午5:01 添加/删除指定链的复杂ip规则
def set_chain_rich_ip(self, info, operation, chain):
'''
@name 添加/删除指定链的复杂ip规则
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not chain in ["INPUT", "OUTPUT"]:
return self._result(False, "设置规则失败:{}".format("不支持的链类型"))
if "Address" in info and info["Address"] == "":
info["Address"] = "all"
if "Address" in info and public.is_ipv6(info['Address']):
return self._result(False, "设置规则失败:{}".format("不支持的IPV6地址"))
if info["Strategy"] == "accept":
info["Strategy"] = "ACCEPT"
elif info["Strategy"] == "drop":
info["Strategy"] = "DROP"
elif info["Strategy"] == "reject":
info["Strategy"] = "REJECT"
else:
return self._result(False, "设置规则失败:{}".format("不支持的策略类型"))
if operation == "add":
operation = "-I"
elif operation == "remove":
operation = "-D"
if ":" in info["Address"] or "-" in info["Address"]:
# iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT
rule = "{} -t filter {} {} -m iprange --src-range {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Address'],
info['Strategy']
)
else:
# iptables -t filter -I INPUT -s 192.168.1.100 -j ACCEPT
rule = "{} -t filter {} {} -s {} -j {}".format(
self.cmd_str,
operation,
chain,
info['Address'],
info['Strategy']
)
stdout, stderr = public.ExecShell(rule)
if stderr:
return self._result(False, "设置规则失败:{}".format(stderr))
return self._result(True, "设置规则成功")
except Exception as e:
return self._result(False, "设置规则失败:{}".format(str(e)))
# 2024/4/29 下午2:51 INPUT复杂一些的规则管理
def rich_rules(self, info, operation):
'''
@name
@author wzz <2024/4/29 下午2:51>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if "Priority" in info and not "Port" in info:
return self.set_chain_rich_ip(info, operation, "INPUT")
else:
return self.set_chain_rich_port(info, operation, "INPUT")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/4/29 下午2:52 OUTPUT复杂一些的规则管理
def output_rich_rules(self, info, operation):
'''
@name OUTPUT复杂一些的规则管理
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if "Priority" in info and not "Port" in info:
return self.set_chain_rich_ip(info, operation, "OUTPUT")
else:
return self.set_chain_rich_port(info, operation, "OUTPUT")
except Exception as e:
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/3/19 下午 3:03 清空指定链中的所有规则
def flush_chain(self, chain_name):
'''
@name 清空指定链中的所有规则
@author wzz <2024/3/19 下午 3:03>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
subprocess.check_output(
[self.cmd_str, '-F', chain_name], stderr=subprocess.STDOUT, universal_newlines=True
)
return chain_name + " chain flushed successfully."
except Exception as e:
return "Failed to flush " + chain_name + " chain."
# 2024/3/19 下午 3:03 获取当前系统中可用的链的名称列表
def get_chain_names(self, parm):
'''
@name 获取当前系统中可用的链的名称列表
@author wzz <2024/3/19 下午 3:03>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not self.check_table_name(parm['table']):
return "错误: 不支持的表名."
stdout = subprocess.check_output(
[self.cmd_str, '-t', parm['table'], '-L'], stderr=subprocess.STDOUT, universal_newlines=True
)
chain_names = re.findall(r"Chain\s([A-Z]+)", stdout)
return chain_names
except Exception as e:
return []
# 2024/3/19 下午 3:17 构造端口转发规则,然后调用insert_rule方法插入规则
def port_forward(self, info, operation):
'''
@name 构造端口转发规则,然后调用insert_rule方法插入规则
@param "info": {
"Protocol": "tcp/udp",
"S_Port": "80",
"T_Address": "0.0.0.0/0",
"T_Port": "8080"
}
@param "operation": "add" or "remove"
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
rule = " -p {}".format(info['Protocol'])
if "S_Address" in info and info['S_Address'] != "":
rule += " -s {}".format(info['S_Address'])
rule += " --dport {0} -j DNAT --to-destination {1}:{2}".format(
info['S_Port'],
info['T_Address'],
info['T_Port'],
)
parm = {
"table": "nat",
"chain_name": "PREROUTING",
"rule": rule
}
if operation not in ["add", "remove"]:
return "请输入正确的操作类型. (add/remove)"
if operation == "add":
parm['type'] = "-I"
elif operation == "remove":
parm['type'] = "-D"
return self.rule_manage(parm)
except Exception as e:
return self._result(False, "设置端口转发规则失败:{}".format(str(e)))
# 2024/3/19 下午 3:03 在指定链中管理规则
def rule_manage(self, parm):
'''
@name 在指定链中管理规则
@author wzz <2024/3/19 下午 3:03>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if not self.check_table_name(parm['table']):
return self._result(False, "不支持的表名{}".format(parm['table']))
rule = "{} -t {} {} {} {}".format(
self.cmd_str, parm['table'], parm['type'], parm['chain_name'], parm['rule']
)
stdout, stderr = public.ExecShell(rule)
if stderr:
return self._result(False, "规则设置失败:{}".format(stderr))
return self._result(True, "规则设置成功")
except Exception as e:
return self._result(False, "规则设置失败: {}".format(str(e)))
# 2024/4/29 下午5:55 获取所有端口转发列表
def list_port_forward(self):
'''
@name 获取所有端口转发列表
@author wzz <2024/4/29 下午5:55>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
return self.get_nat_prerouting_rules()
# 2024/3/19 下午 4:00 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回
def get_nat_prerouting_rules(self):
'''
@name 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
port_forward_rules = self.list_rules({"table": "nat", "chain_name": "PREROUTING"})
rules = []
for rule in port_forward_rules:
if rule["target"] != "DNAT": continue
options = rule["options"].split(' ')
protocol = "TCP"
if rule["prot"] == "6" or rule["prot"] == "tcp":
protocol = "TCP"
elif rule["prot"] == "17" or rule["prot"] == "udp":
protocol = "UDP"
elif rule["prot"] == "0" or rule["prot"] == "all":
protocol = "TCP/UDP"
rules.append({
"type": "port_forward",
"number": rule["number"],
"S_Address": rule["source"],
"S_Port": options[1].split("dpt:")[1],
"T_Address": options[2].split("to:")[1].split(":")[0],
"T_Port": options[2].split("to:")[1].split(":")[1],
"Protocol": protocol.lower()
})
return rules
except Exception as e:
return []
# 2024/4/29 下午2:43 格式化输出json
def format_json(self, data):
'''
@name 格式化输出json
@param "data": json数据
@return json字符串
'''
import json
from pygments import highlight, lexers, formatters
formatted_json = json.dumps(data, indent=3)
colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(),
formatters.TerminalFormatter())
return colorful_json
if __name__ == '__main__':
args = sys.argv
firewall = Iptables()
if len(args) < 2:
print("Welcome to the iptables command-line interface!")
print()
print("Available options:")
print("list_rules: list_rules <table> <chain_name>")
print("flush_chain: flush_chain <table>")
print("get_chain_names: get_chain_names <table>")
print("port_forward: port_forward <S_Address> <S_Port> <T_Address> <T_Port> <Protocol> <Operation>")
print("get_nat_prerouting_rules: get_nat_prerouting_rules")
print()
sys.exit(1)
if args[1] == "list_rules":
# firewall.list_rules({"table": args[2], "chain_name": args[3]})
print(firewall.list_rules({"table": args[2], "chain_name": args[3]}))
elif args[1] == "flush_chain":
print(firewall.flush_chain(args[2]))
elif args[1] == "get_chain_names":
table = args[2] if len(args) > 2 else "filter"
print(firewall.get_chain_names({"table": table}))
elif args[1] == "port_forward":
if len(args) < 8:
print("传参使用方法: port_forward <S_Address> <S_Port> <T_Address> <T_Port> <Protocol> <Operation>")
sys.exit(1)
info = {
"S_Address": args[2],
"S_Port": args[3],
"T_Address": args[4],
"T_Port": args[5],
"Protocol": args[6]
}
print(firewall.port_forward(info, args[7]))
elif args[1] == "get_nat_prerouting_rules":
import json
from pygments import highlight, lexers, formatters
formatted_json = json.dumps(firewall.get_nat_prerouting_rules(), indent=3)
colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(),
formatters.TerminalFormatter())
print(colorful_json)
# print(firewall.get_nat_prerouting_rules())
elif args[1] == "list_port":
print(firewall.format_json(firewall.list_port()))
elif args[1] == "list_input_port":
print(firewall.format_json(firewall.list_input_port()))
elif args[1] == "list_output_port":
print(firewall.format_json(firewall.list_output_port()))
elif args[1] == "list_address":
print(firewall.format_json(firewall.list_address()))
elif args[1] == "list_input_address":
print(firewall.format_json(firewall.list_input_address()))
elif args[1] == "list_output_address":
print(firewall.format_json(firewall.list_output_address()))
elif args[1] == "input_port":
info = {
"Protocol": args[2],
"Port": args[3],
"Strategy": args[4]
}
print(firewall.input_port(info, args[5]))
elif args[1] == "output_port":
info = {
"Protocol": args[2],
"Port": args[3],
"Strategy": args[4]
}
print(firewall.output_port(info, args[5]))
elif args[1] == "rich_rules":
info = {
"Protocol": args[2],
"Port": args[3],
"Address": args[4],
"Strategy": args[5]
}
print(firewall.rich_rules(info, args[6]))
elif args[1] == "output_rich_rules":
info = {
"Protocol": args[2],
"Port": args[3],
"Address": args[4],
"Strategy": args[5]
}
print(firewall.output_rich_rules(info, args[6]))
else:
print("不支持的传参: " + args[1])
sys.exit(1)
+729
View File
@@ -0,0 +1,729 @@
#!/www/server/panel/pyenv/bin/python3.7
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# 系统防火墙模型 - ufw封装库
# ------------------------------
import subprocess
import os
import sys
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
# import re
from firewallModel.app.appBase import Base
class Ufw(Base):
def __init__(self):
self.cmd_str = self._set_cmd_str()
def _set_cmd_str(self):
return "ufw"
# 2024/3/19 下午 5:00 获取系统防火墙的运行状态
def status(self):
'''
@name 获取系统防火墙的运行状态
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run([self.cmd_str, "status"], capture_output=True, text=True, check=True)
if "Status: active" in result.stdout:
return "running"
elif "状态: 激活" in result.stdout:
return "running"
else:
return "not running"
except subprocess.CalledProcessError:
return "not running"
except Exception as e:
return "not running"
# 2024/3/19 下午 5:00 获取系统防火墙的版本号
def version(self):
'''
@name 获取系统防火墙的版本号
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run([self.cmd_str, "version"], capture_output=True, text=True, check=True)
info = result.stdout.replace("\n", "")
return info.replace("ufw ", "")
except Exception as e:
return "未知版本"
# 2024/3/19 下午 5:00 启动防火墙
def start(self):
'''
@name 启动防火墙
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
stdout, stderr = public.ExecShell("echo y | {} enable".format(self.cmd_str))
if stderr:
return self._result(False, "启动防火墙失败:{}".format(stderr))
return self._result(True, "启动防火墙成功")
except Exception as e:
return self._result(False, "启动防火墙失败:{}".format(str(e)))
# 2024/3/19 下午 5:00 停止防火墙
def stop(self):
'''
@name 停止防火墙
@author wzz <2024/3/19 下午 5:00>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
stdout, stderr = public.ExecShell("{} disable".format(self.cmd_str))
if stderr:
return self._result(False, "停止防火墙失败:{}".format(stderr))
return self._result(True, "停止防火墙成功")
except Exception as e:
return self._result(False, "停止防火墙失败:{}".format(str(e)))
# 2024/3/19 下午 4:59 重启防火墙
def restart(self):
'''
@name 重启防火墙
@author wzz <2024/3/19 下午 4:59>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
self.stop()
self.start()
except Exception as e:
return self._result(False, "重启防火墙失败:{}".format(str(e)))
# 2024/3/19 下午 4:59 重载防火墙
def reload(self):
'''
@name 重载防火墙
@author wzz <2024/3/19 下午 4:59>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
subprocess.run([self.cmd_str, "reload"], check=True, stdout=subprocess.PIPE)
except Exception as e:
return self._result(False, "重载防火墙失败:{}".format(str(e)))
# 2024/3/19 上午 10:39 列出防火墙中所有端口规则
def list_port(self):
'''
@name 列出防火墙中所有端口规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
item_fire = self._load_info(line, "port")
if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]:
item_fire["Port"] = item_fire["Port"].replace(":", "-")
item_fire["Address"] = "all" if item_fire["Address"] == "Anywhere" else item_fire["Address"]
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 上午 10:39 列出防火墙中所有input端口规则
def list_input_port(self):
'''
@name 列出防火墙中所有input端口规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
item_fire = self._load_info(line, "port")
if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]:
item_fire["Port"] = item_fire["Port"].replace(":", "-")
if item_fire["Chain"] == "INPUT":
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 上午 10:39 列出防火墙中所有output端口规则
def list_output_port(self):
'''
@name 列出防火墙中所有output端口规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
item_fire = self._load_info(line, "port")
if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]:
item_fire["Port"] = item_fire["Port"].replace(":", "-")
if item_fire["Chain"] == "OUTPUT":
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 上午 10:39 列出防火墙中所有的ip规则
def list_address(self):
'''
@name 列出防火墙中所有的ip规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
item_fire = self._load_info(line, "address")
if "Port" in item_fire: continue
if item_fire.get("Address"):
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 上午 10:39 列出防火墙中所有input的ip规则
def list_input_address(self):
'''
@name 列出防火墙中所有input的ip规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
if " IN" not in line:
continue
item_fire = self._load_info(line, "address")
if "Port" in item_fire: continue
if item_fire.get("Address"):
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 上午 10:39 列出防火墙中所有output的ip规则
def list_output_address(self):
'''
@name 列出防火墙中所有output的ip规则
@author wzz <2024/3/19 上午 10:39>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
result = subprocess.run(
[self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True
)
port_infos = result.stdout.split("\n")
datas = []
is_start = False
for line in port_infos:
if line.startswith("-"):
is_start = True
continue
if not is_start:
continue
if " OUT" not in line:
continue
item_fire = self._load_info(line, "address")
if "Port" in item_fire: continue
if item_fire.get("Address"):
datas.append(item_fire)
return datas
except Exception as e:
return []
# 2024/3/19 下午 4:59 添加端口规则
def input_port(self, info, operation):
'''
@name 添加端口规则
@author wzz <2024/3/19 下午 4:59>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if info["Strategy"] == "accept":
info["Strategy"] = "allow"
elif info["Strategy"] == "drop":
info["Strategy"] = "deny"
if info["Port"].find('-') != -1:
info["Port"] = info["Port"].replace('-', ':')
if operation == "add":
if info['Protocol'].find("/") != -1:
rich_rule = self.cmd_str + " insert 1 {} {}".format(info['Strategy'], info['Port'])
stdout, stderr = public.ExecShell(rich_rule)
elif info['Protocol'] == "tcp/udp":
stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port'])
else:
stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port'] + "/" + info['Protocol'])
else:
if info['Protocol'].find("/") != -1:
rich_rule = "{} delete {} {}".format(self.cmd_str, info['Strategy'], info['Port'])
stdout, stderr = public.ExecShell(rich_rule)
elif info['Protocol'] == "tcp/udp":
stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port'])
else:
stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port'] + "/" + info['Protocol'])
if stderr:
if "setlocale" in stderr:
return self._result(True, "设置端口规则成功")
return self._result(False, "设置端口规则失败:{}".format(stderr))
return self._result(True, "设置端口规则成功")
except Exception as e:
if "setlocale" in str(e):
return self._result(True, "设置端口规则成功")
return self._result(False, "设置端口规则失败:{}".format(str(e)))
# 2024/3/24 下午 11:28 设置output端口策略
def output_port(self, info, operation):
'''
@name 设置output端口策略
@param info: 端口号
@param operation: 操作
@return None
'''
try:
if info["Strategy"] == "accept":
info["Strategy"] = "allow"
elif info["Strategy"] == "drop":
info["Strategy"] = "deny"
if operation == "add":
if info['Protocol'].find('/') != -1:
cmd = "{} {} out {}".format(self.cmd_str, info['Strategy'], info['Port'])
else:
cmd = "{} {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol'])
else:
if info['Protocol'].find('/') != -1:
cmd = "{} delete {} out {}".format(self.cmd_str, info['Strategy'], info['Port'])
else:
cmd = "{} delete {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol'])
stdout, stderr = public.ExecShell(cmd)
if stderr:
if "setlocale" in stderr:
return self._result(True, "设置端口规则成功")
return self._result(False, "设置output端口规则失败:{}".format(stderr))
return self._result(True, "设置output端口规则成功")
except Exception as e:
if "setlocale" in str(e):
return self._result(True, "设置output端口规则成功")
return self._result(False, "设置output端口规则失败:{}".format(str(e)))
# 2024/3/19 下午 4:58 复杂一些的规则管理
def rich_rules(self, info, operation):
'''
@name 复杂一些的规则管理
@author wzz <2024/3/19 下午 4:58>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
if info["Strategy"] == "accept":
info["Strategy"] = "allow"
elif info["Strategy"] == "drop":
info["Strategy"] = "deny"
else:
return self._result(False, "未知的策略参数:{}".format(info["Strategy"]))
rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"])
if "Address" in info and public.is_ipv6(info['Address']):
rule_str = "{} {} ".format(self.cmd_str, info["Strategy"])
if operation == "remove":
rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"])
if "Address" in info and info['Address'] != "all":
rule_str += "from {} ".format(info['Address'])
if len(info.get("Protocol", "")) != 0 and "/" not in info['Protocol']:
rule_str += "proto {} ".format(info['Protocol'])
if len(info.get("Port", "")) != 0:
rule_str += "to any port {} ".format(info['Port'])
stdout, stderr = public.ExecShell(rule_str)
if stderr:
if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout:
return self._result(True, "设置规则成功")
if "setlocale" in stderr:
return self._result(True, "设置规则成功")
return self._result(False, "规则设置失败:{}".format(stderr))
return self._result(True, "设置规则成功")
except Exception as e:
if "setlocale" in str(e):
return self._result(True, "设置规则成功")
return self._result(False, "规则设置失败:{}".format(e))
# 2024/3/24 下午 11:29 设置output rich_rules
def output_rich_rules(self, info, operation):
'''
@name 设置output rich_rules
@param info: 规则
@param operation: 操作
@return None
'''
try:
if info["Strategy"] == "accept":
info["Strategy"] = "allow"
elif info["Strategy"] == "drop":
info["Strategy"] = "deny"
else:
return self._result(False, "未知的策略: {}".format(info["Strategy"]))
rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"])
if "Address" in info and public.is_ipv6(info['Address']):
rule_str = "{} {} ".format(self.cmd_str, info["Strategy"])
if operation == "remove":
rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"])
if len(info.get("Address", "")) != 0:
rule_str += "out from {} ".format(info['Address'])
if len(info.get("Protocol", "")) != 0:
rule_str += "proto {} ".format(info['Protocol'])
if len(info.get("Port", "")) != 0:
rule_str += "to any port {} ".format(info['Port'])
stdout, stderr = public.ExecShell(rule_str)
if stderr:
if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout:
return self._result(True, "设置output规则成功")
if "setlocale" in stderr:
return self._result(True, "设置规则成功")
return self._result(False, "outpu规则设置失败:{}".format(stderr))
return self._result(True, "设置output规则成功")
except Exception as e:
if "setlocale" in str(e):
return self._result(True, "设置output规则成功")
return self._result(False, "outpu规则设置失败:{}".format(e))
# 2024/3/19 下午 5:01 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则
def _load_info(self, line, fire_type):
'''
@name 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则
@author wzz <2024/3/19 上午 10:38>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
fields = line.split()
item_info = {}
if "LIMIT" in line or "ALLOW FWD" in line:
return item_info
if len(fields) < 4:
return item_info
if fields[0] == "Anywhere" and fire_type != "port":
item_info["Strategy"] = "drop"
if fields[1] != "(v6)":
if fields[1] == "ALLOW":
item_info["Strategy"] = "accept"
if fields[2] == "IN":
item_info["Chain"] = "INPUT"
elif fields[2] == "OUT":
item_info["Chain"] = "OUTPUT"
item_info["Address"] = fields[3]
item_info["Family"] = "ipv4"
else:
if fields[2] == "ALLOW":
item_info["Strategy"] = "accept"
if fields[3] == "IN":
item_info["Chain"] = "INPUT"
elif fields[3] == "OUT":
item_info["Chain"] = "OUTPUT"
item_info["Address"] = fields[4]
item_info["Family"] = "ipv6"
return item_info
if "/" in fields[0]:
item_info["Port"] = fields[0].split("/")[0]
item_info["Protocol"] = fields[0].split("/")[1]
else:
item_info["Port"] = fields[0]
item_info["Protocol"] = "tcp/udp"
if "v6" in fields[1]:
item_info["Family"] = "ipv6"
if fields[2] == "ALLOW":
item_info["Strategy"] = "accept"
else:
item_info["Strategy"] = "drop"
if fields[3] == "IN":
item_info["Chain"] = "INPUT"
elif fields[3] == "OUT":
item_info["Chain"] = "OUTPUT"
item_info["Address"] = fields[4]
else:
item_info["Family"] = "ipv4" if ":" not in fields[3] else "ipv6"
if fields[1] == "ALLOW":
item_info["Strategy"] = "accept"
else:
item_info["Strategy"] = "drop"
if fields[2] == "IN":
item_info["Chain"] = "INPUT"
elif fields[2] == "OUT":
item_info["Chain"] = "OUTPUT"
item_info["Address"] = fields[3]
return item_info
# 2024/3/25 下午 2:29 设置端口转发
def port_forward(self, info, operation):
'''
@name 设置端口转发
@param port: 端口号
@param ip: ip地址
@param operation: 操作
@return None
'''
from firewallModel.app.iptables import Iptables
self.firewall = Iptables()
return self.firewall.port_forward(info, operation)
# 2024/3/25 下午 2:34 获取所有端口转发列表
def list_port_forward(self):
'''
@name 获取所有端口转发列表
@return None
'''
from firewallModel.app.iptables import Iptables
self.firewall = Iptables()
return self.firewall.get_nat_prerouting_rules()
if __name__ == '__main__':
args = sys.argv
firewall = Ufw()
ufw_status = firewall.status()
if len(args) < 2:
print("Welcome to the UFW (Uncomplicated Firewall) command-line interface!")
print("Firewall status is :", ufw_status)
print("Firewall version: ", firewall.version())
if ufw_status == "not running":
print("ufw未启动,请启动ufw后再执行命令!")
print("启动命令: start")
print()
sys.exit(1)
print()
print("Available options:")
print("1. Check Firewall Status: status")
print("2. Check Firewall Version: version")
print("3. Start Firewall: start")
print("4. Stop Firewall: stop")
print("5. Restart Firewall: restart")
print("6. Reload Firewall: reload")
print("7. List All Ports: list_port")
print("8. List All IP Addresses: list_address")
print("9. Add Port: add_port <port> <protocol>")
print("10. Remove Port: remove_port <port> <protocol>")
print("11. Add Port Rule: add_port_rule <address> <port> <protocol> <strategy> <operation>")
print("12. Remove Port Rule: remove_port_rule <address> <port> <protocol> <strategy> <operation>")
print("13. Add IP Rule: add_ip_rule <address> <strategy> <operation>")
print("14. Remove IP Rule: remove_ip_rule <address> <strategy> <operation>")
print()
sys.exit(1)
if args[1] == "status":
print(firewall.status())
elif args[1] == "version":
print(firewall.version())
elif args[1] == "start":
error = firewall.start()
if error:
print(f"Error: {error}")
else:
print("Firewall started successfully.")
elif args[1] == "stop":
error = firewall.stop()
if error:
print(f"Error: {error}")
else:
print("Firewall stopped successfully.")
elif args[1] == "restart":
error = firewall.restart()
if error:
print(f"Error: {error}")
else:
print("Firewall restarted successfully.")
elif args[1] == "reload":
error = firewall.reload()
if error:
print(f"Error: {error}")
else:
print("Firewall reloaded successfully.")
elif args[1] == "list_input_port":
ports = firewall.list_input_port()
for p in ports:
print(p)
elif args[1] == "list_output_port":
ports = firewall.list_output_port()
for p in ports:
print(p)
elif args[1] == "list_input_address":
addresses = firewall.list_input_address()
for a in addresses:
print(a)
elif args[1] == "list_output_address":
addresses = firewall.list_output_address()
for a in addresses:
print(a)
elif args[1] == "add_port":
port = args[2]
protocol = args[3]
error = firewall.input_port(f"{port}/{protocol}", "allow")
if error:
print(f"Error: {error}")
else:
print(f"Port {port}/{protocol} added successfully.")
elif args[1] == "remove_port":
port = args[2]
protocol = args[3]
error = firewall.input_port(f"{port}/{protocol}", "remove")
if error:
print(f"Error: {error}")
else:
print(f"Port {port}/{protocol} removed successfully.")
elif args[1] == "add_port_rule":
address = args[2]
port = args[3]
protocol = args[4]
strategy = args[5]
operation = args[6]
error = firewall.rich_rules(
{"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation)
if error:
print(f"Error: {error}")
else:
print("Rich rule added successfully.")
elif args[1] == "remove_port_rule":
address = args[2]
port = args[3]
protocol = args[4]
strategy = args[5]
operation = args[6]
error = firewall.rich_rules(
{"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation)
if error:
print(f"Error: {error}")
else:
print("Rich rule removed successfully.")
elif args[1] == "add_ip_rule":
address = args[2]
strategy = args[3]
operation = args[4]
error = firewall.rich_rules(
{"Address": address, "Strategy": strategy}, operation)
if error:
print(f"Error: {error}")
else:
print("Rich rule added successfully.")
elif args[1] == "remove_ip_rule":
address = args[2]
strategy = args[3]
operation = args[4]
error = firewall.rich_rules(
{"Address": address, "Strategy": strategy}, operation)
if error:
print(f"Error: {error}")
else:
print("Rich rule removed successfully.")
elif args[1] == "output_port":
port = args[2]
operation = args[3]
error = firewall.output_port(port, operation)
if error:
print(f"Error: {error}")
else:
print(f"Output port {port} {operation} successfully.")
elif args[1] == "output_rich_rules":
address = args[2]
port = args[3]
protocol = args[4]
strategy = args[5]
operation = args[6]
error = firewall.output_rich_rules(
{"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation)
if error:
print(f"Error: {error}")
else:
print("Output rich rule added successfully.")
else:
print("Invalid args")
sys.exit(1)
File diff suppressed because it is too large Load Diff
+220
View File
@@ -0,0 +1,220 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@bt.cn>
# -------------------------------------------------------------------
# ------------------------------
# 系统防火墙模型 - 基类
# ------------------------------
import os
import re
from typing import Dict, Union, Any
from xml.etree.ElementTree import ElementTree
import public
class Base(object):
def __init__(self):
self.config_path = "{}/class/firewallModel/config".format(public.get_panel_path())
self._isUfw = False
self._isFirewalld = False
self._isIptables = False
if os.path.exists('/usr/sbin/ufw'):
self._isUfw = True
from firewallModel.app.ufw import Ufw
self.firewall = Ufw()
elif os.path.exists('/usr/sbin/firewalld'):
self._isFirewalld = True
from firewallModel.app.firewalld import Firewalld
self.firewall = Firewalld()
elif not self._isUfw and not self._isFirewalld:
self._isIptables = True
from firewallModel.app.iptables import Iptables
self.firewall = Iptables()
_months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07',
'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
# 2024/3/14 上午 11:27 获取防火墙运行状态
def get_firewall_status(self) -> bool:
'''
@name 获取防火墙运行状态
@author wzz <2024/3/14 上午 11:27>
@param
@return bool True/False
'''
if self._isUfw:
res = public.ExecShell("systemctl is-active ufw")[0]
if res == "active": return True
res = public.ExecShell("systemctl list-units | grep ufw")[0]
if res.find('active running') != -1: return True
res = public.ExecShell('/lib/ufw/ufw-init status')[0]
if res.find("Firewall is not running") != -1: return False
res = public.ExecShell('ufw status verbose')[0]
if res.find('inactive') != -1: return False
return True
if self._isFirewalld:
res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0]
if res: return True
res = public.ExecShell("systemctl is-active firewalld")[0]
if res == "active": return True
res = public.ExecShell("systemctl list-units | grep firewalld")[0]
if res.find('active running') != -1: return True
return False
else:
res = public.ExecShell("/etc/init.d/iptables status")[0]
if res.find('not running') != -1: return False
res = public.ExecShell("systemctl is-active iptables")[0]
if res == "active": return True
return True
# 2024/3/14 上午 11:30 设置禁ping
def set_ping(self, get) -> dict:
'''
@name 设置禁ping
@author wzz <2024/3/14 上午 11:31>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
get.status = get.get("status", "1")
get.status = str(get.status) if str(get.status) in ['0', '1'] else '1'
filename = '/etc/sysctl.conf'
conf = public.readFile(filename)
if conf.find('net.ipv4.icmp_echo') != -1:
rep = r"net\.ipv4\.icmp_echo.*"
conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf)
else:
conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n"
if public.writeFile(filename, conf):
public.ExecShell('sysctl -p')
return public.returnMsg(True, 'SUCCESS')
else:
return public.returnMsg(
False,
'<a style="color:red;">错误:设置失败,sysctl.conf不可写!</a><br>'
'1、如果安装了[宝塔系统加固],请先关闭<br>'
'2、如果安装了云锁,请关闭[系统加固]功能<br>'
'3、如果安装了安全狗,请关闭[系统防护]功能<br>'
'4、如果使用了其它安全软件,请先卸载<br>'
)
# 2024/3/14 上午 11:37 获取网站日志目录的大小
def get_www_logs_size(self, get) -> Dict[str, Union[str, Any]]:
'''
@name 获取网站日志目录的大小
@author wzz <2024/3/14 上午 11:37>
@param
@return dict{"status":True/False,"msg":"提示信息"}
'''
path_size = public.get_size_total("/www/wwwlogs")
if not path_size:
return {"log_path": "/www/wwwlogs", "size": "0B"}
return {"log_path": "/www/wwwlogs", "size": public.to_size(path_size["/www/wwwlogs"])}
# 2024/3/25 上午 10:50 获取防火墙类型,firewall或ufw
def _get_firewall_type(self) -> str:
'''
@name 获取防火墙类型,firewall或ufw
@return str firewall/ufw
'''
import os
if os.path.exists('/usr/sbin/ufw'):
return 'ufw'
if os.path.exists('/usr/sbin/firewalld'):
return 'firewall'
return 'iptables'
# 2024/3/26 下午 5:01 获取指定域名的A记录
def get_a_ip(self, domain: str) -> str:
'''
@name 获取指定域名的A记录
@param domain: 域名
@return str
'''
try:
import socket
return socket.gethostbyname(domain)
except Exception as e:
return ""
# 2024/3/26 下午 5:40 检查是否已添加计划任务,如果没有则添加
def check_resolve_crontab(self):
'''
@name 检查是否已添加计划任务
@author wzz <2024/3/26 下午 5:41>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
python_path = "{}/pyenv/bin/python".format(public.get_panel_path())
if not public.M('crontab').where('name=?', ('[勿删]系统防火墙域名解析检测任务',)).count():
cmd = '{} {}'.format(python_path, '/www/server/panel/script/firewall_domain.py')
args = {"name": "[勿删]系统防火墙域名解析检测任务", "type": 'minute-n', "where1": '5', "hour": '',
"minute": '', "sName": "",
"sType": 'toShell', "notice": '', "notice_channel": '', "save": '', "save_local": '1',
"backupTo": '', "sBody": cmd,
"urladdress": ''}
import crontab
res = crontab.crontab().AddCrontab(args)
if res and "id" in res.keys():
return True
return False
return True
# 2024/3/26 下午 11:37 当没有域名解析时,删除域名解析的计划任务
def remove_resolve_crontab(self):
'''
@name 当没有域名解析时,删除域名解析的计划任务
@author wzz <2024/3/26 下午 11:37>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
if not public.M('firewall_domain').count():
pdata = public.M('crontab').where('name=?', '[勿删]系统防火墙域名解析检测任务').select()
if pdata:
import crontab
for i in pdata:
args = {"id": i['id']}
crontab.crontab().DelCrontab(args)
# 2024/3/26 下午 6:22 端口扫描
def CheckPort(self, port, protocol):
'''
@name 端口扫描
@author wzz <2024/3/26 下午 6:22>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
import socket
localIP = '127.0.0.1'
temp = {}
temp['port'] = port
temp['local'] = True
try:
if 'tcp' in protocol.lower():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.01)
s.connect((localIP, port))
s.close()
if 'udp' in protocol.lower():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0.01)
s.sendto(b'', (localIP, port))
s.close()
except:
temp['local'] = False
result = 0
if temp['local']: result += 2
return result
+10 -10
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x5
# | aaPanel x5
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 1249648969@qq.com
# +-------------------------------------------------------------------
@@ -138,7 +138,7 @@ class firewalls:
def AddDropAddress(self,get):
import time
import re
rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$"
rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$"
if not re.search(rep,get.port): return public.return_msg_gettext(False,'IP address youve entered is illegal!');
address = get.port
if public.M('firewall').where("port=?",(address,)).count() > 0: return public.return_msg_gettext(False,'The IP exists in block list, no need to repeat processing!')
@@ -189,7 +189,7 @@ class firewalls:
def AddAcceptPort(self,get):
flag=False
import re
rep = "^\d{1,5}(:\d{1,5})?$"
rep = r"^\d{1,5}(:\d{1,5})?$"
if not re.search(rep,get.port): return public.return_msg_gettext(False,'Port range is incorrect!');
import time
port = get.port
@@ -297,7 +297,7 @@ class firewalls:
filename = '/etc/sysctl.conf'
conf = public.readFile(filename)
if conf.find('net.ipv4.icmp_echo') != -1:
rep = u"net\.ipv4\.icmp_echo.*"
rep = r"net\.ipv4\.icmp_echo.*"
conf = re.sub(rep,'net.ipv4.icmp_echo_ignore_all='+get.status,conf)
else:
conf += "\nnet.ipv4.icmp_echo_ignore_all="+get.status
@@ -320,7 +320,7 @@ class firewalls:
file = '/etc/ssh/sshd_config'
conf = public.readFile(file)
rep = "#*Port\s+([0-9]+)\s*\n"
rep = "#*Port\\s+([0-9]+)\\s*\n"
conf = re.sub(rep, "Port "+port+"\n", conf)
public.writeFile(file,conf)
@@ -345,7 +345,7 @@ class firewalls:
def GetSshInfo(self,get):
file = '/etc/ssh/sshd_config'
conf = public.readFile(file)
rep = "#*Port\s+([0-9]+)\s*\n"
rep = "#*Port\\s+([0-9]+)\\s*\n"
port = re.search(rep,conf).groups(0)[0]
import system
panelsys = system.system();
@@ -367,7 +367,7 @@ class firewalls:
try:
file = '/etc/sysctl.conf'
conf = public.readFile(file)
rep = "#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)"
rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)"
tmp = re.search(rep,conf).groups(0)[0]
if tmp == '1': isPing = False
except:
@@ -397,11 +397,11 @@ class firewalls:
flag = False
import re
# 判断端口是否正确
rep = "^\d{1,5}(:\d{1,5})?$"
rep = r"^\d{1,5}(:\d{1,5})?$"
if not re.search(rep, get.port): return public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535');
# 判断IP是否正确
rep2 = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$"
rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$"
if not re.search(rep2, get.address): return public.return_msg_gettext(False, 'IP address is illegal!');
import time
ports = get.port
+2 -2
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python
# coding:utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 1249648969@qq.com,
# +-------------------------------------------------------------------
+9 -10
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x3
# | aaPanel x3
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import sys,os,public,re,firewalld,time
@@ -310,15 +310,13 @@ class firewalls:
#改远程端口
def SetSshPort(self,get):
port = get.port
if not port:
return public.return_msg_gettext(False, 'Port range must be between 22 and 65535!')
try:
if int(port) < 22 or int(port) > 65535: return public.return_msg_gettext(False,'Port range must be between 22 and 65535!')
except:
return public.return_msg_gettext(False, 'Port range must be between 22 and 65535!')
ports = ['21','25','80','443','8080','888','8888','7800']
if port in ports: return public.return_msg_gettext(False,'Do NOT use common default port!')
if port in ports:
return public.return_msg_gettext(False,'Do NOT use common default port!')
# return public.return_message(-1, 0, 'Do NOT use common default port!')
file = '/etc/ssh/sshd_config'
conf = public.readFile(file)
@@ -343,6 +341,7 @@ class firewalls:
public.M('firewall').add('port,ps,addtime',(port,'SSH remote service',time.strftime('%Y-%m-%d %X',time.localtime())))
public.WriteLog("TYPE_FIREWALL", "FIREWALL_SSH_PORT",(port,))
return public.return_msg_gettext(True,'Setup successfully!')
# return public.return_message(0, 0, 'Setup successfully!')
#取SSH信息
def GetSshInfo(self,get):
+7 -7
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# + -------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# + -------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# + -------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# + -------------------------------------------------------------------
import public,db,re,os,firewalls
try:
@@ -24,13 +24,13 @@ class ftp:
import files,time
fileObj=files.files()
if get['ftp_username'].strip().find(' ') != -1: return public.returnMsg(False,'Username cannot contain spaces')
if re.search("\W+",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')}
if re.search(r"\W+",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')}
if len(get['ftp_username']) < 3: return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, cannot be less than 3 characters!')}
if not fileObj.CheckDir(get['path']): return {'status':False,'code':501,'msg':public.get_msg_gettext('System critical directory cannot be used as FTP directory!')}
if public.M('ftps').where('name=?',(get.ftp_username.strip(),)).count(): return public.return_msg_gettext(False,'User [{}] exists!',(get.ftp_username,))
username = get['ftp_username'].strip()
if re.search("[\/\\\:\*\?\"\'\<\>\|]+",username):
return public.return_msg_gettext(False,"Name cannot contain /\:*?\"<>| symbol")
if re.search("[\\/\\\\:\\*\\?\"\'\\<\\>\\|]+",username):
return public.return_msg_gettext(False,"Name cannot contain /\\:*?\"<>| symbol")
password = get['ftp_password'].strip()
if len(password) < 6: return public.return_msg_gettext(False, 'Password must be at least [{}] characters',("6",))
get.path = get['path'].replace(' ','')
@@ -122,7 +122,7 @@ class ftp:
if int(port) < 1 or int(port) > 65535: return public.return_msg_gettext(False,'Port range is incorrect!')
file = '/www/server/pure-ftpd/etc/pure-ftpd.conf'
conf = public.readFile(file)
rep = u"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)"
rep = u"\n#?\\s*Bind\\s+[0-9]+\\.[0-9]+\\.[0-9]+\\.+[0-9]+,([0-9]+)"
#preg_match(rep,conf,tmp)
conf = re.sub(rep,"\nBind 0.0.0.0," + port,conf)
public.writeFile(file,conf)
+29 -110
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# + -------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# + -------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# + -------------------------------------------------------------------
# | Author: hezhihong <272267659@@qq.cn>
# + -------------------------------------------------------------------
@@ -30,6 +30,8 @@ month_list = {
class ftplog:
__AUTH_MSG =public.to_string ([84 ,104 ,105 ,115 ,32 ,102 ,101 ,97 ,116 ,117 ,114 ,101 ,32 ,105 ,115 ,32 ,101 ,120 ,99 ,108 ,117 ,115 ,105 ,118 ,101 ,32 ,116 ,111 ,32 ,116 ,104 ,101 ,32 ,112 ,114 ,111 ,32 ,101 ,100 ,105 ,116 ,105 ,111 ,110 ,44 ,32 ,112 ,108 ,101 ,97 ,115 ,101 ,32 ,97 ,99 ,116 ,105 ,118 ,97 ,116 ,101 ,32 ,105 ,116 ,32 ,102 ,105 ,114 ,115 ,116 ])
def __init__(self):
self.__messages_file = "/var/log/"
self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/'
@@ -75,128 +77,44 @@ class ftplog:
reverse=False)
return file_name_list
def __check_auth(self):
from plugin_auth_v2 import Plugin as Plugin
plugin_obj = Plugin(False)
plugin_list = plugin_obj.get_plugin_list()
import PluginLoader
self.__IS_PRO_MEMBER = PluginLoader.get_auth_state() > 0
return int(plugin_list["pro"]) > time.time() or self.__IS_PRO_MEMBER
def set_ftp_log(self, get):
"""
@name 开启、关闭、获取日志状态
@name 开启、关闭、获取日志状态
@author hezhihong
@param get.exec_name 执行的动作
"""
if not self.__check_auth():
return public.returnMsg(False, self.__AUTH_MSG)
if not hasattr(get, 'exec_name'):
return public.returnMsg(False, 'The parameter is incorrect!')
conf_path = '/etc/rsyslog.conf'
conf = public.readFile(conf_path)
import re
search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log"
search_str_two = "ftp.none"
rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n'
result = re.search(search_str, conf)
ftp_file='/etc/rsyslog.d/pure-ftpd.conf'
write_string = '\nftp.*\t\t-/var/log/pure-ftpd.log\n'
#获取日志状态
if get.exec_name == 'getlog':
if result:
return_result = 'start'
if os.path.exists(ftp_file):
return public.returnMsg(True, 'start')
else:
return_result = 'stop'
return public.returnMsg(True, return_result)
#开启日志审计
return public.returnMsg(True, 'stop')
# 开启日志审计
elif get.exec_name == 'start':
# 兼容之前开启,会将配置文件搞坏
if conf.count('ftp.nonenftp') > 5:
conf = '''
# /etc/rsyslog.conf configuration file for rsyslog
#
# For more information install rsyslog-doc and see
# /usr/share/doc/rsyslog-doc/html/configuration/index.html
#
# Default logging rules can be found in /etc/rsyslog.d/50-default.conf
#################
#### MODULES ####
#################
module(load="imuxsock") # provides support for local system logging
#module(load="immark") # provides --MARK-- message capability
# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")
# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")
# provides kernel logging support and enable non-kernel klog messages
module(load="imklog" permitnonkernelfacility="on")
###########################
#### GLOBAL DIRECTIVES ####
###########################
#
# Use traditional timestamp format.
# To enable high precision timestamps, comment out the following line.
#
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
# Filter duplicated messages
$RepeatedMsgReduction on
#
# Set the default permissions for all log files.
#
$FileOwner syslog
$FileGroup adm
$FileCreateMode 0640
$DirCreateMode 0755
$Umask 0022
$PrivDropToUser syslog
$PrivDropToGroup syslog
#
# Where to place spool and state files
#
$WorkDirectory /var/spool/rsyslog
#
# Include all config files in /etc/rsyslog.d/
#
$IncludeConfig /etc/rsyslog.d/*.conf
'''
public.writeFile(conf_path, conf)
return self.set_ftp_log(get)
if '*.info;mail.none;authpriv.none;' not in conf:
conf += '\n*.info;mail.none;authpriv.none;cron.none /var/log/messages\n'
if result:
conf = conf.replace(search_str, rep_str)
else:
conf += rep_str
#禁止ftp日志写入/var/log/messages
d_conf = conf[conf.rfind('info;'):]
d_conf = d_conf[:d_conf.find('/')]
s_conf = d_conf.replace(',', ';')
if s_conf.find(search_str_two) == -1:
str_index = s_conf.rfind(';')
s_conf = s_conf[:str_index +
1] + search_str_two + s_conf[str_index + 1:]
conf = conf.replace(d_conf, s_conf)
public.writeFile(ftp_file, write_string)
self.add_crontab()
#关闭日志审计
# 关闭日志审计
elif get.exec_name == 'stop':
if result:
conf = re.sub(search_str, '', conf)
#取消禁止ftp日志写入/var/log/messages
if conf.find(search_str_two) != -1:
conf = conf.replace(search_str_two, '')
for i in [';;', ',,', ';,', ',;']:
if conf.find(i) != -1: conf = conf.replace(i, '')
if os.path.exists(ftp_file):
os.remove(ftp_file)
if os.path.exists(ftp_file):
return public.returnMsg(False, 'failed to close the log')
self.del_crontab()
public.writeFile(conf_path, conf)
public.ExecShell('systemctl restart rsyslog')
return public.returnMsg(True, 'successfully set')
@@ -404,6 +322,7 @@ $IncludeConfig /etc/rsyslog.d/*.conf
if search_str not in line: continue
tmp_v = line.split(search_str)
if len(tmp_v[0].strip().split())<3:continue
hostname = tmp_v[0].strip().split()[3].strip()
action_time = tmp_v[0].replace(hostname, '').strip()
action_info['time'] = self.get_format_time(action_time)
+16 -10
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
# +-------------------------------------------------------------------
@@ -440,13 +440,19 @@ exit($header."\r\n\r\n".json_encode($body));
match = re.search("(.|\n)+\r\n\r\n",req)
if not match: return req,{},0
tmp = match.group().split("\r\n")
i = 0
if tmp[i].find('Continue') != -1: i+=1
if not tmp[i]: i+=1
try:
status_code = int(tmp[i].split()[1])
except:
status_code = 0
status_code = 0
from public.regexplib import search_http_response_status_line
for i in range(len(tmp) - 1):
m = search_http_response_status_line.search(tmp[i])
if not m:
continue
status_code = int(m.group(1))
break
body = req.replace(match.group(),'')
return body,tmp,status_code
+52 -11
View File
@@ -1,15 +1,16 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import time,public,db,os,sys,json,re,shutil
os.chdir('/www/server/panel')
def control_init():
update_py312()
public.chdck_salt()
clear_other_files()
sql_pacth()
@@ -21,6 +22,7 @@ def control_init():
clean_max_log('/root/.pm2/pm2.log',1024*1024*20)
remove_tty1()
clean_hook_log()
acme_crond_reinit()
run_new()
clean_max_log('/www/server/cron',1024*1024*5,20)
clean_max_log("/www/server/panel/plugin/webhook/script",1024*1024*1)
@@ -31,7 +33,7 @@ def control_init():
set_pma_access()
# public.set_open_basedir()
clear_fastcgi_safe()
update_py37()
# update_py37()
run_script()
set_php_cli_env()
check_enable_php()
@@ -45,6 +47,7 @@ def control_init():
#hide_docker()
rep_pyenv_link()
rm_apache_cgi_test()
uninstall_pip_lxml()
def rm_apache_cgi_test():
'''
@@ -56,6 +59,32 @@ def rm_apache_cgi_test():
if os.path.exists(test_cgi_file):
os.remove(test_cgi_file)
def uninstall_pip_lxml():
'''
@name 如果lxml版本大于5.0.0卸载它,解决部分机器 import bs4 程序崩溃的问题
'''
try:
import lxml
if lxml.__version__ > '5.0.0':
public.ExecShell('/www/server/panel/pyenv/bin/pip3 uninstall lxml -y')
except:
pass
def acme_crond_reinit():
'''
@name 修复acme定时任务
@return void
'''
try:
lets_config_file = os.path.join(public.get_panel_path(),'config/letsencrypt_v2.json')
if not os.path.exists(lets_config_file): return
import acme_v2
acme_v2.acme_v2().set_crond()
except:
pass
def rep_pyenv_link():
'''
@name 修复pyenv环境软链
@@ -156,7 +185,7 @@ def clear_other_files():
shutil.copyfile(src_file,init_file)
if os.path.getsize(init_file) < 10:
public.ExecShell("chattr -i " + init_file)
public.ExecShell("\cp -arf %s %s" % (src_file,init_file))
public.ExecShell(r"\cp -arf %s %s" % (src_file,init_file))
public.ExecShell("chmod +x %s" % init_file)
except:pass
public.writeFile('/var/bt_setupPath.conf','/www')
@@ -484,7 +513,7 @@ def set_php_cli_env():
if not os.path.exists(php_fpm) and os.path.exists(php_fpm_src): os.symlink(php_fpm_src,php_fpm)
if not os.path.exists(php_pecl) and os.path.exists(php_pecl_src): os.symlink(php_pecl_src,php_pecl)
if not os.path.exists(php_pear) and os.path.exists(php_pear_src): os.symlink(php_pear_src,php_pear)
public.ExecShell("\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini
public.ExecShell(r"\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini
public.ExecShell('sed -i "/disable_functions/d" {}'.format(php_cli_ini)) # 清理禁用函数
bashrc_body += "alias php{}='php{} -c {}'\n".format(php_version,php_version,php_cli_ini) # 设置别名
else:
@@ -507,7 +536,7 @@ def check_enable_php():
for php_v in php_versions:
ngx_php_conf = public.get_setup_path() + '/nginx/conf/enable-php-{}.conf'.format(php_v)
if os.path.exists(ngx_php_conf): continue
enable_conf = '''
enable_conf = r'''
location ~ [^/]\.php(/|$)
{{
try_files $uri =404;
@@ -549,7 +578,7 @@ def run_script():
if not os.path.exists(script_info['script_file']) \
or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \
or not re.match('^\w+$',script_info['script_file']):
or not re.match(r'^\w+$',script_info['script_file']):
os.remove(script_conf_file)
if os.path.exists(exec_log_file): os.remove(exec_log_file)
continue
@@ -594,6 +623,7 @@ def files_set_mode():
["/www/backup","","root",600,True],
["/www/wwwlogs","","www",700,True],
["/www/enterprise_backup","","root",600,True],
["/www/server/panel/webserver", "", "root", 755, False],
["/www/server/cron","","root",700,True],
["/www/server/cron","/*.log","root",600,True],
["/www/server/stop","","root",755,True],
@@ -602,7 +632,7 @@ def files_set_mode():
["/www/server/panel/class","","root",600,True],
["/www/server/panel/data","","root",600,True],
["/www/server/panel/plugin","","root",600,False],
["/www/server/panel/BTPanel","","root",600,True],
["/www/server/panel/BTPanel","","root",755,True],
["/www/server/panel/vhost","","root",600,True],
["/www/server/panel/rewrite","","root",600,True],
["/www/server/panel/config","","root",600,True],
@@ -617,6 +647,7 @@ def files_set_mode():
["/www/server/panel/BT-Panel","","root",700,False],
["/www/server/panel/BT-Task","","root",700,False],
["/www/server/panel","/*.py","root",600,False],
["/www/server/panel","","root",755,False],
["/dev/shm/session.db","","root",600,False],
["/dev/shm/session_py3","","root",600,True],
["/dev/shm/session_py2","","root",600,True],
@@ -706,7 +737,7 @@ def set_pma_access():
#尝试升级到独立环境
#尝试升级到独立环境3.7
def update_py37():
pyenv='/www/server/panel/pyenv/bin/python3'
pyenv_exists='/www/server/panel/data/pyenv_exists.pl'
@@ -716,6 +747,16 @@ def update_py37():
public.writeFile(pyenv_exists,'True')
return True
#尝试升级到独立环境3.12
def update_py312():
pyenv='/www/server/panel/pyenv/bin/python3.12'
if os.path.exists(pyenv): return False
download_url = public.get_url()
only_update_pyenv312 = '/tmp/only_update_pyenv312.pl'
public.writeFile(only_update_pyenv312, 'True')
public.ExecShell("nohup curl -k {}/install/update_7.x_en.sh|bash &>/tmp/panelUpdate.pl &".format(download_url))
return True
def test_ping():
_f = '/www/server/panel/data/ping_token.pl'
if os.path.exists(_f): os.remove(_f)
@@ -855,7 +896,7 @@ def disable_putenv(fun_name):
if os.path.exists(is_set_disable): return True
php_vs = public.get_php_versions()
php_ini = "/www/server/php/{0}/etc/php.ini"
rep = "disable_functions\s*=\s*.*"
rep = r"disable_functions\s*=\s*.*"
for pv in php_vs:
php_ini_path = php_ini.format(pv)
if not os.path.exists(php_ini_path): continue
+6 -6
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lkq <lkq@bt.cn>
# | Author: lkq <lkq@aapanel.com>
# |
# | 日志分析工具
# +-------------------------------------------------------------------
@@ -21,7 +21,7 @@ class log_analysis:
def __init__(self):
if not os.path.exists(self.path + '/log/'): os.makedirs(self.path + '/log/')
if not os.path.exists(self.log_analysis_path):
log_analysis_data = '''help(){
log_analysis_data = r'''help(){
echo "Usage: ./action.sh [options] [FILE] [OUTFILE] "
echo "Options:"
echo "xxx.sh san_log [FILE] Get the log list with the keywords xss|sql|mingsense information|php code execution in the successful request [OUTFILE] 11"
@@ -231,7 +231,7 @@ echo "[*] shut down"
result['is_status'] = True
else:
result['is_status'] = False
if os.path.exists(speed+".time"):
if os.path.exists(speed+".time") and os.path.getsize(speed+".time") > 0:
time_data, start_time, status = public.ReadFile(self.path + '/log/' + log_path + ".time").split("[]")
if status == '1' or start_time==1:
result['time']=time_data
@@ -257,4 +257,4 @@ echo "[*] shut down"
type_list = ['xss', 'sql', 'san', 'php', 'ip', 'url']
if get.type not in type_list: return public.ReturnMsg(False, 'Type mismatch')
if not os.path.exists(speed + get.type + '.log'): return public.ReturnMsg(False, 'Record does not exist')
return self.get_log_count(speed + get.type + '.log', is_body=True)
return self.get_log_count(speed + get.type + '.log', is_body=True)
+452 -469
View File
@@ -1,469 +1,452 @@
#coding: utf-8
# + -------------------------------------------------------------------
# | 宝塔Linux面板
# + -------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# + -------------------------------------------------------------------
# | Author: hezhihong <272267659@@qq.cn>
# + -------------------------------------------------------------------
import public, os, time
from logsModel.base import logsBase
try:
from BTPanel import session
except:
pass
#英文转月份缩写
month_list = {
"Jan": "1",
"Feb": "2",
"Mar": "3",
"Apr": "4",
"May": "5",
"Jun": "6",
"Jul": "7",
"Aug": "8",
"Sept": "9",
"Sep": "9",
"Oct": "10",
"Nov": "11",
"Dec": "12"
}
class main(logsBase):
def __init__(self):
self.__messages_file = "/var/log/"
self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/'
if not os.path.isdir(self.__ftp_backup_path):
public.ExecShell('mkdir -p {}'.format(self.__ftp_backup_path))
self.__script_py = public.get_panel_path() + '/script/ftplogs_cut.py'
def get_file_list(self, path, is_bakcup=False):
"""
@name 取所有messages日志文件
@param path: 日志文件路径
@return: 返回日志文件列表
"""
files = os.listdir(path)
if is_bakcup:
file_name_list = [{
"file": "/var/log/pure-ftpd.log",
"time": int(time.time())
}]
else:
file_name_list = []
for i in files:
tmp_dict = {}
if not i: continue
file_path = path + i
tmp_dict['file'] = file_path
if is_bakcup:
if os.path.isfile(file_path) and i.find('pure-ftpd.log') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('_')[0] +
' 00:00:00'))
file_name_list.append(tmp_dict)
else:
if os.path.isfile(file_path) and i.find('messages') != -1:
tmp_dict['time'] = int(
public.to_date(
times=os.path.basename(file_path).split('-')[1] +
' 00:00:00'))
file_name_list.append(tmp_dict)
file_name_list = sorted(file_name_list,
key=lambda x: x['time'],
reverse=False)
return file_name_list
def set_ftp_log(self, get):
"""
@name 开启、关闭、获取日志状态
@author hezhihong
@param get.exec_name 执行的动作
"""
if not hasattr(get, 'exec_name'):
return public.returnMsg(False, 'The parameter is incorrect!')
conf_path = '/etc/rsyslog.conf'
conf = public.readFile(conf_path)
import re
search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log"
search_str_two = "ftp.none"
rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n'
result = re.search(search_str, conf)
#获取日志状态
if get.exec_name == 'getlog':
if result:
return_result = 'start'
else:
return_result = 'stop'
return public.returnMsg(True, return_result)
#开启日志审计
elif get.exec_name == 'start':
if result:
conf = conf.replace(search_str, rep_str)
else:
conf += rep_str
#禁止ftp日志写入/var/log/messages
d_conf = conf[conf.rfind('info;'):]
d_conf = d_conf[:d_conf.find('/')]
s_conf = d_conf.replace(',', ';')
if s_conf.find(search_str_two) == -1:
str_index = s_conf.rfind(';')
s_conf = s_conf[:str_index +
1] + search_str_two + s_conf[str_index + 1:]
conf = conf.replace(d_conf, s_conf)
self.add_crontab()
#关闭日志审计
elif get.exec_name == 'stop':
if result:
conf = re.sub(search_str, '', conf)
#取消禁止ftp日志写入/var/log/messages
if conf.find(search_str_two) != -1:
conf = conf.replace(search_str_two, '')
for i in [';;', ',,', ';,', ',;']:
if conf.find(i) != -1: conf = conf.replace(i, '')
self.del_crontab()
public.writeFile(conf_path, conf)
public.ExecShell('systemctl restart rsyslog')
return public.returnMsg(True, 'successfully set')
def get_format_time(self, englist_time):
"""
@name 时间英文转换
"""
chinanese_time = ''
try:
for i in month_list.keys():
if i in englist_time:
tmp_time = englist_time.replace(i, month_list[i])
tmp_time = tmp_time.split()
chinanese_time = '{}-{} {}'.format(tmp_time[0], tmp_time[1],
tmp_time[2])
break
return chinanese_time
except:
return chinanese_time
def get_login_log(self, get):
"""
@name 取登录日志
@author hezhihong
@param get.user_name ftp用户名
return
"""
search_str = 'pure-ftpd:'
search_str2 = 'pure-ftpd['
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect!')
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
data = []
sortid = 0
tmp_dict = {}
login_all = []
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
login_info = {}
if search_str not in line and search_str2 not in line:
continue
tmp_value = ' is now logged in'
info = line[:line.find(search_str)].strip()
if not info:
info = line[:line.find(search_str2)].strip()
hostname = info.split()[-1]
exec_time = info.split(hostname)[0].strip()
exec_time = self.get_format_time(exec_time)
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
#取登录成功日志
if tmp_value in line:
user = line.split(tmp_value)[0].strip().split()[-1]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
if dict_index not in tmp_dict:
tmp_dict[dict_index] = []
tmp_dict[dict_index].append(exec_time)
#取登出日志
tmp_value = '[INFO] Logout.'
tmp_value_two = 'Timeout - try typing a little faster next time'
if tmp_value in line or tmp_value_two in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
if user == '?' or user != get.user_name: continue
dict_index = '{}__{}'.format(user, ip)
try:
login_info['out_time'] = exec_time
login_info['in_time'] = tmp_dict[dict_index][0]
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Success' #0为登录失败,1为登录成功
login_info['sortid'] = sortid
login_all.append(login_info)
tmp_dict[dict_index] = []
sortid += 1
except:
pass
#取登录失败日志
tmp_value = 'Authentication failed for user'
if tmp_value in line:
user = line.split(tmp_value)[-1].replace('[', '').replace(
']', '').strip()
if user == '?' or user != get.user_name: continue
login_info['user'] = user
login_info['ip'] = ip
login_info['status'] = 'Failure' #0为登录失败,1为登录成功
login_info['in_time'] = exec_time
login_info['out_time'] = exec_time
login_info['sortid'] = sortid
login_all.append(login_info)
sortid += 1
if tmp_dict:
for item in tmp_dict.keys():
if not tmp_dict[item]: continue
info = {
"status": "login successful",
"in_time": tmp_dict[item][0],
"out_time": "connecting",
"user": item.split('__')[0],
"ip": item.split('__')[1],
"sortid": sortid
}
sortid += 1
login_all.append(info)
#搜索过滤
if login_all and 'search' in get and get.search and get.search.strip():
for info in login_all:
try:
search_str = str(get.search).strip().lower()
# public.writeFile('/tmp/aa.aa', get.search)
if info['ip'].find(search_str) != -1 or info['user'].lower(
).find(search_str) != -1 or info['status'].find(
search_str) != -1 or info['in_time'].find(
search_str) != -1:
data.append(info)
elif info['out_time'] and info['out_time'].find(
search_str) != -1:
data.append(info)
except:
pass
else:
for info2 in login_all:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def get_page(self, data, get):
"""
@name 取分页
@author hezhihong
@param data 需要分页的数据 list
@param get.p 第几页
@return 指定分页数据
"""
# 包含分页类
import page
# 实例化分页类
page = page.Page()
info = {}
info['count'] = len(data)
info['row'] = 10
info['p'] = 1
if hasattr(get, 'p'):
info['p'] = int(get['p'])
info['uri'] = {}
info['return_js'] = ''
# 获取分页数据
result = {}
result['page'] = page.GetPage(info, limit='1,2,3,4,5,8')
n = 0
result['data'] = []
for i in range(info['count']):
if n >= page.ROW: break
if i < page.SHIFT: continue
n += 1
result['data'].append(data[i])
return result
def get_action_log(self, get):
"""
@name 取操作日志
@author hezhihong
@param get.user_name ftp用户名
return {"upload":[],"download":[],"rename":[],"delete":[]}
"""
search_str = 'pure-ftpd:'
args = public.dict_obj()
args.exec_name = 'getlog'
file_name = self.__ftp_backup_path
is_backup = True
if self.set_ftp_log(get) == 'stop':
file_name = self.__messages_file
is_backup = False
file_list = self.get_file_list(file_name, is_backup)
if not hasattr(get, 'user_name'):
return public.returnMsg(False, 'The parameter is incorrect!')
data = []
tmp_data = []
sortid = 0
for file in file_list:
if not os.path.isfile(file['file']): continue
conf = public.readFile(file['file'])
lines = conf.split('\n')
for line in lines:
if not line: continue
action_info = {}
if search_str not in line: continue
tmp_v = line.split(search_str)
hostname = tmp_v[0].strip().split()[3].strip()
action_time = tmp_v[0].replace(hostname, '').strip()
action_info['time'] = self.get_format_time(action_time)
upload_value = ' uploaded '
download_value = ' downloaded '
rename_value = 'successfully renamed or moved:'
delete_value = ' Deleted '
ip = line[line.find('(') + 1:line.find(')')].split('@')[1]
action_info['ip'] = ip
action_info['type'] = ''
#取操作用户
user = ''
if upload_value in line or download_value in line or rename_value in line or delete_value in line:
user = line[line.find('(') +
1:line.find(')')].split('@')[0]
action_info['sortid'] = sortid
sortid = sortid + 1
if not user or user != get.user_name: continue
#取上传日志
if (get.type == 'all'
or get.type == 'upload') and upload_value in line:
line_list = line.split()
upload_index = line_list.index('uploaded')
# action_info['file'] = line_list[upload_index - 1].replace(
# '//', '/')
action_info['file'] = line[line.find(']') +
1:line.rfind('(')].replace(
'uploaded',
'').replace('//',
'/').strip()
action_info['type'] = 'upload'
tmp_data.append(action_info)
#取下载日志
if (get.type == 'all'
or get.type == 'download') and download_value in line:
line_list = line.split()
upload_index = line_list.index('downloaded')
action_info['file'] = line_list[upload_index - 1].replace(
'//', '/')
action_info['type'] = 'download'
tmp_data.append(action_info)
#取重命名日志
if (get.type == 'all'
or get.type == 'rename') and rename_value in line:
action_info['file'] = line.split(rename_value)[1].replace(
'->', 'Renamed to').strip().replace('//', '/')
action_info['type'] = 'rename'
tmp_data.append(action_info)
#取删除日志
if (get.type == 'all'
or get.type == 'delete') and delete_value in line:
action_info['file'] = line.split()[-1].strip().replace(
'//', '/')
action_info['type'] = 'delete'
tmp_data.append(action_info)
# f.close
#搜索过滤
if tmp_data and 'search' in get and get.search and get.search.strip():
for info in tmp_data:
search_str = str(get.search).strip().lower()
if info['ip'].find(search_str) != -1 or info['file'].lower(
).find(search_str) != -1 or info['type'].find(
search_str) != -1 or info['time'].find(
search_str) != -1 or get.user_name.lower().find(
search_str) != -1:
data.append(info)
else:
for info2 in tmp_data:
data.append(info2)
data = sorted(data, key=lambda x: x['sortid'], reverse=True)
return self.get_page(data, get)
def del_crontab(self):
"""
@name 删除项目定时清理任务
@auther hezhihong<2022-10-31>
@return
"""
cron_name = '[Do not delete] FTP audit log cutting task'
cron_path = public.GetConfigValue('setup_path') + '/cron/'
cron_list = public.M('crontab').where("name=?", (cron_name, )).select()
if cron_list:
for i in cron_list:
if not i: continue
cron_echo = public.M('crontab').where(
"id=?", (i['id'], )).getField('echo')
args = {"id": i['id']}
import crontab
crontab.crontab().DelCrontab(args)
del_cron_file = cron_path + cron_echo
public.ExecShell(
"crontab -u root -l| grep -v '{}'|crontab -u root -".
format(del_cron_file))
def add_crontab(self):
"""
@name 构造日志切割任务
"""
python_path = ''
try:
python_path = public.ExecShell('which btpython')[0].strip("\n")
except:
try:
python_path = public.ExecShell('which python')[0].strip("\n")
except:
pass
if not python_path: return False
if not public.M('crontab').where('name=?',
('[Do not delete] FTP audit log cutting task', )).count():
cmd = '{} {}'.format(python_path, self.__script_py)
args = {
"name": "[Do not delete] FTP audit log cutting task",
"type": 'day',
"where1": '',
"hour": '0',
"minute": '1',
"sName": "",
"sType": 'toShell',
"notice": '0',
"notice_channel": '',
"save": '',
"save_local": '1',
"backupTo": '',
"sBody": cmd,
"urladdress": ''
}
import crontab
res = crontab.crontab().AddCrontab(args)
if res and "id" in res.keys():
return True
return False
return True
QRASP55VO/1DQ98p1csw9A==
PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs=
5G1X0WJyak7IcriKROwRfg==
PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs=
ZuLVraRZUj2RhJbyALJsxNMuhZe1AKXjIZKX1s/bahYi6PnNCfy1Qv0MKdv1oxSXYGJY2PXP7NLNatPW4VSB/zJjWGw5HqJLpeYo/MseDrI=
PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs=
mlbcpvT0kUVOtns2yTGydDt4+07xDvuvRqS/JgkUon3SrcxUlm2Z4rG4Pa9PwOq+
PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs=
iOUsBGgOHEqzdAdQgfAp4trfzBztc5RmfOjqgmGickU=
XQTKBPclCgMYfJTNC1Qwx9JOU5PR8V/GzRhXc9amMLuHdAcID+TXUZzz7SdDuJA9
1u+XjG/2+GSQRv6EzCaWRQ==
f3AsbhS4PaN1B4cUkY9T+A==
vlNl0s/hLJ4MLdvF5a1XATBpmRe09mwg70GHdHdh7mTmKAaerhorUT+w+dAajOtb
MMZtIZZuuuWy0CLBzKekTg==
i8F7pUrlHPBNsIux2MgfYA==
hAZTEkbxoxowQJisJSulNX6xYijfsoIV2aWlEpje85k=
Fzf1WPeHT+phfW8FPaBhNw==
iwW5Y10vLm5bol1PMh2K5PcgF/7PR7VaxhpNPLVQXiQ=
iiFBxU1BEtsymTSqJJqljqN1RY1RLiUspBdLrvd0CW0=
ldCxpJy7GYU9Z3wc/nhJ/te4XvT+sJUYSl50ZnBKy3w=
CrJuPSINqYUMS/s5z4adcU1LJqJ2Sojj3C2wjmLuRsM=
Psg6WqdUeMsPYvW+jnYn0evEHl+1XoE/rnCfo+c6WdA=
1z8iprMiHgI6LYZftXGjKDYPI7FVqZWCQ09oWcM3Pag=
o1ftBjL5WX5IAh9xWVdqd9wdMBvoYRdNfl+IAvPWFp8=
QUUBWS4HdgaVs9J11lpeBKBH5NkT6WbMMMnumMF/7Aw=
tfRWYnUvHUwnwQYB+ywLpslVnsH3tMXHAPD/cYoTpT4=
AtAHlguqAHnQsUP0meqLH3VU1D/0sLjQMR/6J/fyRbM=
7MgotmZFCo6+Kg3s1/uhRtwRAi7VKRh0cRfONXMb3yI=
CvGG8CijcRlYyvz5dGomfhXk6DzjYPOBtu63/Veri24=
/PTfuW9bPdAz/6cUQf+E55cP43jnvaceBCRBneohYbA=
YHmVAP+XfXvh6Do8nF7SSw==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
Tz7Nk5H535VRrRyTrsKtQL8WpBb8+0Czjnl0ON67RWc=
gSinJxQ2Dl02IUzuK5alezKMVZMklWE7tWiUg2329L/n+lRYCQ9UCFbErLWlw54SDdzf2aW7xcFo9f6yWsFr+Gmxv7QW7ILh8TsYOZ9znNp1PFfZW6D80dgQ9cC19CuCsN/nORHFj+ZzOKgvaEAfFiLoGt0ZpN5vpGxumpW0gpcWn6x7Ldft+bCe8OO/4vq4eE9RKaqjTgoUu0FLyAKLMsUrm9spdqinKdfS+0njcmvjNfpj5WOsg4REfjqMdCjT+eYrQYJpSEhzTFpPXaGYrdCg7TnFabrpVoJ3F5uhjQdkKGWqXHHHV/nbuQtIHnCtT8dwjmznuv5sSlpRCVqsgj9kw0VHrZYCN0PnqZ18QBTTb6aQzq2vcHCtyHbfnTgZYpXapiGZtR0S/U7DzfJCjnAyqRZxt4/wKHbAGYy8PQ8OUSXjFZJkxujMHXXUoxC2tVM7rRO1UyMTyERrP/FjuBzgj8YxTjyaxV4GnCT02jD45RbtdAK6txhyTaDZXcGS
9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U=
NRG8WiBknxy8LZ7uM7Rtwjpor9jB6CRSN8s/tRh0bCGCT3O3yIYveV/kC3whqCOj
g+9Bvcv6QScOWqD1KWI83lDWKDEvFH24UumsH9uh9SSEzyoQuAQyJOm+k2nYRQYDLlXqKegz+5H8/A6yDuU4hTssV9YA2l/qH0zdlTP5IOI=
wgR07xfoapmx6eEnFHXXYsrtutdMRxYffYBJpExAFLL3LFn2eCspav2L+PBokDEhe88eat5Ab0fx9+MVZjZtHQ==
xWoGNWjKGPfI4gq8aHoTfOgPIu3QGZOJ5qEujzh3pzi1Iz2/AHJ8GvmS3bUx5254nOirctK/k6BFZA2v8ayK/EDVY6Wbr2d7jYxq8UAugp4=
uRzTWU2MnopGqqEdWeT6ZU1hlM1Dw8+qKY/ieJj+NsUqvH0CTNguPTeX+Laan8WHKtl7c/+1YA4rF+Fa3pfogwkGJNEhZb49H+rX+WZ7S1s=
1u+XjG/2+GSQRv6EzCaWRQ==
J24jXQQti6wQv5pCoF2ZrQbQF1j1bF4/QmmX1CwZUVEjmug7a9JAroEXBBTfhYssEYHy8XwASHc+rw3shghfWA==
Z8UsPk1Q7HtwjRd4g01ryw==
a1WCYQuoRJeK3mOCGHQIlhC5ovOIKOhQYI+YFVzTxDyBspL/Zz/EA3ekwqZLC22t
eeFiPlXUI0Fos82HYztSmTsAYhvcpYqGtZuapEgzpCBypVgTjJ6W0KJC4ipcw897
WuRvIBaPNRsVGEMoM4NSTioH6DPOvYqQxMaOqX/7OKFY0pBl49Sar+3IeBmADhmj
Z8UsPk1Q7HtwjRd4g01ryw==
N5xldEoitd9eWUThCvdOTB6LOXhNE9TOsjnkpc/9SMN2L8WJXh22t578ODk/HhdL
oxiSpIWwD4wuBub3KU1/cF5fchu1Vqpk+nU9bn11yzo=
PRG/YRzXVD8iVR3bzEcUnjHro+U+4TucaVNI9ygPWuVU3EqLuxmqZkTk2CyNK7O/
VmVrGQo2zRokW/ZuO9bN68uCgzKuG5LF3dEm8osmV0HPcWbbdr6DBOymTEvfp0P/ftdl4LLYfLwuPpwsYazhEA==
VmVrGQo2zRokW/ZuO9bN68sblSJDKz0OqxMt0bHs/NUCob2DYRF35HVY0+LpxHBb
iDFAoKi0IJZ7t2pJuBivrw==
l2FJPs4YkAmmok1ulDRuSA==
PRG/YRzXVD8iVR3bzEcUnt6EfhtcL4GrcBoio2oUko9nPAoPP0N4kWpuhhh+oYdV
9zgnmC3+5N9XK5/NRaR4SMVNup0N5fm76yGD/GNlNnU=
sDlCNtGYD6nPzg4cZ+6YAIVaGRKK1htDJsqNdoebWm8=
1V2v8QerKOmubvSxgB4eTCXRIGj4wwBQxqmi+7HBYgg=
PRG/YRzXVD8iVR3bzEcUntHGmZ5qTlWv7ywh0UaUxJzXifYQX4pUcW7z5PPhLeh5
sDlCNtGYD6nPzg4cZ+6YAOK/4Cv++bqYgS9TyEF32cuA5AzQdBq8lyKWQabFTagT
1D18KWr2hdVsBcdZx1OaPWkr8ytK7qVWaaza8e1vU6k=
VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupO+yaDkxvgzGWeJhaeVem9Wq+8oeyKDNsYcPKXT94yeZ0UymKFveKN91eF38ApXJp2
VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT
VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a
VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlojzSftx5vWNNRCgaO4RyF8=
VmVrGQo2zRokW/ZuO9bN6xD6PoCXoaoICnYYUV03lnrKjmQB9MEOOIg0j4Z9zWge
VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA==
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupOJvco2FZSOBWgiEqCcFM7Q/vW4PqCZLGYaAAy6qf2tko=
VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT
VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a
VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlpd+8N/8ymIPDrStsyiyEo4=
VmVrGQo2zRokW/ZuO9bN6xD6PoCXoaoICnYYUV03lnrKjmQB9MEOOIg0j4Z9zWge
VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA==
ojv9MyHa70Aio7SDrWcjwSYYqgfeHLgPvpjtVJfUmX0s6OSNU+WrIf9mNVPQ5TfqPavTidvffxVkcshQNOCsRA==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklNjZdFug/ngOpMcvCKxEBrAN68NaDKAtp2GLglVerAbQ==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkllTuznFui81R4CASaEKJI9
5wsVwKLrIjIWqeTNpSVp9YtfZPXXv5krNmxeXAjq+NQ=
1u+XjG/2+GSQRv6EzCaWRQ==
JKgfL+COM6qpIwe0Wm0zY0qrBwJPhJivgZdbq2ek/MNyEskG/o3RxQuD1murI70P
Z8UsPk1Q7HtwjRd4g01ryw==
9JiWXiRNWDTg+XstQ0v3tAQjzhKVhg9yH4FpL7cmNHMby1dZvf+KLKXDFxiL/GyHwt+mo7J9Eh26MMHXk7dZ7A==
vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg=
ho/Q+jrWDtBeg9J9ZTnKizg5CYSqIH+8noUWvD9u7YuJKKfD4Zw1m+s2/8nfOg5c
Z8UsPk1Q7HtwjRd4g01ryw==
fLETQ22HuDIh4ibW1C50WajryFpGmdAvdbylaFXR0PYbVChIKwUBviTdPnbJl3rp
uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVtxbXBpGlFIU9ZBe0L9nDB3cJqXtkVNxiOuaaYNBuGag==
c0jePRxtTVZYop75Q5JCFonnV+X8W6zX0oXQ+sQuZtHIAMgLt2jbmRWY4GrPgmiW
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY=
Mmz9tB5RaVIWQerkiDs2BePvXl4dNWSjl+vJFVGrvpac5kzICL/kBoClsURQxOPYTWXamhuvoo8Pwhw/Asx8vw==
R14ZYm6mELJXNFBxHSBttdYfQPT7lYz3sNsgm+2cAVEVZqqsWoLADHy1tzgaDLwVuLUQU00i5jhUkarevZBgbw==
jpkMvZxe0AU7LK72hOoSV9O2+nkRjDR2qPsiciba+P0=
SSb4svay1O2En913Q8HWTI5XiKI6KpCz9PP4OKkiVgIZ9PAiv190X/Q6cvCmIvEn
O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8
VmVrGQo2zRokW/ZuO9bN62aET6XweFEf8bCs0QU19kFV77Lm2Kxx3MCAPMJIlJSp
sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q=
VmVrGQo2zRokW/ZuO9bN62aET6XweFEf8bCs0QU19kF1ezkxxQftj/4sG8qLYubO
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVhrSS9BSOA1ZLru6nt/7dm75/vFI6+H/XoIqtvVBWgGA==
13vYlKuO6A3n1KjBvCLS2cZOnSAYizlRW6NVt55Nvpg=
Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH1KB0feE4DfP+R5GNLxg7AM
xWoGNWjKGPfI4gq8aHoTfBMUl5q05yLZWa9BqWZ+hHXNzfrvrNJv7lUhLv58UG50furKaKDp9/7/vwDMn7xgPA==
32pdC9DD05OE2l0oXazDFBLSw6QGkeD2EftJ95N/ZFU=
9Ky7E5Jxg7bFh45xr+PtLAQzRENCwucwjn+oXpXkeQE=
Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH3FuQzm6WS6LLN0bq9R/qzA
O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8
VmVrGQo2zRokW/ZuO9bN6zRl0iz3kGEFiV+wGJc1Rx+/HiOt/vX0mu63k3Gx19QQ
VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmq0nitHFfMxYtPtCzV22AE
VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUikKCfZfG5oV8DWImIHip5MshUySi7lDaZb63pdnAbPrhvWTvaNXMG/2J1IdORcGPQ=
32pdC9DD05OE2l0oXazDFFXAy1MI/uPhakkzCdBdnMA=
4+wKB+Tel1iow8au3cOIJaTLjj3pysITCcJJ7ec43Sg8lqI/J7o8CtRAAtYI0AOu3JqoT8kI2t89ug8sZ1eCiQ==
96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOHsyhzYJSgj0XYfDyZD/qpmTBlCMqtpJ0emRNzsjaERyA==
1u+XjG/2+GSQRv6EzCaWRQ==
1u+XjG/2+GSQRv6EzCaWRQ==
f+rqXRH5RpetwpbV8cB3LNb4H/TF90wkaKs48d1M7L+avSymddn7mKYz0NItjsSS
Z8UsPk1Q7HtwjRd4g01ryw==
6Jpks4lpI/jb7zQY9ZwZK1zkFQHFLO1SftfqDQPgq1EBsFoAqyniZa+4+xJKzZfT
Z8UsPk1Q7HtwjRd4g01ryw==
IOQ7R1QbtHtN/omAE4BWfAjZgPJASxuTDruVD3whaNc=
b4OJVZe8QyIpjuTpKXDL9A==
wlLHv6kT3Q/RmtMBN4nDAQ8YSs8Zt0gDBRyWHhM8ed+GGdWzPmL+jZ6S+F5YV+22
VmVrGQo2zRokW/ZuO9bN6xjRZ/jgghl2/5XRKMIuEuFfgv1283QcodM/1ZOCBV+E
VmVrGQo2zRokW/ZuO9bN6x+Y6lBOlB/5zr7yqielcHbAt1j/vhhHLiR0XLFVJ4MYIB7qEVFJCIX6M64zazFiQeEMWR6HA8xoXyo6nAZJhlk=
VmVrGQo2zRokW/ZuO9bN6zKW75ke8QGqFd99GlEdhOmQAXdMhXbfkO0SVCSKXZhAgrRq5E4FlbZVLyjCi8XIzw==
VmVrGQo2zRokW/ZuO9bN6/VA0tcNP0B0yWPLZItZlkbpytNLRqAe/ddM0lgyhD/+AVdhq9Y31ZAG+aChEtUpGjK+qk8XXC1AHcYIovJNZ+PRowAmFi0HJrFUfIQN6E7Q
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpXsF9xMLoel3FaVnpw+fNuSwrGC7RSUXTIrW2j2yH61A=
VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY=
L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs
1u+XjG/2+GSQRv6EzCaWRQ==
69Uik37T733MFTHEnugne9sIK0q/BrAEinbmIsH25IyzJWj8GWaHG6Tu8rx0i8YI
Z8UsPk1Q7HtwjRd4g01ryw==
a1WCYQuoRJeK3mOCGHQIluXzbJqLPwB2hMZiB6idlsw=
vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg=
ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj
1xJ0clp+39cOWW64WGs9Tw==
Z8UsPk1Q7HtwjRd4g01ryw==
1u+XjG/2+GSQRv6EzCaWRQ==
avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL
avTFp0LCGrKbHzgXWCq/pSoIEj7PXeiBI2BkNQsojArLvnQoeeQAiRG4S9Ely2YX
c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY=
TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO
W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe
ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/
IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus=
MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1
PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy
j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8=
yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg==
NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew=
jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I=
kTToPYSGc065HLDqbjV0HFp+qk2oCg3rcMPzGznvj4s=
UsNFlHPXrsQOqjn4RzHaA2d+Dsggsz8fegt1C0el50M=
m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s=
1u+XjG/2+GSQRv6EzCaWRQ==
1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw==
k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw==
rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x
wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE=
VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY
VmVrGQo2zRokW/ZuO9bN65W0SAM+Lz+ixIA1yfBIlyIfbjLiw8kUEWgRJErCtT/B
VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpE6JlT9cIzf6HYvRvtfkHtUzfa7mLb2YRp06Fq3uXR7IgrEOFQK+h9IpdXLW6HxD8Q=
VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw=
VmVrGQo2zRokW/ZuO9bN68NGdAuCULKcdnQ9QIPhYF2d+4WoU/gSRVS6rllapIH/JcXSlRMgq/Lahda1FjioQA==
VmVrGQo2zRokW/ZuO9bN6/D3pD6pMXGNmGG8sGEgUx6XUzf02GbZTCaXui9nRWWO8tkj2O9evUoVTjlZbcp3bw==
VmVrGQo2zRokW/ZuO9bN656ysh/dA/L9/oKKFnGsliQ=
VmVrGQo2zRokW/ZuO9bN62VJEYtP1Byl1VdEwj5br8q1Cxt1YWn0/LGOVI2rmkKmLTqDuNfg3z4w6nFv1fa0OQ6VY4XRiSyXGr0pXrL72no=
VmVrGQo2zRokW/ZuO9bN682I1WfoM5O31Eu4y7eErmS1UbchPX+RbJH8okRw6uaf
VmVrGQo2zRokW/ZuO9bN6wGhkWn/nbtYgnRrfABp3Wx1AshDUYpYVsgVW1JY2KGZFsqdt6fYlLewntOhfc3+yQ==
VmVrGQo2zRokW/ZuO9bN61QRx6pmjScEC6Mg8X6jMiYZxM1o8NnnHqaeAW/oXr0H2CGH5XEtUznATSrKZ2lZEQ==
VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN6+WmmMTtHxp83MPGxzXMHzRsy/BAZUo3PX2S6H3yS5eG
VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS
VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWgIaiQrzxYa4sTWMTgESGdzp52DXDe+CM2icfFsZP8BQ=
VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw=
VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw==
VmVrGQo2zRokW/ZuO9bN60TADvNmgaySXS1m1WJNuPeBE1HygRWbriOlU7U2Ssu3klRVzuCKqRzIyX8uoMGm2Q==
VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ==
VmVrGQo2zRokW/ZuO9bN69V5vRMZphCh9ZgEve1d0/wJCCaWNPSysYTVRsjVkHV1kaWCUu2teK5jcDOFuBzPQg==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN62Ywq26Utqu0XTnaZ+HQKopdamM5KbwMRVVnvfRPuEM1
VmVrGQo2zRokW/ZuO9bN69qq0Yi+mSeaf9labGycI4pPobf7Rk2EV5UV2RidC6jX
VmVrGQo2zRokW/ZuO9bN63BfTcQ4tGBocvZdkD6/kO+b2BWi71GtoR82tq7ZzboaxwEKuBjLI8hR7w2Vz8MwvmfTeIGeWGGODE9Xm7plcChd9CESFtAhCbsVbUZfRATe
VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKD5/dQ70v3ovug8BVS13b+a04fDuz4GqTVSudpfL0PZ3Q==
VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo=
VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw=
VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw==
VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg=
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6/zCWmOuloAo6ldSTiaAbum79T3ZyCN2uTGYeWaRk8ZDw==
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6996q3aNX+Q/j+AZP8Qvo1SsqECC6pGPNuHt7GBg6OuU7Z1kO5CyxL0yZW1h1ERJGY=
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69ya1Zc1M+KUYAJsz6AJ8F9YFPnDyZrQbgW0mxsmgLccg==
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69tjxVx99Q6mXAvsEky2j6E
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6+u5zuOiB74Y40ABQeHY7iFbajQdIlVw8YH0S9x9fm/0gGxi5N3i0IL4ZOQgrP8+OX40wVL+NzPVrUtKnd1s0s9
VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH693/pJPvxX08JK6/fU1NpPhvWQO/SYGDQsMBTlZKO7NUA==
VmVrGQo2zRokW/ZuO9bN65UApnVIJ5/vPXwprQ1J61CB61rainG+jx/I7BmKhaXNbQAK6argkLKn8aYOSqMyZg==
VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ==
VmVrGQo2zRokW/ZuO9bN64RgViN5TsYlk5QzRuGR7Q/OP4DOltXpxjAehZXCVn5t
VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E=
VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0=
VmVrGQo2zRokW/ZuO9bN69wvhLovBGRbT42dQt1px1AK/igYwlUjVrS2AJALzGdr
VmVrGQo2zRokW/ZuO9bN6xfwiyo+r5sBtLLY/yC2JQuHAqyNIUWdS8mL+/TK0tDkDYK6sWUkPNuSjWFfywjVYg==
VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS
VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWfecK1ZYcY5aRkTVMuXTf4H0ornquPXZt6sNbcV9pvdE=
VmVrGQo2zRokW/ZuO9bN69fMKIOt3Fz+VdjDCrbU0LCl+bFI5Faw9xdIi1QHkSiy
VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw=
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLwPXg3jnnr3ZgO5MqTOchYl
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLymwotzxdwFuDwNOf7s1SnK
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLxqHw0Wm+3Oz89atXNvpO+3MXp3Qtmegk6NHmWrFwnYkOvDHfuz5NaWnAG6esuIjESLWMw8mumKWR+OGLFQatxn
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLyUNjqRPrStY0n7cNwmxO8c2/xYLBixqHfcg+3JdMiYfw==
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLx3KKJ5e9ptEgMTxJyuDzDUvQprgIXqnEOJyY8Wjzx/7g==
VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLzg8EOHacHSOV2zn2Ehx3MZFJJGKychYjDMMiIIfaMNUg==
VmVrGQo2zRokW/ZuO9bN6zSaI2hF+nruhyzPFkleUq8kEqJLrAmChIOBKZkxcqwkGxR20IThZlGA1Dw2fFbmvg==
VmVrGQo2zRokW/ZuO9bN61yX1Dmgg5/3/nXVEduWvGRjAxUjUGE7S9oN2liQJclq
1u+XjG/2+GSQRv6EzCaWRQ==
M8WJLh8/G3HKYOcF0o/j+ZydjE/y4s9kCBsZH9Ydiys=
wlLHv6kT3Q/RmtMBN4nDAR8Bo0ERF5FTL0peq/6tsQxyyboaug1HqBM4U72iapnH
VmVrGQo2zRokW/ZuO9bN68F9oo0dUBAgf3zfH+UjZ2vBnQt9FOIIe/Mpc+VcY0hPyb7Y4hQhLdZIwPaG+7+qSw==
VmVrGQo2zRokW/ZuO9bN6wO+YLo8joUjMSrMMYsU2gc=
VmVrGQo2zRokW/ZuO9bN6wxqWDjyux4j5kWMbM/YT04A7rFtRY3ZvKwNmal8z607JZ0N1ek63JQAbp1/rMmDdg==
VmVrGQo2zRokW/ZuO9bN66+oTs/ylSo2IwYwivOcQ5QBeGO3/qhUXmuWRmyY22oicnIskjhngFIDfDK5fq2/pg==
VmVrGQo2zRokW/ZuO9bN6wPyViMJrLgq6QE06AJQ/mAxMxhT6z5+FEPokBQMNnem
VmVrGQo2zRokW/ZuO9bN61Ayy1rssK205MlM60bu6RKTG0Yfp2Lha8/dhaOOY7W18jbXMCbohsZ+i3OCAo0UDg==
VmVrGQo2zRokW/ZuO9bN63oDITMAygWGYYDyry55YDO/Rs3iFoOYCQPRB0Vo6QHG
VmVrGQo2zRokW/ZuO9bN65wPRQBNUUIF+K895b5fOrPxf0bXsfEkNdHCrolD4R0g
VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE=
VmVrGQo2zRokW/ZuO9bN63D/Hc2z6pQlInT4BP7h+Iw=
VmVrGQo2zRokW/ZuO9bN6yUmZOZSHWWMW4cj+zuxv+kqaCT5BWcHH/D3f8p49LGM
zvzyAjCZxViDIIg5wIBcfv3Z+YCuz/oELxYEQudPAfA=
/jVx2Sfk8Nv4tQDKNVrh5YQDDOyw39n94XpeN+Fc18Kb/ocx6XA91E4DLjnGHUVOp/hG38KiBHnsJF7rC+FrGh3/Y7Yjh+e73kBNop061ts4ALDF136iqb3NjiK6eB/P
wlLHv6kT3Q/RmtMBN4nDARhCtgHyXaIoGDNPdzxanK1C9qqxbuBKdK1MCBW9e3yd
VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc=
VmVrGQo2zRokW/ZuO9bN65v9BdTfHDX5h5JoIA/XwnnexMAistxqcEkO+FfI33OD8/CZEhqElXeJdGaAFQ+8dlBsrteF3JVNER2SAEya4ro=
VmVrGQo2zRokW/ZuO9bN60wy8tZH9NZmPUZU3ymx7Rp230YC0uhO/IEmXkv95OC+kYR59ne0kzYbTAnbjiDQU/tfbdzA12veXVBzv74WMwI=
VmVrGQo2zRokW/ZuO9bN6w1QwjDxqY23oBLfy+yRgv/pNEUynCImsZYJyFdRKiY3f9n/tdlMbWaxhorqdeT5IvbcJCzD8QfNSTYnlePbg2dOcteS5jjxFSQ3VLx5bqO0
VmVrGQo2zRokW/ZuO9bN6xDmLuIA9/MQHFSh5NeXQIQ+yxAa1KwUsFahRjuiZ8Mht4CYtcVKfJe+264KUvItRwPdoTCmQExpQVyY7LXmw64=
VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbVpMoEipT3erObxJsWCoytEOGK/hbak+7Shcx1wGKtjvE9kvRNNO0FTZ9ViojU52Bs=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkdh44SEE/n2JvhNVaLLGFkAT0r5NcHasfhHQIuEbaS/w==
VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq
VmVrGQo2zRokW/ZuO9bN6y282QljYt9u/aACctfJxlfOZSOWj+ka4WVOhE7+gYPHIAVL1wqdRYXpc242KdGWJ/V8BbFe5Mk6POcVdzCcc/E=
VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbUT/1fI7DpxplEbSq3o7wy5
VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq
VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY=
VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc=
l2FJPs4YkAmmok1ulDRuSA==
wlLHv6kT3Q/RmtMBN4nDAU4MoUw37WOeYAqXUx8nKXV08D/vyeLYo268Q6TQAkcy
VmVrGQo2zRokW/ZuO9bN68rwZzz6qT+BRwAM0w/VgNam6voCMhL/LyfmhkE+4WOq
1u+XjG/2+GSQRv6EzCaWRQ==
YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM=
CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X
1u+XjG/2+GSQRv6EzCaWRQ==
oSOaxL22S+/P+2LLtATJSAaArdQ7fQZ1yguM5QAidIUD9pZGaNnuSKZ/6l+iYdvR
Z8UsPk1Q7HtwjRd4g01ryw==
hpr9H7QXrORqNT7P49jOY2MKXv3knmxSuBVQsYMFcyo=
YThduOXrtrzATLnwayQWnd8n5YD62KOD6YsiYJw+RAk=
7JQQDTi0T2idhFjao4jEpWRxFagB+y/O50vW7a0bZYhQWe9QbLzaMInaqQiRp7EGliGMBr4A0vhy+GCOuLQR2g==
7JQQDTi0T2idhFjao4jEpVM5kDnYAXzeJH6AUYrQZAHIQ4Zi/CV9839Nkf/Sc8cE
h2Amc56dmaRo3inGZxN9N4t4UB6ZIYdgw3I8UrLsiK9g6KbQ85/a9qF4mH08k4ge
rErGIG0jHuEUhPzM/Cvn7eDAiXr3299blfSBm1Bfub0=
B3iNup9pdvLGx6CAo6KC6KE/v+ibyUyH8Yud04Lz810=
q4nc/jwATOMUyfSjLibfEenY5RFzfDAlBFkea5rX8Ic=
nhEO6jGYTLTxNd7tfL8haGb6dIE98SqR0mAOJokWbaA=
2gqFvhn8R1IfPGeV2rMswbHUHFXVUzGBLmhsu02wzqE=
1u+XjG/2+GSQRv6EzCaWRQ==
C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs=
to+v3MAswOY2NqesdSm5E+gSyJU0y7Xs7zqrUefMUuCWwwmO2rJrPN7egFiRQVKU
vJtKpUSg5Ukl4nJbF6QjQJ7EE1taDQMnZThgUYKjS1A=
m64JmVQ8ZxnJzJGTbbSLFgYYjwLvqe5k1J852jWCJ20=
GDxnwLueKeYNT0/dIp4TaMZIrCOXt9Fadk7QEvmi3LY=
iZaIsa48RXfE+uf/yF/rQBFFBTbLDCVgmOpzzd9fNSoU5yqcis6eMa2AhGtSBk42
8+kEPw2kbWRhVZANSTiFn91S4x0IfqnYB21Go0MrYHk=
ed2GXKkXmwU2EQk+6MUOb5wVZ0JLOm9I7uChKGZVytg=
ojcUvW+Z2ehEJ6yMJpmY+4qFMPDKWboTeo1Y4LE86Gs=
/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk=
YSlit6erKXylrjSZnvyW8KJQ5WBrDhjuDH3szUt3rF4TtLL3u+2vhmfGxql7dElVlZ+FltHCrJLtURHR876Z+5QJwoIiDk96EuxXOkKMTdk=
DaK4moDemtv24P20mOaUzQ==
YSlit6erKXylrjSZnvyW8F3ElaLeR/Rlppv/IAE6pDs=
9zgnmC3+5N9XK5/NRaR4SOWz2y/YEwWVWSZltrwvNrSfE3PSPwQMDu0BXDhBm14C
1V2v8QerKOmubvSxgB4eTCxJBqBlZI1ptPijcUHCDz5sYoASyVMieGEeAAQXWrck
1D18KWr2hdVsBcdZx1OaPfHgcwFUa7nBQO7bfH5tm74XTDXVMzBwLoLCrGgXhe0X
fqq/Y0gPbKQeEXzqLGS0q/J+017f+uH388qCfSeGlh8=
o6QhOIN2Sc4SHELnst17uYigovIGLGw9Sk8MSoNStgLGPEqp/JNtpdjWEUUK4pQG
ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8=
1u+XjG/2+GSQRv6EzCaWRQ==
lkFGPJ6keZ1Jd10HSdN0i5ZJfFnQV/nGEGCTaGt06s58zYz056g6djTo00sC9scb
Z8UsPk1Q7HtwjRd4g01ryw==
a1WCYQuoRJeK3mOCGHQIlhrCEedAYfpQnUktuEYoFlw=
vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg=
ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj
AWid6G8ypXqQe9XxPPYED6EG/d+wAZpLmHn10ofRnJm2zuVMuvU9qhZPIJsbg007g9BmQxGLRAPjJyKkcrVzW+hTwBZcS8FDXPlLD0f/q/4=
Z8UsPk1Q7HtwjRd4g01ryw==
avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL
TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO
W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe
ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/
IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus=
MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1
PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy
j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8=
yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg==
c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b
L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY=
NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew=
z7RR81hY6MAA2voH8Z2gcSMl4BzicmRRwwcx6alhVes=
jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I=
m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s=
1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw==
k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw==
rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x
wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE=
VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY
VmVrGQo2zRokW/ZuO9bN69YWkPGheqxTevWr4koVetNO2nXtSSYd+VohbeMtpTJM
VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpFeadOrAd8WGDCLEKR35dm2edAnPxHas9j394knOwJ8NA==
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69nNx2wsmuj5rj8dMZU/ic5BLawNF/f6lthvaeVh/Ls+
VmVrGQo2zRokW/ZuO9bN69Fm+oKgY4oZ96N2lUN0xCYIR65p/6S+rx79lvSqg2I47knqkaZTvrsnLR+tEwCTnA==
VmVrGQo2zRokW/ZuO9bN6+qkEKPDxNwYFnkXN8TuQuYkotp65nYQ0fa3zEFof2zYnFi/LEybvPyjh48Z1idmyjl0NtdcsjLBkBJ13a8Xf98=
VmVrGQo2zRokW/ZuO9bN6wm11lyKsRHgTY7hsLdIWagcAA4Fqj10A0nlHnCL31hRaLimdtsSr1rF3vfwAkIE9UNe1Oq6AC93jtsLEBybVC4=
1u+XjG/2+GSQRv6EzCaWRQ==
VmVrGQo2zRokW/ZuO9bN69fMYEokQlwk/9w9fdzOny6b4sFM5OW1KAT1oeECeG7J
VmVrGQo2zRokW/ZuO9bN6x9kSHAfsYQbQ95eUk47zCSU++ESIivk4zGTm21DHKkHEqyRv+yZpHFD0LXHT4m58A==
VmVrGQo2zRokW/ZuO9bN68s9bgaf6E2UUkkykIzwZHpz3P+5wRaqeeTuq1ofqFvFKnCBTa5TbqmgBM/RmZJPNAUu69T5cyQoQbj9yc2u8n0=
VmVrGQo2zRokW/ZuO9bN65llTn0pe1t030hd6gBN+zBd3IDBl9SSuthuY26B8/Pu
VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU=
VmVrGQo2zRokW/ZuO9bN62HF7Hu1aUGvgMj3NxvOaP7lJ1t3oULkhfzDpf5obr4i
VmVrGQo2zRokW/ZuO9bN6yfJ0tQlmRWb/s+hHO4yegbY9OZJu7Pz1YpWO35g7fWo
VmVrGQo2zRokW/ZuO9bN63coKIwm0os5grqf/FNTOQs15MAmmbu+Svk4r/hr3Mwt
VmVrGQo2zRokW/ZuO9bN633IXjOdT/W54PqLLNSn1Vg=
VmVrGQo2zRokW/ZuO9bN60VxqaV005HoUzQg7og2uQzzkGvTFfQImHgCDcOTreTvBj0gdrkAAkZ1tFN5kmALjU7kVG7jii3XUUaGJzXU1Dm0brYcNqsnFgqAbRd+hLig734vVILUrNX+DvQbOCpPeCtyQ8YEDj2QHDc0gf1laMU=
VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13yvcZl75di1EfS2yW7gx+JTVNJQF0HOMQVAegRfGv1eA==
VmVrGQo2zRokW/ZuO9bN64OaX2n92Ly7KbozWq7a7t04nFfOkcL8NsC84xQi78iQ
VmVrGQo2zRokW/ZuO9bN6+a8J14HUH3L5V5KblyMysRYdesKR+fGd24F6C9j1l7wwo/p5lCfK41p1tvTNJzD/Q==
VmVrGQo2zRokW/ZuO9bN6yZNwtKaS0KLSStVEVMnEqjMcCKBADGrWUZZOFSQE2G8
VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1
VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bqZ+RlmOwSQwtD/IjfE4TEXKm1zXESIuOIjor2vta6yx+sUjFgCbNTyLF8DtvW/9/Y=
VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8
VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkEGH9RxcsHHlq/24VKap3dQ==
VmVrGQo2zRokW/ZuO9bN6xRDOkMGch8OU8UdKKuA98T78S/RtIEO+vPGquNUPYP8j7Hqi5x7cOT1rq7lCPTbFRrq+ZOiuhVZC6d2uE6b5Y4RMkjy5kdxgYDR8lyh08y6
VmVrGQo2zRokW/ZuO9bN6x+CQDkpZHTSzVJB0vr6/owqo2rDPdhhBctC47HwoQJI
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12i/zuDq/sxwawfAUxLzsmyvkFQXYvSK+lgmw3Dfruf8oUMKRDLCW0YoRSFNGJnVRA=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmJ6/ykoozm7YfC70113tbsRK8vQxRXP6m1Lk5Pq4lJ71yrM2OyBQ2a20YT6ATnjGs=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehp008Hpwo+KIS41Bq+plw28g==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpCUt6sS2ZM7/SgZvw2/D7KN2T4F8mibCUg/Vu5XXI0q8=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehp369EMxzMKwrpZ1Fsc6Rzihs0R+CDz1UtQsB7s3e2X1Y=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12q9ECvLOWcUe9cfTPHyuxucjjrMsKMpYA6OLWCOLB2Qw==
VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA==
VmVrGQo2zRokW/ZuO9bN68uqYuUF7HS6ZsAigA5dAhhdPk0G+p/b9PgaumDuRt89
VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1
VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bpaS2w5sSDIfS2hUtvDW/ZPwVwLAcDR+Kc98dntjxh559OJdKONTmZolY5AjSf4KMU=
VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8
VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkfYflZHE0nkkYN92Iz4DvtrB7pbXI8nJ1B+v8kBcTL9k=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH10h4UbnbgTDNlKt0H8bnNKHH19OWWvR1gRk3zXeKMJ+R8jqCu7/rIb/rNBOG+zFbIM=
VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH11DboSZV8b8syS3y20nGfXmJMrA3OSI5dV3PIVmDHiETg==
VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA==
VmVrGQo2zRokW/ZuO9bN66uuxBZGV0E5w7uGfD+hzU4AmPi0rGaOz7BvwaEoiXgK
VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1
VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9brMUFxnvPbybqGRmv07gv34NbyJ44u8fMbC6Sq1JkAEaNjiyTMCoEHy6fKKN/tEOOE=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxiyvsKVsK3znbe1ecdgFhbIZqxwirWCHagb7ADC5VmGM=
VmVrGQo2zRokW/ZuO9bN6yc8qBfgAsioQiqSI3s7WIFM5rHpAQSYfkhRNfXBk2JBPvIvjRzS10i3GMc0J2bxTrSvtMX11tg5EOzI+VjAy9E=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12W5piSXwXcBTxZsgj+fR5PRj+yKlsuMRhvQaQszRXFiA==
VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA==
VmVrGQo2zRokW/ZuO9bN69jndazZ1JZSETYUFSRjpTYuSd0gU4R+ef5NLXuDtL3Q
VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1
VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bqkLLlR3IpZgSDiX5ChzlR6NnjTJlDwKlsxY/AdyE1faRiMQGjhE0oX8Ie0uAI0rvI=
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxP38Ud5r+2JgPLAafH7N4i6SpRZsaYMAGNiTp9HaQsd4=
VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw
VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13ETFhTDjpNg4UjDJ/ZX/5m5v/P88Ser/GIltB+CyiPFA==
VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA==
SS3eUI6O4Am57+zsCenaFkHmh+pwM+FJ/cut0UtoxJ0=
zvzyAjCZxViDIIg5wIBcfv3Z+YCuz/oELxYEQudPAfA=
M8WJLh8/G3HKYOcF0o/j+UKQix7E5QZpJV0Tfc/XRq/AKN5kkYE+IMwFcAyVsz2M5wHJAN89+nJ1b7GSY68O6925BBXUxFwqdflokenOUZU=
wlLHv6kT3Q/RmtMBN4nDAZf8v50ttctcXvJzAQ4D3y4VerbRFUVUK+NjcpNFPsa+
VmVrGQo2zRokW/ZuO9bN60s8cEnb0+qXY0vQh1/7elp8TJKn/o/Cr8ab/L4KpzVyx+zZfHPJlblcJVM2evPA8Q==
VmVrGQo2zRokW/ZuO9bN62wFNZfsNF2gO4y5vOS6dGtOhTIohM0/9iaTTdt4VAkwr3U4JR6xB2mGWrp5KaqxgOX+iIeHDUU+B09yhkuBbeQ=
VmVrGQo2zRokW/ZuO9bN6zJHGvDJmOJJYHHdXzByxKsyeKpya/o0dns6NEjvZ9FhBqfG56Ge8FxUmH1UPM8WYg==
VmVrGQo2zRokW/ZuO9bN641hlBSX1Vyx3lcgSR68G0pIVXm8+RXV4rlYHPfD1CE2Ifl6RiQnkn3hT82X+tXXDQim3jnyNRBLGfZDXhejLG8=
VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbVpMoEipT3erObxJsWCoytE96eId0MgNxxUF/+ybzG/f+gvdjS4IHHuGSQJeLB3dqc=
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkdh44SEE/n2JvhNVaLLGFkAT0r5NcHasfhHQIuEbaS/w==
VmVrGQo2zRokW/ZuO9bN67WLIC8ccIPl/iWyXLvwqc6z89/fim1Zup64yhd9bw8m
l2FJPs4YkAmmok1ulDRuSA==
wlLHv6kT3Q/RmtMBN4nDATXcKKq3YicYjT+ilqwnePASEk6UJ4kb2FJ4sCzHQMkb
VmVrGQo2zRokW/ZuO9bN68rwZzz6qT+BRwAM0w/VgNam6voCMhL/LyfmhkE+4WOq
YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM=
CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X
1u+XjG/2+GSQRv6EzCaWRQ==
VEyEXJTNTVvV1TGW5V2yEMzjBDxvV9j1MdPjo5gCe0Q=
Z8UsPk1Q7HtwjRd4g01ryw==
dar6N0tUAb9rU8/aX1b3tI5lWSbcXQgKxiMzJeLasCmdJgT+u3YcH7fkhDzLb+1X
9BuKX7c+cBUFGH/xEQqKwDJwv6pCDrQbGrc/aEaAxEizXN1tDiWRXfYhvFOYGiCD
3AcPLYoMMn4rxWOUMA1wJdOmdYprhThTGxGyDnWq840=
Z8UsPk1Q7HtwjRd4g01ryw==
90/F6C3nHF3UrQ/Er80CwwMY0Oi3JHbGAYQNb/w2V49c1F+wL9hoGKNjAjwN6pWeeHbxvhz6JqsdWpYj5f3cjKeHXg+8Lul7hoesFngMXfE=
g9lRvj200ApJT2I/NDdmaNS2bs03/Yy1N4LL8JCPrSEGtYUIbFGqz+9Nvsx/WW8DAPlJEAOAh1mN1Q2H5srR9aN9BcyfgbXU677udPpvmJs=
AC/CEOsuoGTyXbh39EUlAxckdwNHSoPmK9UbpHaeRy5+9wXscQv0OinnRdatf4o5vPdsr8lUrlC4beVwlerQ34wo2xApt+NOGbWFjuh6p10OnBiFzXIsBBGS2CKsc8ur
HBkACpLXsHV9FQoDE2PMYwSntxSga34Rul8QLEjV96M=
wlLHv6kT3Q/RmtMBN4nDAeWjc9eUr6obACCnQsg7vueRPByWE2DayjQ0xFllv1tW
VmVrGQo2zRokW/ZuO9bN65VlXQyBOMicxNyTkfSbDVVHhvBq22DM+S7/byvVxhpE
VmVrGQo2zRokW/ZuO9bN6yititPhxMgpb/nuYcU2bQxlVrgi32/YGL7BfDcYBS6ZrhF8DKeSq4/xyWh1l402NQ==
VmVrGQo2zRokW/ZuO9bN60VWeNWzP5MtK6lHtmFWfiOlcDBQw592yq0YxYnQPyb2cY8haKBhRx3ogIkozyLzGA==
VmVrGQo2zRokW/ZuO9bN61P2OVHnBm22rHl5VcwespdSVFvlh6jWmhNQGuWaiPCn
VmVrGQo2zRokW/ZuO9bN6/C3rj8+QHPPw24V/a4Ay7A=
VmVrGQo2zRokW/ZuO9bN61MqA9WsfKOg/tExmVwPpl7QnDnpNlS+CkeEkfTa9kINLQXgJYeYvwm13Q5goIzeLg==
VmVrGQo2zRokW/ZuO9bN632LGHZY9uic2w89Yj9RNWLrcjnK57CYRX2bXewhAHx7FVtzwI0ZOgY/T/V/6qaQlw==
VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjakEuF8eNiysMFwArESQEwY
VmVrGQo2zRokW/ZuO9bN6yg8DfkEZba0oHLbAQabDs+4/JxGHZrKdgs6bMyBPLRMxwPtWLarWOkPgDMMkW2/RUdxOLVZk3+IQpAGkMdAX/s=
VmVrGQo2zRokW/ZuO9bN62dh+FzI+VGLu7ulc+ItFAbFXemabBEE/LbP+46UYm60
1u+XjG/2+GSQRv6EzCaWRQ==
JEnsSsP7ogTJg1OXQ2YdoeqaOYYcnxFN3j3ktV+Kpps=
Z8UsPk1Q7HtwjRd4g01ryw==
8xWkPvEiREBWC3F3wdDkfHfKi35MXWHSVRhGdhf5R5T9XB9HNeEomkbAWdDKXpBP
Z8UsPk1Q7HtwjRd4g01ryw==
tsXNkoeF46BtVmFZ50qzKkC9C9rJjI8DYYqtZ5sTRNk=
b4OJVZe8QyIpjuTpKXDL9A==
cjZRE5RYY9TGg8THaNbKeimi7CKPJrP0YrozyGJJ6po8VG8Og36dzm29TqPtykmq3ShDIRxTK5G7NnirQ35JIwA7rlx3Yk1mP0YkpIDY3Ec=
mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU=
vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo=
VmVrGQo2zRokW/ZuO9bN60gMmPI+qllGkwxOA9qbMmMB1m4JoBAOFYQ6YXkMo1VXquwSJ3IOKilGLS5oWPYO5OYeeloJXPkrrYqwK73A4D8=
M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ=
VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ=
W3A5Dt+8AxWSKsTYeXZoL1vLKDueV7P0Ut7HIASfZlp4D0IrBt2bwp8YHjJXXxXp
W3A5Dt+8AxWSKsTYeXZoLzB4TUiScS3D8yuLKBmm3AwWtYcxV6f46K0T0Egvi5WKkEtlHJveHdb2egjl/+1LDg==
VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkknASfUzt1VKHSynjO/3GCHs8NYZ0lBfA3YkEhN9KGBNrviqTxIwFkI8b8tfQOhuu8e1qX/wTQkIHS4gFt6ErXtomVQ+AVI6XYbacH6pVLAEQ==
3ihEq+EcGY3EkKhgSscgbxPe6xF3X8bYDWyX0qt6BtkDpzyqs5cxV+fqGAxgw4n0F0ocWo63+pTEB2B02sSu997O+t/1VUp8dx53F4oaaq8=
1S1xjYS0s+Ph1PyvECx3QMdMFDZ5B3+BEK8kyCfI1RM=
VmVrGQo2zRokW/ZuO9bN67pnuem2Cn+Z/63FRYUcFkLoOrSR0T8WP6DifYG5JOXMPR4AFoHBPiKvg8hnpKw+PCDgxml+g92wfGPN8zZ2WJk=
VmVrGQo2zRokW/ZuO9bN64Ga+02XCL+A28NNybmlSnI=
VmVrGQo2zRokW/ZuO9bN65a8NYx2zqK2mFD4wjj1HmQ=
VmVrGQo2zRokW/ZuO9bN61jw9zMkfm0nvQi9tveiWV0=
VmVrGQo2zRokW/ZuO9bN6zYzNJFTIYiz/a08bSUNc5M=
VmVrGQo2zRokW/ZuO9bN6ztX+cJqy4yqv63JnfA/mqI=
VmVrGQo2zRokW/ZuO9bN60jLKcJEMj36X6NZvvwxF/XvqrmJF3W3RArdDHrkgZZ6
VmVrGQo2zRokW/ZuO9bN6zskyREk1B9SpIIVpfE36zU=
VmVrGQo2zRokW/ZuO9bN63leNhHVk6qBYEqAElCrxOXVPHIpJ8U4k7mou4tbIR/7
VmVrGQo2zRokW/ZuO9bN68tloVdndHDn6gJZRayJG+k=
VmVrGQo2zRokW/ZuO9bN617eR9WvYqaE7y/ppCzNL1570CCRJc668VdVufs+tRvC
VmVrGQo2zRokW/ZuO9bN6x59qEj9owkguOGj8QrHQSLi1Q/1C75rARTXu4TGt8DP
VmVrGQo2zRokW/ZuO9bN60Bq4PDCei1vxH18OtSGKCg=
VmVrGQo2zRokW/ZuO9bN67biU+BUR7/d7BkQj4RgpT8E7BfCa3N4KcxGo7ivT2Hg
YH231WTDnzQG3bFilBiqIg==
p9oVsBgcyiBMjDb8CcPOqP2sLQ/zn5Dzo5lCtrgxqTQ=
lOh2GtzHjjMM8E9J40AuOcPN2eZCahfvScF49QHmlNXYE5PQWq+WYPigt3dG9DzvOjcs8WVNkHQ6P1pZps2Ehg==
UbJuac0dxLiN+5ocS3w2vTpUyzdtCQ6EQJg8V3275Z+ztU2/bGujfta5s3IG3yXD
VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM=
L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U=
guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE=
+4 -4
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <bt_ahong@qq.com>
# Author: cjxin <bt_ahong@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -198,7 +198,7 @@ class main(logsBase):
try:
if self.find_line_str(_line,search):
#根据用户名查找
if username and not re.search('-\s+({})\s+\('.format(username),_line):
if username and not re.search(r'-\s+({})\s+\('.format(username),_line):
continue
find_idx += 1
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <bt_ahong@qq.com>
# Author: cjxin <bt_ahong@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: wzjie <wzj@bt.cn>
# | Author: wzjie <wzj@aapanel.com>
# +-------------------------------------------------------------------
import os
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
+2 -2
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lx
# | 消息通道飞书通知模块
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: jose <zhw@bt.cn>
# | Author: jose <zhw@aapanel.com>
# | 消息通道电报模块
# +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息通道邮箱模块
# | 常用功能
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
+22 -22
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -38,7 +38,7 @@ class nginx:
gets = ["worker_processes","worker_connections","keepalive_timeout","gzip","gzip_min_length","gzip_comp_level","client_max_body_size","server_names_hash_bucket_size","client_header_buffer_size"]
n = 0
for i in gets:
rep = "(%s)\s+(\w+)" % i
rep = r"(%s)\s+(\w+)" % i
k = re.search(rep, ngconfcontent)
if not k:
return public.return_msg_gettext(False,"Get key {} False".format(k))
@@ -64,7 +64,7 @@ class nginx:
gets = ["client_body_buffer_size"]
n = 0
for i in gets:
rep = "(%s)\s+(\w+)" % i
rep = r"(%s)\s+(\w+)" % i
k = re.search(rep, proxycontent)
if not k:
return public.return_msg_gettext(False,"Get key {} False".format(k))
@@ -88,14 +88,14 @@ class nginx:
n+=1
return conflist
def SetNginxValue(self,get):
def SetNginxValue(self, get: public.dict_obj):
ngconfcontent = public.readFile(self.nginxconf)
proxycontent = public.readFile(self.proxyfile)
if public.get_webserver() == 'nginx':
shutil.copyfile(self.nginxconf, '/tmp/ng_file_bk.conf')
shutil.copyfile(self.proxyfile, '/tmp/proxyfile_bk.conf')
conflist = []
getdict = get.__dict__
getdict = get.get_items()
for i in getdict.keys():
if i != "__module__" and i != "__doc__" and i != "data" and i != "args" and i != "action":
getpost = {
@@ -105,13 +105,13 @@ class nginx:
conflist.append(getpost)
for c in conflist:
rep = "%s\s+[^kKmMgG\;\n]+" % c["name"]
rep = r"%s\s+[^kKmMgG\;\n]+" % c["name"]
if c["name"] == "worker_processes" or c["name"] == "gzip":
if not re.search("auto|on|off|\d+", c["value"]):
return public.return_msg_gettext(False, 'Parameter ERROR!')
if not re.search(r"auto|on|off|\d+", c["value"]):
return public.return_msg_gettext(False, 'Parameter ERROR! -1')
else:
if not re.search("\d+", c["value"]):
return public.return_msg_gettext(False, 'Parameter ERROR!')
if not re.search(r"\d+", c["value"]):
return public.return_msg_gettext(False, 'Parameter ERROR! -2')
if re.search(rep,ngconfcontent):
newconf = "%s %s" % (c["name"],c["value"])
ngconfcontent = re.sub(rep,newconf,ngconfcontent)
@@ -132,7 +132,7 @@ class nginx:
def add_nginx_access_log_format(self,args):
'''
@name 添加日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param log_format 需要设置的日志格式["$server_name","$remote_addr","-"....]
@param log_format_name
@param act 操作方式 add/edit
@@ -151,7 +151,7 @@ class nginx:
conf = public.readFile(self.nginxconf)
if not conf:
return public.return_msg_gettext(False,'Nginx configuration file does not exist!')
reg = 'http(\n|\s)+{'
reg = r'http(\n|\s)+{'
conf = re.sub(reg,'http\n\t{'+data,conf)
public.writeFile(self.nginxconf,conf)
public.serviceReload()
@@ -162,14 +162,14 @@ class nginx:
def del_nginx_access_log_format(self,args):
'''
@name 删除日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param log_format_name
'''
log_format_name = args.log_format_name
conf = public.readFile(self.nginxconf)
if not conf:
return public.return_msg_gettext(False, 'Nginx configuration file does not exist!')
reg = '\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name)
reg = r'\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name)
conf = re.sub(reg,'',conf)
self._del_format_log_of_website(log_format_name)
public.writeFile(self.nginxconf,conf)
@@ -227,7 +227,7 @@ class nginx:
format_name = [i.split('LOG_FORMAT_BEGIN_')[-1] for i in data]
format_log = {}
for i in format_name:
format_reg = "#LOG_FORMAT_BEGIN_{n}(\n|.)+log_format\s+{n}\s*(.*);".format(n=i)
format_reg = r"#LOG_FORMAT_BEGIN_{n}(\n|.)+log_format\s+{n}\s*(.*);".format(n=i)
tmp = re.search(format_reg,conf)
if not tmp:
continue
@@ -240,7 +240,7 @@ class nginx:
def set_format_log_to_website(self,args):
'''
@name 设置日志格式
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@param sites aaa.com,bbb.com
@param log_format_name
'''
@@ -248,13 +248,13 @@ class nginx:
sites = loads(args.sites)
try:
all_site = public.M('sites').field('name').select()
reg = 'access_log\s+/www.*{}\s*;'.format(args.log_format_name)
reg = r'access_log\s+/www.*{}\s*;'.format(args.log_format_name)
for site in all_site:
website_conf_file = '/www/server/panel/vhost/nginx/{}.conf'.format(site['name'])
conf = public.readFile(website_conf_file)
if not conf:
return public.return_msg_gettext(False, 'Nginx configuration file does not exist!')
format_exist_reg = '(access_log\s+/www.*\.log).*;'
format_exist_reg = r'(access_log\s+/www.*\.log).*;'
access_log = self.get_nginx_access_log(conf)
if not access_log:
continue
@@ -272,7 +272,7 @@ class nginx:
def get_nginx_access_log(self,nginx_conf):
try:
reg = 'access_log\s+(.*\.log)'
reg = r'access_log\s+(.*\.log)'
log_path = re.findall(reg, nginx_conf)
if not log_path:
return False
@@ -309,7 +309,7 @@ class nginx:
if not site_format_log_status[s]:
continue
website_conf_file = '/www/server/panel/vhost/nginx/{}.conf'.format(s)
format_exist_reg = 'access_log\s+/www.*\.log\s+{};'.format(log_format_name)
format_exist_reg = r'access_log\s+/www.*\.log\s+{};'.format(log_format_name)
conf = public.readFile(website_conf_file)
if not conf:continue
if not re.search(format_exist_reg,conf):continue
+20 -20
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zhwwen <zhw@bt.cn>
# Author: zhwwen <zhw@aapanel.com>
# -------------------------------------------------------------------
#
# ------------------------------
@@ -26,7 +26,7 @@ class ols:
conf = public.readFile(self._main_conf_path)
data = {}
for k in keys:
rep = '{}\s+(\w+)'.format(k)
rep = r'{}\s+(\w+)'.format(k)
tmp = re.search(rep,conf)
if tmp:
data[k] = tmp.groups(1)
@@ -43,7 +43,7 @@ class ols:
conf = public.readFile(self._main_conf_path)
data = json.loads(get.array)
for k in data:
rep = '{}\s+(\w+)'.format(k)
rep = r'{}\s+(\w+)'.format(k)
tmp = re.search(rep,conf)
if tmp:
conf = re.sub('{}.*'.format(k),'{} {}'.format(k,data[k]),conf)
@@ -58,24 +58,24 @@ class ols:
data = {}
sitename = public.M('sites').where("id=?", (get.id,)).getField('name')
conf = public.readFile(self._detail_conf_path.format(sitename))
rep = 'expiresByType\s+(.*)'
rep = r'expiresByType\s+(.*)'
tmp = re.search(rep,conf)
if tmp:
tmp = tmp.groups(1)[0].split(',')
for i in tmp:
if 'image' in i:
rep = 'image\/\*=A(\d+)'
rep = r'image\/\*=A(\d+)'
data['image'] = re.search(rep,conf).groups(1)[0]
print(data['image'])
if 'css' in i:
rep = 'css=A(\d+)'
rep = r'css=A(\d+)'
data['css'] = re.search(rep, conf).groups(1)[0]
if 'javascript' in i:
rep = 'javascript=A(\d+)'
rep = r'javascript=A(\d+)'
data['javascript'] = re.search(rep, conf).groups(1)[0]
if 'font' in i:
rep = 'font.*=A(\d+)'
rep = r'font.*=A(\d+)'
data['font'] = re.search(rep, conf).groups(1)[0]
return data
@@ -92,12 +92,12 @@ class ols:
print(self._detail_conf_path.format(sitename))
values = json.loads(get.values)
for k in values:
rep = '{}/[\*\w+=]=A\d+'.format(k)
rep = r'{}/[\*\w+=]=A\d+'.format(k)
old_cache = re.search(rep,conf)
if not old_cache:
continue
old_cache = old_cache.group()
new_cache = re.sub('\d+',values[k],old_cache)
new_cache = re.sub(r'\d+',values[k],old_cache)
conf = conf.replace(old_cache,new_cache)
public.writeFile(self._detail_conf_path.format(sitename),conf)
public.serviceReload()
@@ -127,13 +127,13 @@ class ols:
if tmp:
# 获取排除文件
tmp = tmp.group()
rep = '#\s*excluding.*\n.*\((.*)\)'
rep = r'#\s*excluding.*\n.*\((.*)\)'
tmp = re.search(rep,tmp)
if tmp:
data['exclude_file'] = [i+'.php' for i in tmp.groups(1)[0].split('|')]
print(data)
# 获取缓存时间
rep = 'max-age=(\d+)'
rep = r'max-age=(\d+)'
tmp = re.search(rep,conf)
if tmp:
data['maxage'] = tmp.groups(1)[0]
@@ -154,7 +154,7 @@ class ols:
conf = self.get_private_cache_conf(get.id)[0]
file = self.get_private_cache_conf(get.id)[1]
if 'BTLSCACHE_BEGIN' not in conf:
confstr = """#######################BTLSCACHE_BEGIN#######################
confstr = r"""#######################BTLSCACHE_BEGIN#######################
<IfModule LiteSpeed>
RewriteEngine on
@@ -206,10 +206,10 @@ RewriteRule (.*\.php)?$ - [E=Cache-Control:private]
for file in get.exclude_file.split('\n'):
exclude_files.append(file.split('.')[0])
exclude_file = "|".join(exclude_files)
old_exc_rep = 'RewriteCond\s+\%\{REQUEST_URI\}\s+\!/\(.*\)\\\.php\$'
new_exc = 'RewriteCond %{REQUEST_URI} !/(' + exclude_file + ')\.php$'
old_exc_rep = r'RewriteCond\s+\%\{REQUEST_URI\}\s+\!/\(.*\)\\\.php\$'
new_exc = 'RewriteCond %{REQUEST_URI} !/(' + exclude_file + r')\.php$'
bt_conf = re.sub(old_exc_rep,new_exc,bt_conf)
old_max_age_rep = 'max-age=\d+'
old_max_age_rep = r'max-age=\d+'
new_max_age = 'max-age={}'.format(int(get.max_age))
bt_conf = re.sub(old_max_age_rep,new_max_age,bt_conf)
conf = re.sub(bt_conf_rep,bt_conf,conf)
@@ -247,11 +247,11 @@ RewriteRule (.*\.php)?$ - [E=Cache-Control:private]
conf = public.readFile(path)
ap_path = '/www/server/panel/vhost/apache/{}.conf'.format(s['sitename'])
ap_conf = public.readFile(ap_path)
tmp = re.search('ServerName\s+SSL\.(.*)',ap_conf)
tmp = re.search(r'ServerName\s+SSL\.(.*)',ap_conf)
s['phpv'] = re.search(phpv_reg,conf).groups(1)[0]
s['rundir'] = re.search(rundir_reg.format(s),conf).groups(1)[0]
s['ssl_domain'] =tmp.groups(1)[0] if tmp else None
s['port'] = re.search('listen\s+(\d+);',conf).groups(1)[0]
s['port'] = re.search(r'listen\s+(\d+);',conf).groups(1)[0]
return siteinfo
def _make_args(self):
+17 -17
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: zhwen <zhwen@bt.cn>
# | Author: zhwen <zhwen@aapanel.com>
# +-------------------------------------------------------------------
import os
import time
@@ -73,7 +73,7 @@ class one_key_wp:
else:
self.get_plugin_page()
resp = self.__plugin_page_content
_ajax_nonce_rep = '"ajax_nonce\\\"\:\\\"(\w+)'
_ajax_nonce_rep = '"ajax_nonce\\\"\\:\\\"(\\w+)'
rex = re.search(_ajax_nonce_rep, resp)
if rex:
self.__ajax_nonce = rex.group(1)
@@ -130,7 +130,7 @@ class one_key_wp:
# hosts = public.readFile('/etc/hosts')
# if not hosts:
# return False
# if not re.search('127.0.0.1\s+{}'.format(self.__domain),hosts):
# if not re.search(r'127.0.0.1\s+{}'.format(self.__domain),hosts):
# public.writeFile('/etc/hosts','\n127.0.0.1 {}'.format(self.__domain),'a+')
self.__session_resp = retry(
@@ -270,7 +270,7 @@ class one_key_wp:
hosts = public.readFile('/etc/hosts')
if not hosts:
return False
if not re.search('127.0.0.1\s+{}'.format(values['site_name']), hosts):
if not re.search(r'127.0.0.1\s+{}'.format(values['site_name']), hosts):
public.writeFile('/etc/hosts', '\n127.0.0.1 {}'.format(values['site_name']), 'a+')
self.write_logs("|-Start initializing Wordpress...")
@@ -362,7 +362,7 @@ class one_key_wp:
def get_update_wp_nonce(self):
url = "http://{}/wp-admin/update-core.php".format(self.__domain)
# res = self.action_plugin_get(url)
_wp_nonce_rep = '"_wpnonce\\\"\svalue=\\\"(\w+)'
_wp_nonce_rep = '"_wpnonce\\\"\\svalue=\\\"(\\w+)'
# rex = re.search(_wp_nonce_rep,res)
# if rex:
# self.__wp_nonce = rex.group(1)
@@ -414,7 +414,7 @@ class one_key_wp:
def get_smart_http_expire_form_nonce(self, url):
public.writeFile('/tmp/2', str(self.action_plugin_get(url)))
smart_http_expire_form_nonce = '"smart_http_expire_form_nonce\\\"\svalue=\\\"(\w+)'
smart_http_expire_form_nonce = '"smart_http_expire_form_nonce\\\"\\svalue=\\\"(\\w+)'
return self.get_wp_nonce(self.action_plugin_get(url), smart_http_expire_form_nonce)
def set_nginx_helper(self, values):
@@ -557,7 +557,7 @@ class one_key_wp:
conf_file = "{}/wp-includes/version.php".format(path)
conf = public.readFile(conf_file)
try:
version = re.search('\$wp_version\s*=\s*[\'\"]{1}([\d\.]*)[\'\"]{1}', conf).groups(1)[0]
version = re.search('\\$wp_version\\s*=\\s*[\'\"]{1}([\\d\\.]*)[\'\"]{1}', conf).groups(1)[0]
except:
version = "00"
return version
@@ -569,7 +569,7 @@ class one_key_wp:
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36'
}
result = requests.get(url, headers=headers)
result = re.search('Download\s+WordPress\s+([\d\.]+)', result.text)
result = re.search(r'Download\s+WordPress\s+([\d\.]+)', result.text)
if result:
return result.group(1)
return "00"
@@ -793,13 +793,13 @@ class one_key_wp:
rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$"
values = {}
if hasattr(args, 'd_id'):
if re.search('\d+', args.d_id):
if re.search(r'\d+', args.d_id):
values["d_id"] = args.d_id
else:
return public.return_msg_gettext(False, "Please check if the [{}] format is correct For example: {}",
("d_id", "99"))
if hasattr(args, 's_id'):
if re.search('\d+', args.s_id):
if re.search(r'\d+', args.s_id):
values["s_id"] = args.s_id
else:
return public.return_msg_gettext(False, "Please check if the [{}] format is correct For example: {}",
@@ -1218,7 +1218,7 @@ class optimize_db:
class fast_cgi:
def get_fastcgi_conf(self, version):
conf = """
conf = r"""
set $skip_cache 0;
if ($request_method = POST) {
set $skip_cache 1;
@@ -1294,7 +1294,7 @@ class fast_cgi:
one_key_wp().write_logs("|-Nginx FastCgi cache configuration already exists")
print("Nginx FastCgi cache configuration already exists")
return public.return_msg_gettext(True, "Nginx FastCgi cache configuration already exists")
rep = "http\s*\n\s*{"
rep = "http\\s*\n\\s*{"
content = re.sub(rep, "http\n\t{" + conf, content)
public.writeFile(conf_path, content)
@@ -1328,7 +1328,7 @@ class fast_cgi:
print("Nginx init FastCgi cache configuration already exists")
return public.return_msg_gettext(True, "Nginx init FastCgi cache configuration already exists")
# content_init = re.sub(r"\$NGINX_BIN -c \$CONFIGFILE", + conf2, content_init)
rep2 = "\$NGINX_BIN -c \$CONFIGFILE"
rep2 = r"\$NGINX_BIN -c \$CONFIGFILE"
content_init = re.sub(rep2, conf2 + " $NGINX_BIN -c $CONFIGFILE", content_init)
public.writeFile(init_path, content_init)
@@ -1366,7 +1366,7 @@ class fast_cgi:
print("FastCgi configuration does not exist in website configuration")
one_key_wp().write_logs("|-FastCgi configuration does not exist in website configuration, skip")
return public.return_msg_gettext(False, "FastCgi configuration does not exist in website configuration")
rep = "include\s+enable-php-{}-wpfastcgi.conf;".format(version)
rep = r"include\s+enable-php-{}-wpfastcgi.conf;".format(version)
conf = re.sub(rep, "include enable-php-{}.conf;".format(version), conf)
else:
fastcgi_conf = "include enable-php-{}-wpfastcgi.conf;".format(version)
@@ -1375,7 +1375,7 @@ class fast_cgi:
"|-The FastCgi configuration already exists in the website configuration, skip it")
return public.return_msg_gettext(True,
"The FastCgi configuration already exists in the website configuration")
rep = "include\s+enable-php-{}.conf;".format(version)
rep = r"include\s+enable-php-{}.conf;".format(version)
conf = re.sub(rep, fastcgi_conf, conf)
public.writeFile(conf_path, conf)
conf_pass = public.checkWebConfig()
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import math,string,public,re
@@ -200,8 +200,8 @@ class Page():
def __SetUri(self,request_uri):
#构造URI
try:
request_uri = re.sub("&p=\d+",'&',request_uri)
request_uri = re.sub("\?p=\d+",'?',request_uri)
request_uri = re.sub(r"&p=\d+",'&',request_uri)
request_uri = re.sub(r"\?p=\d+",'?',request_uri)
if request_uri.find('&') == -1:
if request_uri[-1] != '?': request_uri += '?'
else:
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import public,os,json,time
class panelApi:
+9 -65
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2019 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -60,8 +60,8 @@ class panelAuth:
def check_serverid(self,get):
if get.serverid != self.create_serverid(get): return False
return True
# 旧接口 没有永久版
def get_plugin_price2(self, get):
def get_plugin_price(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get: return public.return_msg_gettext(False,'Parameter ERROR!')
@@ -71,7 +71,7 @@ class panelAuth:
params['product_id'] = self.get_plugin_info(get.pluginName)['id']
else:
params['product_id'] = get.product_id
data = self.send_cloud('{}/api/product/prices'.format(self.__official_url), params)
data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params)
if not data:
return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!')
if not data['success']:
@@ -82,61 +82,7 @@ class panelAuth:
except:
del(session['get_product_list'])
return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),))
# 获取永久版信息
def get_plugin_price3(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get:
return public.return_msg_gettext(False,'Parameter ERROR!')
if not os.path.exists(userPath):
return public.return_msg_gettext(False,'Please login with account first')
params = {}
if not hasattr(get,'product_id'):
params['product_id'] = self.get_plugin_info(get.pluginName)['id']
else:
params['product_id'] = get.product_id
data = self.send_cloud('{}/api/product/pricesV2'.format(self.__official_url), params)
if not data:
return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!')
if not data['success']:
return public.return_msg_gettext(False,data['msg'])
return data['res']
except:
# del(session['get_product_list'])
return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),))
# 获取价格列表 新增多机购买
def get_plugin_price(self, get):
try:
userPath = 'data/userInfo.json'
if not 'pluginName' in get and not 'product_id' in get:
return public.return_msg_gettext(False, 'Parameter ERROR!')
if not os.path.exists(userPath):
return public.return_msg_gettext(False, 'Please login with account first')
params = {}
if not hasattr(get, 'product_id'):
params['product_id'] = self.get_plugin_info(get.pluginName)['id']
else:
params['product_id'] = get.product_id
data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params)
if not data:
return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!')
if not data['success']:
return public.return_msg_gettext(False, data['msg'])
return data['res']
except Exception as ex:
public.print_log("获取价格报错 {}".format(ex))
# del(session['get_product_list'])
return public.return_msg_gettext(False, 'Syncing information, please try again!\n {}',
(public.get_error_info(),))
def get_plugin_info(self,pluginName):
data = self.get_business_plugin(None)
if not data: return None
@@ -183,7 +129,6 @@ class panelAuth:
return public.return_msg_gettext(False, data['res'])
return data['res']
def get_stripe_session_id(self,get):
params = {}
@@ -413,8 +358,7 @@ class panelAuth:
data = self.send_cloud('{}/api/user/productAuthorizes'.format(self.__official_url), params)
if not data:
return []
if not data['success']:
return []
if not data['success']: return []
data = data['res']
# return [i for i in data['list'] if i['status'] != 'activated' and get.pid == i['product_id']]
res = list()
@@ -444,7 +388,7 @@ class panelAuth:
if hasattr(get,'coupon_id') and get.pay_channel == '10':
params['coupon_id'] = get.coupon_id
data = self.send_cloud('{}/api/authorize/product/renew'.format(self.__official_url), params)
# public.print_log('############ 续费接口 {}'.format(data))
if not data['success']:
data['res'] = 'Invalid authorize OR authorize not found!'
return data
+25 -5
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
@@ -422,10 +422,30 @@ class backup:
def backup_site(self, siteName, save=3, exclude=[], echo_id=None):
try:
self.echo_start()
find = public.M('sites').where('name=?', (siteName,)).field('id,path').find()
find = public.M('sites').where('name=?', (siteName,)).field('id,path,project_type').find()
public.print_log(find)
if not find:
if not find or not isinstance(find, dict):
raise Exception(' The directory for does not exist')
# Wordpress
if find['project_type'] == 'WP2':
import PluginLoader
if PluginLoader.get_auth_state() < 1:
self.echo_info(public.get_msg_gettext('Authorization is invalid or expired'))
return None
try:
from wp_toolkit import wpbackup
bak_info = wpbackup(find['id']).backup_full_get_data()
self.echo_info(public.get_msg_gettext('Backup wordpress [{}] successfully', (siteName,)))
self.echo_end()
return bak_info.bak_file
except Exception as e:
public.print_error()
self.echo_info(str(e))
self.echo_end()
return None
spath = find['path']
pid = find['id']
fname = 'web_{}_{}.tar.gz'.format(siteName, public.format_date("%Y%m%d_%H%M%S"))
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -32,8 +32,8 @@ class Controller:
if args['mod_name'] in ['base']: return public.return_status_code(1000,'错误的调用!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'调用的方法名称中不能包含“__”字符')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'调用的模块名称中不能包含\w以外的字符')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'调用的方法名称中不能包含\w以外的字符')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'调用的模块名称中不能包含\w以外的字符')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'调用的方法名称中不能包含\w以外的字符')
except:
return public.get_error_object()
# 参数处理
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -32,8 +32,8 @@ class DatabaseController:
if args['mod_name'] in ['base']: return public.return_status_code(1000,'Bad call!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
# 参数处理
+2 -2
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang
# +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# +-------------------------------------------------------------------
import public,os,sys,json,time,random
import requests
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -29,9 +29,9 @@ class HttpProxy:
'''
headers = {}
for h in p_res.headers.keys():
if h in ['Content-Encoding','Transfer-Encoding']: continue
if h in ['content-encoding', 'Content-Encoding', 'transfer-encoding', 'Transfer-Encoding']: continue
headers[h] = p_res.headers[h]
if h in ['Location']:
if h in ['location', 'Location']:
if headers[h].find('phpmyadmin_') != -1:
if not self._pma_path:
+4 -4
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# +-------------------------------------------------------------------
import os,sys,json,time,re
setup_path = '/www/server/panel'
@@ -763,7 +763,7 @@ class panelLets:
tlist = []
for siteName in old_list:
if not siteName in cron_list: tlist.append(siteName)
print(public.get_msg_gettext('|-[{}] Not expired or the site does not use the Let\s Encrypt certificate.',(','.join(tlist),)))
print(public.get_msg_gettext(r'|-[{}] Not expired or the site does not use the Let\s Encrypt certificate.',(','.join(tlist),)))
print(public.get_msg_gettext('|-{} Waiting for renewal [{}].',(time.strftime('%Y-%m-%d %X',time.localtime()),str(len(cron_list)))))
sucess_list = []
+2 -2
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <2020-05-18>
# +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
# Author: cjxin <cjxin@aapanel.com>
#-------------------------------------------------------------------
# 备份
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: cjxin <cjxin@bt.cn>
# Author: cjxin <cjxin@aapanel.com>
#-------------------------------------------------------------------
# 面板其他模型新增功能
+274
View File
@@ -0,0 +1,274 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: cjxin <cjxin@aapanel.com>
# -------------------------------------------------------------------
# 备份
# ------------------------------
import os, sys, re, json, shutil, psutil, time
from panelModel.base import panelBase
import public, config, panelTask
try:
from BTPanel import cache
except:pass
class main(panelBase):
__table = 'task_list'
# public.check_database_field("ssl_data.db","ssl_info")
task_obj = panelTask.bt_task()
def __init__(self):
pass
"""
@name 获取面板日志
"""
def get_update_logs(self,get):
try:
skey = 'panel_update_logs'
res = cache.get(skey)
if res: return res
res = public.httpPost('https://www.bt.cn/Api/getUpdateLogs?type=Linux',{})
start_index = res.find('(') + 1
end_index = res.rfind(')')
json_data = res[start_index:end_index]
res = json.loads(json_data)
cache.set(skey,res,60)
except:
res = []
return res
def get_public_config(self, args):
"""
@name 获取公共配置
"""
public.print_log("error 3366666 原始方法: ")
_config_obj = config.config()
data = _config_obj.get_config(args)
data['task_list'] = self.task_obj.get_task_lists(args)
data['task_count'] = public.M('tasks').where("status!=?", ('1',)).count()
data['get_pd'] = self.get_pd(args)
data['ipv6'] = ''
if _config_obj.get_ipv6_listen(None): data['ipv6'] = 'checked'
data['is_local'] = ''
if public.is_local(): data['is_local'] = 'checked'
if data['get_pd'] and data['get_pd'][2] != -1:
time_diff = (data['get_pd'][2]-int(time.time())) % (365*86400)
data['active_pro_time'] = int(time.time()) - (365*86400 - time_diff)
else:
data['active_pro_time'] = 0
data['status_code'] = _config_obj.get_not_auth_status()
if os.path.exists('/www/server/panel/config/api.json'):
try:
res = json.loads(public.readFile('/www/server/panel/config/api.json'))
data['api'] = 'checked' if res['open'] else ''
except:
public.ExecShell('rm -f /www/server/panel/config/api.json')
data['api'] = ''
else:
data['api'] = ''
return data
def get_pd(self, get):
from BTPanel import cache
tmp = -1
try:
import panelPlugin
# get = public.dict_obj()
# get.init = 1
tmp1 = panelPlugin.panelPlugin().get_cloud_list(get)
except:
tmp1 = None
if tmp1:
tmp = tmp1[public.to_string([112, 114, 111])]
ltd = tmp1.get('ltd', -1)
else:
ltd = -1
tmp4 = cache.get(
public.to_string([112, 95, 116, 111, 107, 101, 110]))
if tmp4:
tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4
if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1')
tmp = public.readFile(tmp_f)
if tmp: tmp = int(tmp)
if not ltd: ltd = -1
if tmp == None: tmp = -1
if ltd < 1:
if ltd == -2:
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34,
98, 116, 108, 116, 100, 45, 103, 114, 97, 121, 34, 62, 60,
115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99,
111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54,
59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116,
58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110,
45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62,
24050, 36807, 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97,
32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110,
107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98,
116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116,
97, 95, 108, 116, 100, 40, 41, 34, 62, 32493, 36153, 60,
47, 97, 62, 60, 47, 115, 112, 97, 110, 62
])
elif tmp == -1:
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34,
98, 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32,
111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115,
111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 99, 111,
109, 109, 101, 114, 99, 105, 97, 108, 95, 118, 105, 101,
119, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34,
28857, 20987, 21319, 32423, 21040, 21830, 19994, 29256, 34,
62, 20813, 36153, 29256, 60, 47, 115, 112, 97, 110, 62
])
elif tmp == -2:
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34,
98, 116, 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60,
115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99,
111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54,
59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116,
58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110,
45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62,
24050, 36807, 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97,
32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110,
107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98,
116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116,
97, 95, 112, 114, 111, 40, 41, 34, 62, 32493, 36153, 60,
47, 97, 62, 60, 47, 115, 112, 97, 110, 62
])
if tmp >= 0 and ltd in [-1, -2]:
if tmp == 0:
tmp2 = public.to_string([27704, 20037, 25480, 26435])
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61,
34, 98, 116, 112, 114, 111, 34, 62, 123, 48, 125, 60,
115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34,
99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100,
50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103,
104, 116, 58, 32, 98, 111, 108, 100, 59, 34, 62, 123,
49, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115,
112, 97, 110, 62
]).format(
public.to_string([21040, 26399, 26102, 38388, 65306]),
tmp2)
else:
tmp2 = time.strftime(
public.to_string([37, 89, 45, 37, 109, 45, 37, 100]),
time.localtime(tmp))
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61,
34, 98, 116, 112, 114, 111, 34, 62, 21040, 26399,
26102, 38388, 65306, 60, 115, 112, 97, 110, 32, 115,
116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58,
32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110,
116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111,
108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114,
105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, 48,
125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99,
108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107,
34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98,
116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97,
116, 97, 95, 112, 114, 111, 40, 41, 34, 62, 32493,
36153, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62
]).format(tmp2)
else:
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34,
98, 116, 108, 116, 100, 45, 103, 114, 97, 121, 34, 32, 111,
110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111,
102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116,
100, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34,
28857, 20987, 21319, 32423, 21040, 20225, 19994, 29256, 34,
62, 20813, 36153, 29256, 60, 47, 115, 112, 97, 110, 62
])
else:
tmp3 = public.to_string([
60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98,
116, 108, 116, 100, 34, 62, 21040, 26399, 26102, 38388, 65306,
60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99,
111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59,
102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32,
98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114,
105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, 125, 60, 47,
115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61,
34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108,
105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117,
112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62,
32493, 36153, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62
]).format(
time.strftime(
public.to_string([37, 89, 45, 37, 109, 45, 37, 100]),
time.localtime(ltd)))
return tmp3, tmp, ltd
@staticmethod
def set_backup_path(get):
try:
backup_path = get.backup_path.strip().rstrip("/")
except AttributeError:
return public.returnMsg(False, "参数错误")
if not os.path.exists(backup_path):
return public.returnMsg(False, "指定目录不存在")
if backup_path[-1] == "/":
backup_path = backup_path[:-1]
import files
try:
from BTPanel import session
except:
session = None
fs = files.files()
if not fs.CheckDir(get.backup_path):
return public.returnMsg(False, '不能使用系统关键目录作为默认备份目录')
if session is not None:
session['config']['backup_path'] = os.path.join('/', backup_path)
db_backup = backup_path + '/database'
site_backup = backup_path + '/site'
if not os.path.exists(db_backup):
try:
os.makedirs(db_backup, 384)
except:
public.ExecShell('mkdir -p ' + db_backup)
if not os.path.exists(site_backup):
try:
os.makedirs(site_backup, 384)
except:
public.ExecShell('mkdir -p ' + site_backup)
public.M('config').where("id=?", ('1',)).save('backup_path', (get.backup_path,))
public.WriteLog('TYPE_PANEL', 'PANEL_SET_SUCCESS', (get.backup_path,))
public.restart_panel()
return public.returnMsg(True, "设置成功")
def get_soft_status(self,get):
if not hasattr(get,'name'): return public.returnMsg(False,'参数错误')
name = get.name.strip()
if name == 'sqlite':
return public.returnMsg(True,'符合!')
if os.path.exists('/www/server/{}'.format(name)) and len(os.listdir('/www/server/{}'.format(name))) > 2:
return public.returnMsg(True,'符合!')
if name == ['mysql','pgsql','sqlserver','mongodb','redis']:
count = public.M('database_servers').where("LOWER(db_type)=LOWER(?)", (name,)).count()
if count > 0: return public.returnMsg(True,'符合!')
return public.returnMsg(False,'不符合!')
+23 -10
View File
@@ -2,9 +2,9 @@
# +-------------------------------------------------------------------
# | 宝塔Windows面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# +-------------------------------------------------------------------
import re,os,sys,public
@@ -43,7 +43,8 @@ class panelMssql:
try:
import pymssql
except :
os.system("btpip install pymssql==2.1.4")
os.system("btpip install pymssql==2.3.0")
# os.system("btpip install pymssql==2.1.4")
import pymssql
@@ -53,17 +54,29 @@ class panelMssql:
self.__DB_PORT = self.get_port()
try:
if self.__DB_CLOUD:
self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),user=self.__DB_USER,password=self.__DB_PASS,database = None,login_timeout = 30,timeout = 0,autocommit = True)
if self.__DB_CLOUD:
try:
self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT),
user=self.__DB_USER,
password=self.__DB_PASS, database=None, login_timeout=30,
timeout=0, autocommit=True, charset="CP936", tds_version='7.0')
except:
self.__DB_ERR = 'Failed to connect to database! Check that the remote database information is correct'
return False
# self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT),
# user=self.__DB_USER,
# password=self.__DB_PASS, database=None, login_timeout=30,
# timeout=0, autocommit=True, charset="CP936", tds_version='7.0')
else:
self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),login_timeout = 30,timeout = 0,autocommit = True)
self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。
self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。
self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), login_timeout=30,
timeout=0, autocommit=True, charset="CP936", tds_version='7.0')
self.__DB_CUR = self.__DB_CONN.cursor() # 将数据库连接信息,赋值给cur。
self.__DB_CUR = self.__DB_CONN.cursor() # 将数据库连接信息,赋值给cur。
if self.__DB_CUR:
return True
else:
self.__DB_ERR = '连接数据库失败,请检查是否安装SQL Server'
self.__DB_ERR = 'Failed to connect to the database, please check whether SQL Server is installed'
return False
except Exception as ex:
self.__DB_ERR = public.get_error_info()
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
import re,os,sys,public
+9 -9
View File
@@ -1,10 +1,10 @@
#coding:utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <hwl@bt.cn>
# | Author: hwliang <hwl@aapanel.com>
# +-------------------------------------------------------------------
# +-------------------------------------------------------------------
@@ -199,18 +199,18 @@ class panelPHP:
# if o_pma_root:
# if not os.path.exists(pma_root):
# os.makedirs(pma_root)
# public.ExecShell("\cp -arf {}/* {}/".format(o_pma_root,pma_root))
# public.ExecShell(r"\cp -arf {}/* {}/".format(o_pma_root,pma_root))
# public.ExecShell("chown -R www:www {}".format(pma_root))
# public.ExecShell("chmod -R 700 {}".format(pma_root))
# public.ExecShell("\cp -arf {} {}".format(pma_version_f1,pma_version_f2))
# public.ExecShell(r"\cp -arf {} {}".format(pma_version_f1,pma_version_f2))
# index = public.readFile(pma_root + '/index.php')
# if index:
# if index.find("use PhpMyAdmin\\Util") != -1:
# resp = "use PhpMyAdmin\\Util;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');"
# index = index.replace("use PhpMyAdmin\\Util;",resp)
# elif index.find("use PMA\libraries\LanguageManager;") != -1:
# resp = "use PMA\libraries\LanguageManager;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');"
# index = index.replace("use PMA\libraries\LanguageManager;",resp)
# elif index.find(r"use PMA\libraries\LanguageManager;") != -1:
# resp = "use PMA\\libraries\\LanguageManager;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');"
# index = index.replace(r"use PMA\libraries\LanguageManager;",resp)
# elif index.find("require_once 'libraries/common.inc.php';") != -1:
# resp = "if(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');\nrequire_once 'libraries/common.inc.php';"
# index = index.replace("require_once 'libraries/common.inc.php';",resp)
@@ -364,7 +364,7 @@ class panelPHP:
# dst_path = '/www/server/adminer'
# if os.path.exists(src_path):
# if not os.path.exists(dst_path): os.makedirs(dst_path)
# public.ExecShell("\cp -arf {}/* {}/".format(src_path, dst_path))
# public.ExecShell(r"\cp -arf {}/* {}/".format(src_path, dst_path))
# public.ExecShell("chown -R www:www {}".format(dst_path))
# public.ExecShell("chmod -R 700 {}".format(dst_path))
# public.ExecShell("rm -rf {}".format(src_path))
+609 -152
View File
@@ -1,36 +1,83 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
import public,os,sys,json,time,psutil,py_compile,re
from BTPanel import session,cache,send_file
import public
import os
import sys
import json
import time
import psutil
import re
import shutil
import requests
from BTPanel import session, cache, send_file
if sys.version_info[0] == 3: from importlib import reload
class mget: pass
class panelPlugin:
__isTable = None
__install_path = None
__tasks = None
__list = 'data/list.json'
__type = 'data/type.json'
__index = 'config/index.json'
__link = 'config/link.json'
__product_list = None
__plugin_list = None
__exists_names = {}
__plugin_s_list = []
__official_url = 'https://www.aapanel.com'
# __official_url = 'http://dev.aapanel.com'
pids = None
ROWS = 15
def __init__(self):
self.__isTable = None
self.__tasks = None
self.__product_list = None
self.__plugin_list = None
self.__exists_names = {}
self.__plugin_s_list = []
self.__plugin_info = None
self.__plugin_name = None
self.__plugin_object = None
self.__plugin_list = None
self.__panel_path = '/www/server/panel'
self.__plugin_path = self.__panel_path + '/plugin/'
self.__plugin_save_file = self.__panel_path + '/data/plugin_bin.pl'
self.__api_root_url = self.__official_url + '/api'
self.__api_url = self.__api_root_url + '/panel/get_plugin_list'
self.__download_url = self.__api_root_url + '/panel/download_plugin'
self.__download_d_main_url = self.__api_root_url + '/panel/download_plugin_main'
self._check_url = self.__api_root_url + '/panel/get_soft_list_status'
self._unbinding_url = self.__api_root_url + '/panel/get_unbinding'
self.__tmp_path = self.__panel_path + '/temp/'
self.__plugin_timeout = 3600
self.__is_php = False
self.__install_opt = 'i'
self.__pid = 0
self.__path_error = self.__panel_path + '/data/error_pl.pl'
self.__error_html = '/www/server/panel/BTPanel/templates/default/block_error.html'
self.__sub_rules = []
self.__replace_rule = []
self.pids = None
self.ROWS = 15
self.__install_path = '/www/server/panel/plugin'
#检查依赖
if not self.__tasks:
try:
self.__tasks = public.M('tasks').where("status!=?", ('1',)).field('status,name').select()
except:
self.__tasks = []
if not os.path.exists(self.__tmp_path):
os.makedirs(self.__tmp_path, 0o755)
# 检查依赖
def check_deps(self,get):
cacheKey = 'plugin_lib_list'
if not 'force' in get:
@@ -79,11 +126,11 @@ class panelPlugin:
return True
#检查依赖
def check_dependnet(self,dependnet):
if not dependnet: return True
dependnets = dependnet.split(',')
def check_dependent(self,dependent):
if not dependent: return True
dependents = dependent.split(',')
status = True
for dep in dependnets:
for dep in dependents:
if not dep: continue
if dep.find('|') != -1:
names = dep.split('|')
@@ -137,7 +184,7 @@ class panelPlugin:
if not self.check_mutex(pluginInfo['mutex']): return public.return_msg_gettext(False, 'Please uninstall [{}] first',
(self.mutex_title,))
if not hasattr(get, 'id'):
if not self.check_dependnet(pluginInfo['dependent']): return public.return_msg_gettext(False, 'Depends on the following software, please install [{}] first',
if not self.check_dependent(pluginInfo['dependent']): return public.return_msg_gettext(False, 'Depends on the following software, please install [{}] first',
(pluginInfo['dependent'],))
if 'version' in get:
for versionInfo in pluginInfo['versions']:
@@ -153,7 +200,32 @@ class panelPlugin:
m_ps = {0: "All", 1: "Centos", 2: "Ubuntu/Debian"}
return public.return_msg_gettext(False, 'Only supports [{}] system',(m_ps[int(versionInfo['os_limit'])],))
if not hasattr(get, 'id'):
if not self.check_dependnet(versionInfo['dependent']): return public.return_msg_gettext(False,'Depend on the following software, please install first [{}]',(versionInfo['dependent'],))
if not self.check_dependent(versionInfo['dependent']): return public.return_msg_gettext(False,'Depend on the following software, please install first [{}]',(versionInfo['dependent'],))
# 获取插件安装包下载进度
def get_download_speed(self, get):
'''
@name 获取插件下载进度
@author hwliang<2021-06-25>
@param plugin_name<string> 插件名称
@return dict
'''
result = self.__get_download_speed(get.plugin_name)
return result
# 取消下载
def close_install(self, get):
'''
@name 取消指定插件安装过程
@author hwliang<2021-07-07>
@param plugin_name<string> 插件名称
@return void
'''
plugin_name = get.plugin_name.strip()
tmp_path = '{}/{}'.format(self.__tmp_path, plugin_name)
if os.path.exists(tmp_path): shutil.rmtree(tmp_path)
return public.returnMsg(False, '安装过程已取消!')
#安装插件
def install_plugin(self,get):
str1 = public.get_msg_gettext("System critical directory is not writable!")
@@ -173,6 +245,7 @@ class panelPlugin:
return check_result
if pluginInfo['name'] in ['dns_manager','mail_sys']:
pluginInfo['type'] = 5
if pluginInfo['type'] != 5:
result = self.install_sync(pluginInfo,get)
else:
@@ -209,16 +282,21 @@ class panelPlugin:
if os.path.exists(pluginInfo['install_checks']): update =pluginInfo['versions'][0]['version_msg']
return self.update_zip(None,toFile,update)
else:
download_url = public.get_url() + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh'
toFile = '/tmp/%s.sh' % pluginInfo['name']
public.downloadFile(download_url,toFile)
self.set_pyenv(toFile)
public.ExecShell('/bin/bash ' + toFile + ' install &> /tmp/panelShell.pl')
if os.path.exists(pluginInfo['install_checks']):
public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],))
if os.path.exists(toFile): os.remove(toFile)
return public.return_msg_gettext(True,'Installation succeeded!')
return public.return_msg_gettext(False,'Installation failed')
# download_url = public.get_url() + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh'
# toFile = '/tmp/%s.sh' % pluginInfo['name']
# public.downloadFile(download_url,toFile)
# self.set_pyenv(toFile)
# public.ExecShell('/bin/bash ' + toFile + ' install &> /tmp/panelShell.pl')
# if os.path.exists(pluginInfo['install_checks']):
# public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],))
# if os.path.exists(toFile): os.remove(toFile)
# return public.return_msg_gettext(True,'Installation succeeded!')
# return public.return_msg_gettext(False,'Installation failed')
if hasattr(get, 'min_version'):
get.version += '.' + get.min_version
return self.__install_plugin(pluginInfo['name'], get.version)
# 设置Python环境变量
def set_pyenv(self, filename):
@@ -235,7 +313,11 @@ class panelPlugin:
#异步安装
def install_async(self,pluginInfo,get):
mtype = 'install';
# # 只取主版本号与子版本号,忽略修订号
# if 'version' in get:
# get.version = '.'.join(str(get.version).split('.')[:2])
mtype = 'install'
mmsg = public.get_msg_gettext('Install')
if hasattr(get, 'upgrade'):
mtype = 'update'
@@ -267,6 +349,11 @@ class panelPlugin:
execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh {} {} {} {} {}".format(
get.type, mtype, get.sName, get.version, ols_execstr)
if get.sName == "phpmyadmin":
# 当面板开启SSL时,标记下次打开phpmyadmin时需要设置SSL
if os.path.exists('{}/data/ssl.pl'.format(public.get_panel_path())):
with open('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path()), 'w') as fp:
fp.write('1')
execstr += "&> /tmp/panelExec.log"
if public.get_webserver() == 'openlitespeed':
execstr += " && sleep 1 && /usr/local/lsws/bin/lswsctrl restart"
@@ -322,74 +409,45 @@ class panelPlugin:
return public.return_msg_gettext(True,"Uninstallation succeeded")
#从云端取列表
def get_cloud_list(self,get=None):
lcoalTmp = 'data/plugin.json'
softList = None
listTmp = public.readFile(lcoalTmp)
force_refresh = 0
try:
if listTmp: softList = json.loads(listTmp)
if 'success' in softList and not softList['success']:
if os.path.exists(lcoalTmp): os.remove(lcoalTmp)
force_refresh = 1
except:
if os.path.exists(lcoalTmp): os.remove(lcoalTmp)
def get_cloud_list(self, get=None):
force = 0
if get and hasattr(get, 'force'):
force = int(get.force)
if 'focre_cloud' in session:
if session['focre_cloud']:
force = 1
session['focre_cloud'] = False
if 'init_cloud' not in session:
force = 1
session['init_cloud'] = True
softList = public.load_soft_list(True if force == 1 else False)
if get and 'init' in get:
if softList:
if 'success' not in softList:
return softList
focre = 0
if hasattr(get,'force'): focre = int(get.force)
if session:
if 'focre_cloud' in session:
if session['focre_cloud']:
focre = 1
session['focre_cloud'] = False
if not 'init_cloud' in session:
focre = 1
session['init_cloud'] = True
if force_refresh == 1:
focre = 1
if not softList or focre > 0:
self.clean_panel_log()
# cloudUrl = 'https://console.aapanel.com/api/panel/get_soft_list'
cloudUrl = '{}/api/panel/getSoftList'.format(self.__official_url)
import panelAuth
import requests
pdata = panelAuth.panelAuth().create_serverid(None)
# listTmp = public.httpPost(cloudUrl,pdata,6)
url_headers={}
if 'token' in pdata:
url_headers = {"authorization": "bt {}".format(pdata['token'])}
pdata['environment_info'] = json.dumps(public.fetch_env_info())
# listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers,verify=False,timeout=10)
# listTmp=listTmp.json()
try:
listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10)
listTmp.raise_for_status() # 检查请求是否成功,如果不成功会抛出异常
listTmp = listTmp.json()
except:
listTmp = False
if listTmp is False:
listTmp = public.readFile(lcoalTmp)
try:
softList = listTmp
except:
pass
if softList: public.writeFile(lcoalTmp,json.dumps(softList))
if force > 0:
public.ExecShell('rm -f /tmp/bmac_*')
public.run_thread(self.getCloudPHPExt)
# 专业版和企业版到期提醒,aaPanel目前没有先注释
# self.expire_msg(softList)
try:
public.writeFile("/tmp/" + cache.get('p_token'),str(softList['pro']))
except:pass
p_token = cache.get('p_token')
if p_token is None:
p_token = 'bmac_' + public.Md5(public.get_mac_address())
cache.set('p_token', p_token)
public.writeFile("/tmp/" + p_token, str(softList['pro']))
public.writeFile('/tmp/{}.time'.format(p_token), str(int(time.time())))
except:
pass
sType = 0
try:
if hasattr(get,'type'): sType = int(get['type'])
@@ -414,14 +472,13 @@ class panelPlugin:
sType = 0
except:pass
try:
softList = json.loads(softList)
except:
pass
# 扫描本地插件并追加到软件列表中
softList['list'] = self.get_local_plugin(softList['list'])
softList['list'] = self.get_types(softList['list'],sType)
if hasattr(get,'query'):
# 软件列表分类处理
softList['list'] = self.get_types(softList['list'], sType)
if hasattr(get, 'query'):
if get.query:
get.query = get.query.lower()
tmpList = []
@@ -431,11 +488,11 @@ class panelPlugin:
softInfo['ps'].lower().find(get.query) != -1:
tmpList.append(softInfo)
softList['list'] = tmpList
for softInfo in softList['list']:
if 'uninsatll_checks' not in softInfo:
softInfo['uninsatll_checks'] = softInfo['uninstall_checks']
# if not softList['list']:
# if os.path.exists(lcoalTmp): os.remove(lcoalTmp)
return softList
#取提醒标记
@@ -656,7 +713,7 @@ class panelPlugin:
"s_version": "0",
"manager_version": "1",
"c_manager_version": "1",
"dependnet": "",
"dependent": "",
"mutex": "",
"install_checks": "/www/server/panel/plugin/" + info['name'],
"uninsatll_checks": "/www/server/panel/plugin/" + info['name'],
@@ -666,7 +723,7 @@ class panelPlugin:
{
"m_version": m_version[0],
"version": m_version[1],
"dependnet": "",
"dependent": "",
"mem_limit": 32,
"cpu_limit": 1,
"os_limit": 0,
@@ -756,20 +813,14 @@ class panelPlugin:
return softList
#取首页软件列表
def get_index_list(self,get=None):
def get_index_list(self, get=None):
softList = self.get_cloud_list(get)['list']
if not softList:
get.force = 1
softList = self.get_cloud_list(get)['list']
if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401")
softList = self.set_coexist(softList)
# # 只取应用名
# lista = [i['name'] for i in softList]
#
# public.print_log("23454566777^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_______________________________"
# "列表 {}".format(lista))
if not os.path.exists(self.__index):
public.writeFile(self.__index,'[]')
if not os.path.exists(self.__index): public.writeFile(self.__index,'[]')
try:
indexList = json.loads(public.ReadFile(self.__index))
except Exception:
@@ -1021,11 +1072,18 @@ class panelPlugin:
if softInfo['name'] == sName:
if sName == 'phpmyadmin':
# 检查是否需要开启SSL
if os.path.exists('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path())) and os.path.exists('{}/phpmyadmin'.format(public.get_setup_path())):
os.remove('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path()))
from ajax_v2 import ajax
ajax().set_phpmyadmin_ssl(public.to_dict_obj({'v': '1'}))
from BTPanel import get_phpmyadmin_dir
pmd = get_phpmyadmin_dir()
softInfo['ext'] = self.getPHPMyAdminStatus()
if softInfo['ext'] and pmd:
softInfo['ext']['url'] = 'http://' + public.GetHost() + ':'+ pmd[1] + '/' + pmd[0]
port = softInfo['ext']['ssl_port'] if softInfo['ext'].get('ssl_enabled', False) else pmd[1]
softInfo['ext']['url'] = 'http' + ('s' if softInfo['ext'].get('ssl_enabled', False) else '') + '://' + public.GetHost() + ':'+ port + '/' + pmd[0]
if "php-" in sName:
v = softInfo["versions"][0]["m_version"]
v1 = v.replace(".", "")
@@ -1036,6 +1094,7 @@ class panelPlugin:
else:
softInfo["php_ini"] = "/www/server/php/{}/etc/php.ini".format(v1)
return self.check_status(softInfo)
return False
@@ -1181,7 +1240,13 @@ class panelPlugin:
page = page.Page()
info = {}
info['count'] = len(data)
info['row'] = self.ROWS
info['row'] = self.ROWS
if hasattr(get,'row'):
try:
info['row'] = int(get['row'])
except:
info['row'] = self.ROWS
info['p'] = 1
if hasattr(get,'p'):
try:
@@ -1637,7 +1702,7 @@ class panelPlugin:
if os.path.exists('/www/server/apache/bin/httpd'):
v1 = session.get('httpdv')
if not v1:
v1 = public.ExecShell("/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip();
v1 = public.ExecShell(r"/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip();
session['httpdv'] = v1
#if name == 'mysql':
# if os.path.exists('/www/server/mysql/bin/mysql'): v1 = public.ExecShell("mysql -V|awk '{print $5}'|sed 's/,//'")[0].strip();
@@ -1749,6 +1814,8 @@ class panelPlugin:
pstatus = False
phpversion = "54"
phpport = '888'
ssl_port = '887'
ssl_enabled = False
if os.path.exists(configFile):
conf = public.readFile(configFile)
rep = r"listen\s+([0-9]+)\s*;"
@@ -1756,6 +1823,15 @@ class panelPlugin:
if rtmp:
phpport = rtmp.groups()[0]
# SSL配置文件查看
ssl_config_file = '{}/vhost/nginx/phpmyadmin.conf'.format(public.get_panel_path())
if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10:
tmps = public.readFile(ssl_config_file)
m = re.search(r"listen\s*(\d+)", tmps)
if m is not None:
ssl_enabled = True
ssl_port = m.group(1)
if conf.find('AUTH_START') != -1: pauth = True
if conf.find(setupPath + '/stop') == -1: pstatus = True
configFile = setupPath + '/nginx/conf/enable-php.conf'
@@ -1786,8 +1862,19 @@ class panelPlugin:
rtmp = re.search(rep,conf)
if rtmp:
phpport = rtmp.groups()[0]
# SSL配置文件查看
ssl_config_file = '{}/vhost/apache/phpmyadmin.conf'.format(public.get_panel_path())
if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10:
tmps = public.readFile(ssl_config_file)
m = re.search(r"Listen\s*(\d+)", tmps)
if m is not None:
ssl_enabled = True
ssl_port = m.group(1)
if conf.find('AUTH_START') != -1: pauth = True
if conf.find('/www/server/stop') == -1: pstatus = True
if os.path.exists('/usr/local/lsws/bin/lswsctrl'):
result = self._get_ols_myphpadmin_info()
if result:
@@ -1808,7 +1895,9 @@ class panelPlugin:
tmp['run'] = pstatus
tmp['phpversion'] = phpversion
tmp['ssl_enabled'] = ssl_enabled
tmp['port'] = phpport
tmp['ssl_port'] = ssl_port
tmp['auth'] = pauth
except Exception as ex:
tmp['status'] = False
@@ -1819,18 +1908,18 @@ class panelPlugin:
filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf"
conf = public.readFile(filename)
if not conf:return False
reg = '/usr/local/lsws/lsphp(\d+)/bin/lsphp'
reg = r'/usr/local/lsws/lsphp(\d+)/bin/lsphp'
php_v = re.search(reg,conf)
phpversion = '73'
phpport = '888'
if php_v:
phpversion = php_v.groups(1)
phpversion = php_v.group(1)
filename = '/www/server/panel/vhost/openlitespeed/listen/888.conf'
conf = public.readFile(filename)
reg = 'address\s+\*\:(\d+)'
reg = r'address\s+\*\:(\d+)'
php_port = re.search(reg,conf)
if php_port:
phpport = php_port.groups(1)
phpport = php_port.group(1)
pauth = False
pstatus = False
if conf.find('/www/server/stop') == -1: pstatus = True
@@ -2164,43 +2253,12 @@ class panelPlugin:
return find['title']
#请求插件事件
def a(self,get):
if not hasattr(get,'name'): return public.return_msg_gettext(False,'Input name of plugin!')
def a(self, get):
try:
if not public.path_safe_check("%s/%s" % (get.name,get.s)): return public.return_msg_gettext(False,'Requested method [{}] does not exist!')
path = self.__install_path + '/' + get.name
# aaa = path + '/'+get.name+'_main.py'
# public.print_log("@@@@@@@@@@@@@@@@@aaa {}".format(aaa))
if not os.path.exists(path + '/'+get.name+'_main.py'):
if os.path.exists(path+'/index.php'):
# bbb = path+'/index.php'
# public.print_log("@@@@@@@@@@@@@@@@@bbb {}".format(bbb))
import panelPHP
return panelPHP.panelPHP(get.name).exec_php_script(get)
return public.return_msg_gettext(False,'This plugin does NOT have extend function!')
if not self.check_accept(get):return public.return_msg_gettext(False,"You did not purchase [ {} ] or the authorization has expired", (self.get_title_byname(get),))
public.package_path_append(path)
plugin_main = __import__(get.name+'_main')
try:
reload(plugin_main)
except: pass
pluginObject = eval('plugin_main.' + get.name + '_main()')
if not hasattr(pluginObject,get.s): return public.return_msg_gettext(False,'Requested method [{}] does not exist!',(get.s,))
execStr = 'pluginObject.' + get.s + '(get)'
return eval(execStr)
return public.run_plugin(get.name, get.s, get)
except:
import traceback
errorMsg = traceback.format_exc()
public.writeFile('logs/done.log',errorMsg)
return public.return_msg_gettext(False,'%s:<br> %s ' % (public.get_msg_gettext('Sorry, something went wrong'),errorMsg.replace('\n','<br>')))
return public.get_error_object(None, plugin_name=get.name)
#上传插件包
def update_zip(self,get = None,tmp_file = None, update = False):
tmp_path = '/www/server/panel/temp'
@@ -2266,7 +2324,7 @@ class panelPlugin:
if not os.path.exists(get.tmp_path): return public.return_msg_gettext(False,'TEM_FILE_NOT_EXIST!')
plugin_path = '/www/server/panel/plugin/' + get.plugin_name
if not os.path.exists(plugin_path): os.makedirs(plugin_path)
public.ExecShell("\cp -a -r " + get.tmp_path + '/* ' + plugin_path + '/')
public.ExecShell(r"\cp -a -r " + get.tmp_path + '/* ' + plugin_path + '/')
public.ExecShell('chmod -R 600 ' + plugin_path)
self.set_pyenv(plugin_path + '/install.sh')
public.ExecShell('cd ' + plugin_path + ' && bash install.sh install &> /tmp/panelShell.pl')
@@ -2384,4 +2442,403 @@ class panelPlugin:
config_data.append(args_name)
public.writeFile(config_file,"\n".join(config_data))
public.write_log_gettext('Software manager','Setup software: Custom compilation parameters for {} are configured as: {}'.format(get.name,config_data))
return public.return_msg_gettext(True,'Setup successfully!')
return public.return_msg_gettext(True,'Setup successfully!')
# 安装插件
def __install_plugin(self, upgrade_plugin_name, upgrade_version=None):
'''
@name 安装指定插件
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@param upgrade_version<string> 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版
@return dict
'''
self.__plugin_name = upgrade_plugin_name
plugin_info = self.__get_plugin_find(upgrade_plugin_name)
if not plugin_info:
raise public.PanelError('指定插件不存在,无法安装!')
if not plugin_info['versions']:
raise public.PanelError('指定插件当前未发布版本信息,请稍候再安装!')
if not upgrade_version:
upgrade_version = '{}.{}'.format(
plugin_info['versions'][0]['m_version'],
plugin_info['versions'][0]['version'])
filename = self.__download_plugin(upgrade_plugin_name, upgrade_version)
# 如果下载失败
if isinstance(filename, dict):
return filename
return self.__unpackup_plugin(filename)
# 修复插件
def __repair_plugin(self, upgrade_plugin_name, upgrade_version=None):
'''
@name 修复指定插件
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@param upgrade_version<string> 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版
@return dict
'''
self.__install_opt = 'r'
return self.__install_plugin(upgrade_plugin_name, upgrade_version)
# 升级插件版本
def __upgrade_plugin(self, upgrade_plugin_name, upgrade_version=None):
'''
@name 升级到指定版本
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@param upgrade_version<string> 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版
@return dict
'''
self.__install_opt = 'u'
return self.__install_plugin(upgrade_plugin_name, upgrade_version)
# 获取插件信息
def __get_plugin_info(self, upgrade_plugin_name):
'''
@name 获取插件信息
@author hwliang<2021-06-15>
@param upgrade_plugin_name<string> 插件名称
@return dict
'''
plugin_info_file = '{}/{}/info.json'.format(self.__plugin_path,
upgrade_plugin_name)
if not os.path.exists(plugin_info_file): return {}
info_body = self.__read_file(plugin_info_file)
if not info_body: return {}
plugin_info = json.loads(info_body)
return plugin_info
# 获取插件最近1条更新日志
def __get_update_msg(self, upgrade_plugin_name, upgrade_version):
'''
@name 检查指定插件版本更新日志
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@param upgrade_version<string> 插件版本
@return string
'''
plugin_update_msg = ''
plugin_info = self.__get_plugin_find(upgrade_plugin_name)
if not plugin_info: return plugin_update_msg
for _version_info in plugin_info['versions']:
l_version = '{}.{}'.format(_version_info['m_version'],
_version_info['version'])
if l_version == upgrade_version:
plugin_update_msg = _version_info['update_msg']
break
return plugin_update_msg
# 获取插件最近10条更新日志
def __get_plugin_upgrades(self, upgrade_plugin_name):
'''
@name 检查指定插件最近10条更新日志
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@return list
'''
plugin_info = self.__get_plugin_find(upgrade_plugin_name)
if not plugin_info: return []
try:
upgrade_list = public.httpPost(
self.__api_root_url + '/down/get_update_msg',
{'soft_id': plugin_info['id']})
return json.loads(upgrade_list)
except:
return []
# 获取指定软件信息
def __get_plugin_find(self, upgrade_plugin_name=None):
'''
@name 获取指定软件信息
@author hwliang<2021-06-15>
@param upgrade_plugin_name<string> 插件名称
@return dict
'''
self.__ensure_plugin_list_obtained(True)
for p_data_info in self.__plugin_list['list']:
if p_data_info['name'] == upgrade_plugin_name:
upgrade_plugin_name = p_data_info['name']
return p_data_info
# 如果不在插件列表中
return self.__get_plugin_info(upgrade_plugin_name)
# 检查插件依赖
def __check_dependent(self, upgrade_plugin_name):
'''
@name 检查指定插件的依赖安装情况
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@return dict
'''
plugin_info = self.__get_plugin_find(upgrade_plugin_name)
if not plugin_info: return {}
if not plugin_info['dependent']: return {}
deployment_list = {}
for dependent_plu_name in plugin_info['dependent'].split(','):
p_info = self.__get_plugin_find(dependent_plu_name)
if not p_info: continue
deployment_list[dependent_plu_name] = os.path.exists(
p_info['install_checks'])
return deployment_list
# 读取指定文件
def __read_file(self, filename, open_mode='r'):
'''
@name 读取指定文件
@author hwliang<2021-06-16>
@param filename<string> 文件名
@param mode<string> 打开模式, 默认: r
@return bytes or string
'''
f_object = open(filename, mode=open_mode)
file_body = f_object.read()
f_object.close()
return file_body
# 解包插件压缩包
def __unpackup_plugin(self, tmp_file):
'''
@name 解包插件包
@author hwliang<2021-06-21>
@param tmp_file<string> 下载好的保存路径,从self.download_plugin方法中获取
@return dict
'''
if type(tmp_file) == dict:
return tmp_file
if "false" in tmp_file or "错误" in tmp_file:
return json.loads(tmp_file)
s_tmp_path = self.__tmp_path
if not os.path.exists(s_tmp_path):
os.makedirs(s_tmp_path, mode=384)
if tmp_file:
if not os.path.exists(tmp_file):
return public.returnMsg(False, '文件下载失败!')
import panelTask as plu_panelTask
plu_panelTask.bt_task()._unzip(tmp_file, s_tmp_path, '',
'/dev/null')
if os.path.exists(tmp_file):
os.remove(tmp_file)
s_tmp_path = os.path.join(s_tmp_path, self.__plugin_name)
p_info = os.path.join(s_tmp_path, 'info.json')
if not os.path.exists(p_info):
d_path = None
for plugin_df in os.walk(s_tmp_path):
if len(plugin_df[2]) < 3: continue
if not 'info.json' in plugin_df[2]: continue
if not 'install.sh' in plugin_df[2]: continue
if not os.path.exists(plugin_df[0] + '/info.json'): continue
d_path = plugin_df[0]
if d_path:
s_tmp_path = d_path
p_info = s_tmp_path + '/info.json'
try:
try:
plugin_data_info = json.loads(public.ReadFile(p_info))
except:
plugin_data_info = json.loads(self.__read_file(p_info))
plugin_data_info['size'] = public.get_path_size(s_tmp_path)
if not 'author' in plugin_data_info:
plugin_data_info['author'] = 'aapanel'
if not 'home' in plugin_data_info:
plugin_data_info['home'] = 'https://www.aapanel.com'
p_info_file = self.__plugin_path + plugin_data_info[
'name'] + '/info.json'
plugin_data_info['old_version'] = '0'
plugin_data_info['tmp_path'] = s_tmp_path
if os.path.exists(p_info_file):
try:
old_info = json.loads(public.ReadFile(p_info_file))
plugin_data_info['old_version'] = old_info['versions']
except:
pass
except:
public.ExecShell("rm -rf " + s_tmp_path + '/*')
return public.get_error_object(plugin_name=self.__plugin_name)
plugin_data_info['install_opt'] = self.__install_opt
plugin_data_info['dependent'] = self.__check_dependent(plugin_data_info['name'])
plugin_data_info['update_msg'] = self.__get_update_msg(
plugin_data_info['name'], plugin_data_info['versions'])
not_check = self.not_cpu_or_bit(plugin_data_info)
if not_check:
if os.path.exists(s_tmp_path): shutil.rmtree(s_tmp_path)
return not_check
return plugin_data_info
# 检测是否为不支持的平台和系统位数
def not_cpu_or_bit(self, plugin_data_info):
'''
@name 检测是否为不支持的平台和系统位数
@author hwliang<2021-07-07>
@param plugin_data_info<dict> 插件信息数据
@return dict or None
'''
if 'not_os_bit' in plugin_data_info:
if public.get_sysbit() == int(plugin_data_info['not_os_bit']):
return public.returnMsg(
False,
'该应用不支持{}位系统'.format(plugin_data_info['not_os_bit']))
if 'not_cpu_type' in plugin_data_info:
if not plugin_data_info['not_cpu_type']: return None
machine = os.uname().machine
for c_type in plugin_data_info['not_cpu_type']:
c_type = c_type.lower()
result = public.returnMsg(
False, '该应用不支持{}平台,{}'.format(c_type, machine))
if c_type in ['arm', 'aarch64', 'aarch']:
if machine in ['aarch64', 'aarch']:
return result
elif c_type in ['mips', 'mips64', 'mips64el']:
if machine.find('mips') != -1:
return result
elif c_type in ['x86', 'x86-64']:
if machine in ['x86', 'x86-64']:
return result
return None
# 下载插件安装包
def __download_plugin(self, upgrade_plugin_name, upgrade_version):
'''
@name 下载插件包
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@param upgrade_version<string> 插件版本
@return string 保存路径
'''
pkey = '{}_pre'.format(upgrade_plugin_name)
pdata = public.get_user_info()
pdata['name'] = upgrade_plugin_name
pdata['version'] = upgrade_version
pdata['os'] = 'Linux'
pdata['environment_info'] = json.dumps(public.fetch_env_info(), ensure_ascii=False)
filename = '{}/{}.zip'.format(self.__tmp_path, upgrade_plugin_name)
if not os.path.exists(self.__tmp_path):
os.makedirs(self.__tmp_path, 384)
if not cache.get(pkey):
import config, socket
import requests.packages.urllib3.util.connection as urllib3_conn
_ip_type = config.config().get_request_iptype()
old_family = urllib3_conn.allowed_gai_family
if _ip_type == 'ipv4':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET
elif _ip_type == 'ipv6':
urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6
try:
download_res = requests.post(
self.__download_url,
pdata,
headers=public.get_requests_headers(),
timeout=(60, 1800),
stream=True)
except Exception as ex:
str_ex = str(ex)
if 'Name or service not known' in str_ex:
return public.returnMsg(False,
'下载软件包时DNS解析失败,请检查服务器网络配置是否正常: <p>Error: Name or service not known</p>')
elif 'Failed to establish a new connection' in str_ex:
return public.returnMsg(False,
'连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常: <p>Error: Failed to establish a new connection</p>')
elif 'Read timed out' in str_ex:
return public.returnMsg(False,
'连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常: <p>Error: Read timed out</p>')
elif 'Connection refused' in str_ex:
return public.returnMsg(False,
'连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常: <p>Error: Connection refused</p>')
elif 'Remote end closed connection without response' in str_ex:
return public.returnMsg(False,
'连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常: <p>Error: Remote end closed connection without response</p>')
else:
return public.returnMsg(False, '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常: <p>Error: {}</p>'.format(str_ex))
finally:
urllib3_conn.allowed_gai_family = old_family
try:
headers_total_size = int(download_res.headers['File-size'])
except:
try:
return json.loads(download_res.text).json()
except:
if download_res.text.find('<html>') != -1:
raise public.PanelError(
public.error_conn_cloud(download_res.text))
raise public.PanelError(download_res.text)
res_down_size = 0
res_chunk_size = 8192
last_time = time.time()
with open(filename, 'wb+') as with_res_f:
try:
for download_chunk in download_res.iter_content(
chunk_size=res_chunk_size):
if download_chunk:
with_res_f.write(download_chunk)
speed_last_size = len(download_chunk)
res_down_size += speed_last_size
res_start_time = time.time()
res_timeout = (res_start_time - last_time)
res_sec_speed = int(res_down_size / res_timeout)
pre_text = '{}/{}/{}'.format(res_down_size,
headers_total_size,
res_sec_speed)
cache.set(pkey, pre_text, 3600)
except Exception as ex:
ex_str = str(ex)
if "Read timed out" in ex_str:
return public.returnMsg(False, '软件包下载超时,请重试: {}'.format(ex_str))
if "No space left on device" in ex_str:
return public.returnMsg(False, "磁盘空间不足,请先清理后再试!")
finally:
with_res_f.close()
if cache.get(pkey): cache.delete(pkey)
if public.FileMd5(filename) != download_res.headers['Content-md5']:
return public.returnMsg(False, '软件包校验失败,请更新软件列表,并重试')
else:
while True:
time.sleep(1)
if not cache.get(pkey): break
return ''
return filename
# 获取插件安装包下载进度
def __get_download_speed(self, upgrade_plugin_name):
'''
@name 取插件下载进度
@author hwliang<2021-06-21>
@param upgrade_plugin_name<string> 插件名称
@return dict
'''
pkey = '{}_pre'.format(upgrade_plugin_name)
pre_text = cache.get(pkey)
if not pre_text:
return public.returnMsg(False, '指定进度信息不存在!')
result = {"status": True}
pre_tmp = pre_text.split('/')
result['down_size'], result['total_size'] = (int(pre_tmp[0]),
int(pre_tmp[1]))
result['down_pre'] = round(
result['down_size'] / result['total_size'] * 100, 1)
result['sec_speed'] = int(float(pre_tmp[2]))
result['need_time'] = int(
(result['total_size'] - result['down_size']) / result['sec_speed'])
return result
# 获取插件与授权信息
def __ensure_plugin_list_obtained(self, force: bool = False):
if force or not self.__plugin_list:
self.__plugin_list = public.load_soft_list(force)
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2019 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
import public,os
from BTPanel import request,abort,send_file
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -32,8 +32,8 @@ class ProjectController:
if args['mod_name'] in ['base']: return public.return_status_code(1000,'wrong call!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'Called method name cannot contain [ __ ] characters')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://www.bt.cn) All rights reserved.
# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: 沐落 <cjx@aapanel.com>
# | Author: lx
# | 消息推送管理
# | 对外方法 get_modules_list、install_module、uninstall_module、get_module_template、set_push_config、get_push_config、del_push_config
+16 -16
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -21,7 +21,7 @@ class panelRedirect:
#匹配目标URL的域名并返回
def GetToDomain(self,tourl):
if tourl:
rep = "https?://([\w\-\.]+)"
rep = r"https?://([\w\-\.]+)"
tu = re.search(rep, tourl)
return tu.group(1)
@@ -105,7 +105,7 @@ class panelRedirect:
if os.path.exists(ng_file):
ng_conf = public.readFile(ng_file)
if not p_conf:
rep = "#SSL-END(\n|.)*\/redirect\/.*\*.conf;"
rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;"
ng_conf = re.sub(rep, '#SSL-END', ng_conf)
public.writeFile(ng_file, ng_conf)
return
@@ -114,13 +114,13 @@ class panelRedirect:
sitenamelist.append(i["sitename"])
if get.sitename in sitenamelist:
rep = "include.*\/redirect\/.*\*.conf;"
rep = r"include.*\/redirect\/.*\*.conf;"
if not re.search(rep,ng_conf):
ng_conf = ng_conf.replace("#SSL-END","#SSL-END\n\t%s\n\t" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + "include " + ng_redirectfile + ";")
public.writeFile(ng_file,ng_conf)
else:
rep = "#SSL-END(\n|.)*\/redirect\/.*\*.conf;"
rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;"
ng_conf = re.sub(rep,'#SSL-END',ng_conf)
public.writeFile(ng_file, ng_conf)
@@ -134,18 +134,18 @@ class panelRedirect:
if os.path.exists(ap_file):
ap_conf = public.readFile(ap_file)
if p_conf == "[]":
rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid')
rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid')
ap_conf = re.sub(rep, '', ap_conf)
public.writeFile(ap_file, ap_conf)
return
if sitename in p_conf:
rep = "%s(\n|.)+IncludeOptional.*\/redirect\/.*conf" % public.get_msg_gettext('#referenced redirect rule')
rep = "%s(\n|.)+IncludeOptional.*\\/redirect\\/.*conf" % public.get_msg_gettext('#referenced redirect rule')
rep1 = "combined"
if not re.search(rep,ap_conf):
ap_conf = ap_conf.replace(rep1, rep1 + "\n\t%s" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') +"\n\tIncludeOptional " + ap_redirectfile)
public.writeFile(ap_file,ap_conf)
else:
rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid')
rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid')
ap_conf = re.sub(rep,'', ap_conf)
public.writeFile(ap_file, ap_conf)
@@ -167,7 +167,7 @@ class panelRedirect:
if self.__CheckRedirect(get.sitename,get.redirectname,is_error_page):
return public.return_msg_gettext(False, 'Specified redirect name already exists')
#检测目标URL格式
rep = "http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?"
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 'tourl' in get and not re.match(rep, get.tourl):
return public.returnMsg(False, 'Target URL format is wrong %s' + get.tourl)
@@ -180,12 +180,12 @@ class panelRedirect:
else:
if not get.redirectpath:
return public.return_msg_gettext(False, 'Please enter redirected path')
#repte = "[\?\=\[\]\)\(\*\&\^\%\$\#\@\!\~\`{\}\>\<\,\',\"]+"
#repte = "[\\?\\=\\[\\]\\)\\(\\*\\&\\^\\%\\$\\#\\@\\!\\~\\`{\\}\\>\\<\\,\',\"]+"
# 检测路径格式
if "/" not in get.redirectpath:
return public.return_msg_gettext(False, 'Path format is incorrect, the format is /xxx')
#if re.search(repte, get.redirectpath):
# return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]")
# return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]")
#检测域名是否已经存在配置文件
repeatdomain = self.__CheckRepeatDomain(get,action)
if repeatdomain:
@@ -384,7 +384,7 @@ class panelRedirect:
"""
if self.__firsturl:redirect_path=self.__firsturl
add_type=',R={}]'.format(str(r_type))
add_str='#REWRITE-START\n<IfModule mod_rewrite.c>\n RewriteEngine on\n RewriteCond %\{REQUEST_FILENAME\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n</IfModule>\n#REWRITE-END'
add_str='#REWRITE-START\n<IfModule mod_rewrite.c>\n RewriteEngine on\n RewriteCond %\\{REQUEST_FILENAME\\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n</IfModule>\n#REWRITE-END'
redirectname_md5 = self.__calc_md5(redirectname)
file_path= "%s/panel/vhost/apache/redirect/%s" % (self.setupPath,site_name)
public.ExecShell("mkdir -p %s" % file_path)
@@ -620,9 +620,9 @@ class panelRedirect:
old_conf = public.readFile(conf_path)
rep =""
if i == "nginx":
rep += "#301-START\n+[\s\w\:\/\.\;\$]+#301-END"
rep += "#301-START\n+[\\s\\w\\:\\/\\.\\;\\$]+#301-END"
if i == "apache":
rep += "#301-START[\n\<\>\w\.\s\^\*\$\/\[\]\(\)\:\,\=]+#301-END"
rep += "#301-START[\n\\<\\>\\w\\.\\s\\^\\*\\$\\/\\[\\]\\(\\)\\:\\,\\=]+#301-END"
conf = re.sub(rep, "", old_conf)
public.writeFile(conf_path, conf)
public.serviceReload()
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
+6 -7
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
@@ -34,7 +34,7 @@ class panelSSL:
__BINDURL = 'https://www.aapanel.com/api/user' # 获取token 获取官网token
# __BINDURL = 'https:///dev.aapanel.com/api/user' # 获取token 获取官网token
# __BINDURL = 'http://dev.aapanel.com/api/user' # 获取token 获取官网token
__CODEURL = 'https://api.bt.cn/Auth/GetBindCode' # 获取绑定验证码
__UPATH = 'data/userInfo.json'
@@ -66,7 +66,6 @@ class panelSSL:
else:
self.__userInfo = {}
# public.print_log('初始化 !!!!!!!!!!!!!!!!!!!用户信息: {}'.format(self.__userInfo))
try:
if self.__userInfo:
# 记录里没有这两个key
@@ -141,7 +140,7 @@ class panelSSL:
# public.print_log("写入用户信息 @@@@222 {}".format(self.__APIURL + '/user/login'))
result = json.loads(rtmp)
# public.print_log("写入用户信息 @@@@ {}".format(rtmp))
public.print_log("写入用户信息 @@@@ {}".format(result))
# public.print_log("写入用户信息 @@@@ {}".format(result))
if result['success']:
bind = 'data/bind.pl'
if os.path.exists(bind): os.remove(bind)
@@ -1056,7 +1055,7 @@ class panelSSL:
if cert_list and all_domain:
for cert in cert_list:
d_cert = ''
if re.match("^\*\..*", cert):
if re.match(r"^\*\..*", cert):
d_cert = cert.replace('*.', '')
for domain in all_domain:
if cert == domain:
+21 -21
View File
@@ -14,26 +14,26 @@ class safe:
ev = public.GetMsg("EXPLOITABLE_VULNERABILITIES")
dc = public.GetMsg("DANGEROUS_CITATION")
rulelist = [
{'msg':get_post_ev,'level':danger,'code':'(\$_(GET|POST|REQUEST)\[.{0,15}\]\s{0,10}\(\s{0,10}\$_(GET|POST|REQUEST)\[.{0,15}\]\))'},
{'msg':one_word_th,'level':high_risk,'code':'((eval|assert)(\s|\n)*\((\s|\n)*\$_(POST|GET|REQUEST)\[.{0,15}\]\))'},
{'msg':one_word_th,'level':high_risk,'code':'(eval(\s|\n)*\(base64_decode(\s|\n)*\((.|\n){1,200})'},
{'msg':webshell,'level':danger,'code':'(function\_exists\s*\(\s*[\'|\"](shell\_exec|system|popen|exec|proc\_open|passthru)+[\'|\"]\s*\))'},
{'msg':webshell,'level':danger,'code':'((exec|shell\_exec|passthru)+\s*\(\s*\$\_(\w+)\[(.*)\]\s*\))'},
{'msg':ev,'level':danger,'code':'(\$(\w+)\s*\(\s.chr\(\d+\)\))'},
{'msg':webshell,'level':danger,'code':'(\$(\w+)\s*\$\{(.*)\})'},
{'msg':get_post_cookie_ev,'level':danger,'code':'(\$(\w+)\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\s*\))'},
{'msg':get_post_cookie_ev,'level':danger,'code':'(\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\(\s*\$(.*)\))'},
{'msg':webshell,'level':danger,'code':'(\$\_\=(.*)\$\_)'},
{'msg':webshell,'level':danger,'code':'(\$(.*)\s*\((.*)\/e(.*)\,\s*\$\_(.*)\,(.*)\))'},
{'msg':webshell,'level':danger,'code':'(new com\s*\(\s*[\'|\"]shell(.*)[\'|\"]\s*\))'},
{'msg':webshell,'level':danger,'code':'(echo\s*curl\_exec\s*\(\s*\$(\w+)\s*\))'},
{'msg':public.GetMsg("HAZARDOUS_FILE_OPERATION_VULNERABILITIES"),'level':high_risk,'code':'((fopen|fwrite|fputs|file\_put\_contents)+\s*\((.*)\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\](.*)\))'},
{'msg':public.GetMsg("DANGEROUS_UPLOAD_VULNERABILITIES"),'level':danger,'code':'(\(\s*\$\_FILES\[(.*)\]\[(.*)\]\s*\,\s*\$\_(GET|POST|REQUEST)+\[(.*)\]\[(.*)\]\s*\))'},
{'msg':dc,'level':high_risk,'code':'(\$\_(\w+)(.*)(eval|assert|include|require|include\_once|require\_once)+\s*\(\s*\$(\w+)\s*\))'},
{'msg':dc,'level':high_risk,'code':'((include|require|include\_once|require\_once)+\s*\(\s*[\'|\"](\w+)\.(jpg|gif|ico|bmp|png|txt|zip|rar|htm|css|js)+[\'|\"]\s*\))'},
{'msg':ev,'level':danger,'code':'(eval\s*\(\s*\(\s*\$\$(\w+))'},
{'msg':one_word_th,'level':high_risk,'code':'((eval|assert|include|require|include\_once|require\_once|array\_map|array\_walk)+\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER|SESSION)+\[(.*)\]\s*\))'},
{'msg':one_word_th,'level':danger,'code':'(preg\_replace\s*\((.*)\(base64\_decode\(\$)'}
{'msg':get_post_ev,'level':danger,'code':r'(\$_(GET|POST|REQUEST)\[.{0,15}\]\s{0,10}\(\s{0,10}\$_(GET|POST|REQUEST)\[.{0,15}\]\))'},
{'msg':one_word_th,'level':high_risk,'code':'((eval|assert)(\\s|\n)*\\((\\s|\n)*\\$_(POST|GET|REQUEST)\\[.{0,15}\\]\\))'},
{'msg':one_word_th,'level':high_risk,'code':'(eval(\\s|\n)*\\(base64_decode(\\s|\n)*\\((.|\n){1,200})'},
{'msg':webshell,'level':danger,'code':'(function\\_exists\\s*\\(\\s*[\'|\"](shell\\_exec|system|popen|exec|proc\\_open|passthru)+[\'|\"]\\s*\\))'},
{'msg':webshell,'level':danger,'code':r'((exec|shell\_exec|passthru)+\s*\(\s*\$\_(\w+)\[(.*)\]\s*\))'},
{'msg':ev,'level':danger,'code':r'(\$(\w+)\s*\(\s.chr\(\d+\)\))'},
{'msg':webshell,'level':danger,'code':r'(\$(\w+)\s*\$\{(.*)\})'},
{'msg':get_post_cookie_ev,'level':danger,'code':r'(\$(\w+)\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\s*\))'},
{'msg':get_post_cookie_ev,'level':danger,'code':r'(\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\(\s*\$(.*)\))'},
{'msg':webshell,'level':danger,'code':r'(\$\_\=(.*)\$\_)'},
{'msg':webshell,'level':danger,'code':r'(\$(.*)\s*\((.*)\/e(.*)\,\s*\$\_(.*)\,(.*)\))'},
{'msg':webshell,'level':danger,'code':'(new com\\s*\\(\\s*[\'|\"]shell(.*)[\'|\"]\\s*\\))'},
{'msg':webshell,'level':danger,'code':r'(echo\s*curl\_exec\s*\(\s*\$(\w+)\s*\))'},
{'msg':public.GetMsg("HAZARDOUS_FILE_OPERATION_VULNERABILITIES"),'level':high_risk,'code':r'((fopen|fwrite|fputs|file\_put\_contents)+\s*\((.*)\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\](.*)\))'},
{'msg':public.GetMsg("DANGEROUS_UPLOAD_VULNERABILITIES"),'level':danger,'code':r'(\(\s*\$\_FILES\[(.*)\]\[(.*)\]\s*\,\s*\$\_(GET|POST|REQUEST)+\[(.*)\]\[(.*)\]\s*\))'},
{'msg':dc,'level':high_risk,'code':r'(\$\_(\w+)(.*)(eval|assert|include|require|include\_once|require\_once)+\s*\(\s*\$(\w+)\s*\))'},
{'msg':dc,'level':high_risk,'code':'((include|require|include\\_once|require\\_once)+\\s*\\(\\s*[\'|\"](\\w+)\\.(jpg|gif|ico|bmp|png|txt|zip|rar|htm|css|js)+[\'|\"]\\s*\\))'},
{'msg':ev,'level':danger,'code':r'(eval\s*\(\s*\(\s*\$\$(\w+))'},
{'msg':one_word_th,'level':high_risk,'code':r'((eval|assert|include|require|include\_once|require\_once|array\_map|array\_walk)+\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER|SESSION)+\[(.*)\]\s*\))'},
{'msg':one_word_th,'level':danger,'code':r'(preg\_replace\s*\((.*)\(base64\_decode\(\$)'}
]
ruleFile = '/www/server/panel/data/ruleList.conf';
@@ -101,7 +101,7 @@ class safe:
def checkPHPINI(self):
setupPath = '/www/server';
phps = public.get_php_versions()
rep = "disable_functions\s*=\s*(.+)\n"
rep = "disable_functions\\s*=\\s*(.+)\n"
defs = ['passthru','exec','system','chroot','chgrp','chown','shell_exec','popen','ini_alter','ini_restore','dl','openlog','syslog','readlink','symlink','popepassthru']
data = []
for phpv in phps:
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
@@ -32,8 +32,8 @@ class SafeController:
if args['mod_name'] in ['base']: return public.return_status_code(1000,'wrong call!')
public.exists_args('def_name,mod_name',args)
if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w')
except:
return public.get_error_object()
# 参数处理
+1 -1
View File
@@ -2,7 +2,7 @@
# +-------------------------------------------------------------------
# | version :1.0
# +-------------------------------------------------------------------
# | Author: 梁凯强 <1249648969@qq.com>
# | Author: 梁凯强 <1249648969@aapanel.com>
# +-------------------------------------------------------------------
# | 快速检索
# +--------------------------------------------------------------------
+213 -188
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,10 +1,10 @@
#coding: utf-8
#-------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
#-------------------------------------------------------------------
#------------------------------
+5 -5
View File
@@ -1,10 +1,10 @@
#coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2019-2099 宝塔软件(http://bt.cn) All rights reserved.
# Copyright (c) 2019-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: hwliang <hwl@bt.cn>
# Author: hwliang <hwl@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
@@ -369,7 +369,7 @@ class bt_task:
self.install_rar()
pass_opt = '-p-'
if password:
password = password.replace("&","\&").replace('"','\"')
password = password.replace("&",r"\&").replace('"','\"')
pass_opt = '-p"{}"'.format(password)
public.ExecShell(rar_file + ' x '+ pass_opt +' -u -y "' + sfile + '" "' + dfile + '" &> ' + log_file)
@@ -571,7 +571,7 @@ class bt_task:
public.ExecShell("sed -i '/password=/d' " + my_cnf)
if act:
mycnf = public.readFile(my_cnf)
rep = "\[mysqldump\]\nuser=root"
rep = "\\[mysqldump\\]\nuser=root"
sea = "[mysqldump]\n"
subStr = sea + "user=root\npassword=\"" + root + "\"\n"
mycnf = mycnf.replace(sea, subStr)
+2 -2
View File
@@ -1,8 +1,8 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <2020-05-18>
# +-------------------------------------------------------------------
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/python
#coding: utf-8
# Author: lkqiang<lkq@bt.cn>
# Author: lkqiang<lkq@aapanel.com>
# panelWaf.py
# code: 面板基础安全类
# +-------------------------------------------------------------------
+5 -5
View File
@@ -1,8 +1,8 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: hwliang <2020-08-04>
# +-------------------------------------------------------------------
@@ -227,7 +227,7 @@ class panelWarning:
if not os.path.exists(self.new_vul_list):
if zip_file != '':
downfile = self.__path+'/'+zip_file
public.downloadFile("/safe_warning/{}".format(public.get_url(),zip_file), downfile)
public.downloadFile("{}/safe_warning/{}".format(public.get_url(), zip_file), downfile)
o, e = public.ExecShell("unzip -o {} -d {}".format(downfile, self.__path))
# 解压报错
if e != "":
@@ -1433,9 +1433,9 @@ if __name__ == "__main__":
# #coding: utf-8
# # +-------------------------------------------------------------------
# # | 宝塔Linux面板
# # | aaPanel
# # +-------------------------------------------------------------------
# # | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# # | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# # +-------------------------------------------------------------------
# # | Author: hwliang <2020-08-04>
# # +-------------------------------------------------------------------
+3 -3
View File
@@ -1,10 +1,10 @@
<?php
// +-------------------------------------------------------------------
// | 宝塔Linux面板
// | aaPanel
// +-------------------------------------------------------------------
// | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
// | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
// +-------------------------------------------------------------------
// | Author: hwliang <hwl@bt.cn>
// | Author: hwliang <hwl@aapanel.com>
// +-------------------------------------------------------------------
// +-------------------------------------------------------------------
+6 -6
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# -------------------------------------------------------------------
# 宝塔Linux面板
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zhwwen <zhw@bt.cn>
# Author: zhwwen <zhw@aapanel.com>
# -------------------------------------------------------------------
#
# ------------------------------
@@ -127,7 +127,7 @@ class panel_restore:
def restore_website_backup(self,args):
"""
@name 恢复站点文件
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@parma file_name 备份得文件名
@parma site_id 网站id
"""
@@ -171,7 +171,7 @@ class panel_restore:
def get_progress(self, get):
"""
@name 获取进度日志
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
"""
# result = public.GetNumLines(self._progress_file, 20)
result = public.ExecShell('tail -n 20 {}'.format(self._progress_file))[0]
@@ -183,7 +183,7 @@ class panel_restore:
def restore_db_backup(self,args):
"""
@name 恢复站点文件
@author zhwen<zhw@bt.cn>
@author zhwen<zhw@aapanel.com>
@parma file_name 备份得文件名 /www/backup/database/db_test_com_20200817_112722.sql.gz|Google Drive|db_test_com_20200817_112722.sql.gz
@parma obj_name 数据库名
"""
+1 -1
View File
@@ -2,7 +2,7 @@
# +-------------------------------------------------------------------
# | version :1.0
# +-------------------------------------------------------------------
# | Author: 梁凯强 <1249648969@qq.com>
# | Author: 梁凯强 <1249648969@aapanel.com>
# +-------------------------------------------------------------------
# | 快速检索
# +--------------------------------------------------------------------
+38 -8
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x3
# | aaPanel x3
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: zhw <zhw@bt.cn>
# | Author: zhw <zhw@aapanel.com>
# +-------------------------------------------------------------------
import public
@@ -59,12 +59,42 @@ class panel_telegram_bot:
# 使用tg机器人发送消息
def send_by_tg_bot(self,content,parse_mode=None):
"""
@author hezhihong
"""
"parse_mode 消息格式 html/markdown/markdownv2"
content = self.process_character(content)
# content = self.process_character(content)
conf = self.get_tg_conf()
try:
bot = telegram.Bot(conf['bot_token'])
result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
return result
result= self.send_message(conf['bot_token'],conf['my_id'],content)
if not result['status']:
return False
return True
except:
return False
return False
def send_message(self, bot_token, chat_id, msg):
"""
tg发送信息
@msg 消息正文
@author hezhihong
"""
msg = self.process_character(msg)
url = 'https://api.telegram.org/bot{}/sendMessage'.format(bot_token)
data = {
'chat_id': chat_id,
'text': msg,
'parse_mode':'MarkdownV2',
}
try:
import requests
response = requests.post(url, json=data)
if response.status_code == 200:
return public.returnMsg(True,0,response.json())
else:
return public.returnMsg(False,json.loads(response.text))
except Exception as e:
return public.returnMsg(False,str(e))
+13 -13
View File
@@ -1,10 +1,10 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# | aaPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lkqiang <lkq@bt.cn>
# | Author: lkqiang <lkq@aapanel.com>
# +-------------------------------------------------------------------
# +--------------------------------------------------------------------
# | 密码管理
@@ -88,7 +88,7 @@ class password:
# 开启密码登陆
def SetPassword(self, get):
ssh_password = '\n#?PasswordAuthentication\s\w+'
ssh_password = '\n#?PasswordAuthentication\\s\\w+'
file = public.readFile('/etc/ssh/sshd_config')
if len(re.findall(ssh_password, file)) == 0:
file_result = file + '\nPasswordAuthentication yes'
@@ -114,15 +114,15 @@ class password:
public.ExecShell("ssh-keygen -t %s -P '' -f ~/.ssh/id_rsa |echo y" % type)
if os.path.exists(file[0]):
public.ExecShell('cat %s >%s && chmod 600 %s' % (file[0], file[-1], file[-1]))
rec = '\n#?RSAAuthentication\s\w+'
rec2 = '\n#?PubkeyAuthentication\s\w+'
rec = '\n#?RSAAuthentication\\s\\w+'
rec2 = '\n#?PubkeyAuthentication\\s\\w+'
file = public.readFile('/etc/ssh/sshd_config')
if len(re.findall(rec, file)) == 0: file = file + '\nRSAAuthentication yes'
if len(re.findall(rec2, file)) == 0: file = file + '\nPubkeyAuthentication yes'
file_ssh = re.sub(rec, '\nRSAAuthentication yes', file)
file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh)
if ssh == 'no':
ssh_password = '\n#?PasswordAuthentication\s\w+'
ssh_password = '\n#?PasswordAuthentication\\s\\w+'
if len(re.findall(ssh_password, file_result)) == 0:
file_result = file_result + '\nPasswordAuthentication no'
else:
@@ -137,8 +137,8 @@ class password:
# 关闭sshkey
def StopKey(self, get):
file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys']
rec = '\n#?RSAAuthentication\s\w+'
rec2 = '\n#?PubkeyAuthentication\s\w+'
rec = '\n#?RSAAuthentication\\s\\w+'
rec2 = '\n#?PubkeyAuthentication\\s\\w+'
file = public.readFile('/etc/ssh/sshd_config')
file_ssh = re.sub(rec, '\n#RSAAuthentication no', file)
file_result = re.sub(rec2, '\n#PubkeyAuthentication no', file_ssh)
@@ -151,9 +151,9 @@ class password:
def GetConfig(self, get):
result = {}
file = public.readFile('/etc/ssh/sshd_config')
rec = '\n#?RSAAuthentication\s\w+'
pubkey = '\n#?PubkeyAuthentication\s\w+'
ssh_password = '\nPasswordAuthentication\s\w+'
rec = '\n#?RSAAuthentication\\s\\w+'
pubkey = '\n#?PubkeyAuthentication\\s\\w+'
ssh_password = '\nPasswordAuthentication\\s\\w+'
ret = re.findall(ssh_password, file)
if not ret:
result['password'] = 'no'
@@ -183,7 +183,7 @@ class password:
# 关闭密码方式
def StopPassword(self, get):
file = public.readFile('/etc/ssh/sshd_config')
ssh_password = '\n#?PasswordAuthentication\s\w+'
ssh_password = '\n#?PasswordAuthentication\\s\\w+'
file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file)
self.Wirte('/etc/ssh/sshd_config', file_result)
self.RestartSsh()

Some files were not shown because too many files have changed in this diff Show More