diff --git a/.gitignore b/.gitignore
index 466df64ae..87a711097 100644
--- a/.gitignore
+++ b/.gitignore
@@ -162,7 +162,6 @@ plugins/my_*
plugins/l2tp
plugins/openlitespeed
plugins/tamper_proof
-plugins/tamper_proof_*
plugins/cryptocurrency_trade
plugins/op_load_balance
plugins/gdrive
diff --git a/plugins/system_safe/README.md b/plugins/system_safe/README.md
deleted file mode 100644
index 18671a83b..000000000
--- a/plugins/system_safe/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# plugins_system_safe
-
-系统加固插件
-
-# DEBUG
-```
-
-https://code_hosting_007.midoks.me/midoks/plugins_system_safe
-
-ln -s /www/git/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
-
-ln -s /www/wwwroot/midoks/plugins_system_safe /www/server/mdserver-web/plugins/system_safe
-
-
-python3 /www/server/mdserver-web/plugins/system_safe/index.py bg_start
-
-systemctl daemon-reload
-```
-
diff --git a/plugins/tamper_proof_py/conf/config.json b/plugins/tamper_proof_py/conf/config.json
new file mode 100644
index 000000000..8a372031a
--- /dev/null
+++ b/plugins/tamper_proof_py/conf/config.json
@@ -0,0 +1,5 @@
+{
+ "open": true,
+ "excludePath": [ "cache", "threadcache", "log", "logs", "config", "runtime", "temp","tmp","caches","tmps"],
+ "protectExt": [ "php", "html", "htm", "shtml", "tpl", "js", "css", "jsp", "do" ]
+}
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/ico.png b/plugins/tamper_proof_py/ico.png
new file mode 100644
index 000000000..3dede4e91
Binary files /dev/null and b/plugins/tamper_proof_py/ico.png differ
diff --git a/plugins/tamper_proof_py/index.html b/plugins/tamper_proof_py/index.html
new file mode 100755
index 000000000..cb5de195f
--- /dev/null
+++ b/plugins/tamper_proof_py/index.html
@@ -0,0 +1,1163 @@
+
+
+
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/index.py b/plugins/tamper_proof_py/index.py
new file mode 100755
index 000000000..446dee0c3
--- /dev/null
+++ b/plugins/tamper_proof_py/index.py
@@ -0,0 +1,628 @@
+# coding:utf-8
+
+import sys
+import io
+import os
+import time
+import json
+import re
+import psutil
+from datetime import datetime
+
+sys.dont_write_bytecode = True
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+
+app_debug = False
+if mw.isAppleSystem():
+ app_debug = True
+
+
+class App:
+
+ __total = 'total.json'
+ __sites = []
+
+ def __init__(self):
+ pass
+
+ def getPluginName(self):
+ return 'tamper_proof_py'
+
+ def getPluginDir(self):
+ return mw.getPluginDir() + '/' + self.getPluginName()
+
+ def getServerDir(self):
+ return mw.getServerDir() + '/' + self.getPluginName()
+
+ def getInitDFile(self):
+ if app_debug:
+ return '/tmp/' + self.getPluginName()
+ return '/etc/init.d/' + self.getPluginName()
+
+ def getInitDTpl(self):
+ path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl"
+ return path
+
+ def getArgs(self):
+ args = sys.argv[2:]
+ tmp = {}
+ args_len = len(args)
+
+ if args_len == 1:
+ t = args[0].strip('{').strip('}')
+ if t.strip() == '':
+ tmp = []
+ else:
+ t = t.split(':')
+ tmp[t[0]] = t[1]
+ elif args_len > 1:
+ for i in range(len(args)):
+ t = args[i].split(':')
+ tmp[t[0]] = t[1]
+ return tmp
+
+ def checkArgs(self, data, ck=[]):
+ for i in range(len(ck)):
+ if not ck[i] in data:
+ return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
+ return (True, mw.returnJson(True, 'ok'))
+
+ def getTotal(self, siteName=None, day=None):
+ defaultTotal = {"total": 0, "delete": 0,
+ "create": 0, "modify": 0, "move": 0}
+ if siteName:
+ total = {}
+ total_path = self.getServerDir() + '/sites/' + siteName + '/' + self.__total
+ if not os.path.exists(total_path):
+ total['site'] = defaultTotal
+ else:
+ total_data = mw.readFile(total_path)
+ if total_data['site']:
+ total['site'] = json.loads(total_data['site'])
+ else:
+ total['site'] = defaultTotal
+
+ if not day:
+ day = time.strftime("%Y-%m-%d", time.localtime())
+ total_day_path = self.getServerDir() + '/sites/' + siteName + '/day/total.json'
+ if not os.path.exists(total_day_path):
+ total['day'] = defaultTotal
+ else:
+ total['day'] = mw.readFile(total_day_path)
+ if total['day']:
+ total['day'] = json.loads(total['day'])
+ else:
+ total['day'] = defaultTotal
+ else:
+ filename = self.getServerDir() + '/sites/' + self.__total
+ if os.path.exists(filename):
+ total = json.loads(mw.readFile(filename))
+ else:
+ total = defaultTotal
+ return total
+
+ def getSites(self):
+ sites_path = self.getServerDir() + '/sites.json'
+ t = mw.readFile(sites_path)
+ if not os.path.exists(sites_path) or not t:
+ mw.writeFile(sites_path, '[]')
+ data = json.loads(mw.readFile(sites_path))
+
+ is_write = False
+ rm_keys = ['lock', 'bak_open']
+ for i in data:
+ i_keys = i.keys()
+ if not 'open' in i_keys:
+ i['open'] = False
+ for o in rm_keys:
+ if o in i_keys:
+ if i[o]:
+ i['open'] = True
+ i.pop(o)
+ is_write = True
+ if is_write:
+ mw.writeFile(sites_path, json.dumps(data))
+
+ self.__sites = data
+ return data
+
+ def writeSites(self, data):
+ mw.writeFile(self.getServerDir() + '/sites.json', json.dumps(data))
+ # mw.ExecShell('/etc/init.d/bt_tamper_proof reload')
+
+ def __getFind(self, siteName):
+ data = self.getSites()
+ for siteInfo in data:
+ if siteName == siteInfo['siteName']:
+ return siteInfo
+ return None
+
+ def writeLog(self, log):
+ mw.writeLog('防篡改程序', log)
+
+ def saveSiteConfig(self, siteInfo):
+ data = self.getSites()
+ for i in range(len(data)):
+ if data[i]['siteName'] != siteInfo['siteName']:
+ continue
+ data[i] = siteInfo
+ break
+ self.writeSites(data)
+
+ def syncSites(self):
+ data = self.getSites()
+ sites = mw.M('sites').field('name,path').select()
+
+ config_path = self.getPluginDir() + '/conf/config.json'
+ config = json.loads(mw.readFile(config_path))
+ names = []
+ n = 0
+
+ # print(config)
+ for siteTmp in sites:
+ names.append(siteTmp['name'])
+ siteInfo = self.__getFind(siteTmp['name'])
+ if siteInfo:
+ if siteInfo['path'] != siteTmp['path']:
+ siteInfo['path'] = siteTmp['path']
+ self.saveSiteConfig(siteInfo)
+ data = self.getSites()
+ continue
+ siteInfo = {}
+ siteInfo['siteName'] = siteTmp['name']
+ siteInfo['path'] = siteTmp['path']
+ siteInfo['open'] = False
+ siteInfo['excludePath'] = config['excludePath']
+ siteInfo['protectExt'] = config['protectExt']
+ data.append(siteInfo)
+ n += 1
+
+ newData = []
+ for siteInfoTmp in data:
+ if siteInfoTmp['siteName'] in names:
+ newData.append(siteInfoTmp)
+ else:
+ mw.execShell("rm -rf " + self.getServerDir() +
+ '/sites/' + siteInfoTmp['siteName'])
+ n += 1
+ if n > 0:
+ self.writeSites(newData)
+ self.__sites = None
+
+ def initDreplace(self):
+ file_tpl = self.getInitDTpl()
+ service_path = self.getServerDir()
+
+ initD_path = service_path + '/init.d'
+ if not os.path.exists(initD_path):
+ os.mkdir(initD_path)
+
+ # init.d
+ file_bin = initD_path + '/' + self.getPluginName()
+ if not os.path.exists(file_bin):
+ # initd replace
+ content = mw.readFile(file_tpl)
+ content = content.replace('{$SERVER_PATH}', service_path)
+ mw.writeFile(file_bin, content)
+ mw.execShell('chmod +x ' + file_bin)
+
+ # systemd
+ # /usr/lib/systemd/system
+ systemDir = mw.systemdCfgDir()
+ systemService = systemDir + '/tamper_proof_py.service'
+ systemServiceTpl = self.getPluginDir() + '/init.d/tamper_proof_py.service.tpl'
+ if os.path.exists(systemDir) and not os.path.exists(systemService):
+ se_content = mw.readFile(systemServiceTpl)
+ se_content = se_content.replace('{$SERVER_PATH}', service_path)
+ mw.writeFile(systemService, se_content)
+ mw.execShell('systemctl daemon-reload')
+
+ return file_bin
+
+ def getDays(self, path):
+ days = []
+ if not os.path.exists(path):
+ os.makedirs(path)
+ for dirname in os.listdir(path):
+ if dirname == '..' or dirname == '.' or dirname == 'total.json':
+ continue
+ if not os.path.isdir(path + '/' + dirname):
+ continue
+ days.append(dirname)
+ days = sorted(days, reverse=True)
+ return days
+
+ def status(self):
+ '''
+ 状态
+ '''
+ initd_file = self.getServerDir() + '/init.d/' + self.getPluginName()
+ if not os.path.exists(initd_file):
+ return 'stop'
+ cmd = initd_file + ' status|grep already'
+ data = mw.execShell(cmd)
+ if data[0] != '':
+ return 'start'
+ return 'stop'
+
+ def tpOp(self, method):
+ file = self.initDreplace()
+ if not mw.isAppleSystem():
+ cmd = 'systemctl ' + method + ' ' + self.getPluginName()
+ data = mw.execShell(cmd)
+ if data[1] == '':
+ return mw.returnJson(True, '操作成功')
+ return mw.returnJson(False, '操作失败')
+
+ cmd = file + ' ' + method
+ data = mw.execShell(cmd)
+ if data[1] == '':
+ return mw.returnJson(True, '操作成功')
+ return mw.returnJson(False, '操作失败')
+
+ def start(self):
+ return self.tpOp('start')
+
+ def restart(self):
+ return self.tpOp('restart')
+
+ def service_admin(self):
+ if mw.isAppleSystem():
+ return mw.returnJson(False, '仅支持Linux!')
+
+ args = self.getArgs()
+ check = self.checkArgs(args, ['serviceStatus'])
+ if not check[0]:
+ return check[1]
+
+ method = args['serviceStatus']
+ return self.tpOp(method)
+
+ def initd_status(self):
+ if mw.isAppleSystem():
+ return "Apple Computer does not support"
+ shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % (
+ self.getPluginName())
+ data = mw.execShell(shell_cmd)
+ if data[0] == '':
+ return 'fail'
+ return 'ok'
+
+ def initd_install(self):
+ if mw.isAppleSystem():
+ return "Apple Computer does not support"
+
+ mw.execShell('systemctl enable ' + self.getPluginName())
+ return 'ok'
+
+ def initd_uninstall(self):
+ if mw.isAppleSystem():
+ return "Apple Computer does not support"
+
+ mw.execShell('systemctl disable ' + self.getPluginName())
+ return 'ok'
+
+ def set_site_status(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ siteInfo = self.__getFind(siteName)
+ if not siteInfo:
+ return mw.returnJson(False, '指定站点不存在!')
+ try:
+ siteInfo['open'] = not siteInfo['open']
+ except:
+ siteInfo['open'] = not siteInfo['open']
+
+ m_logs = {True: '开启', False: '关闭'}
+ self.writeLog('%s站点[%s]防篡改保护' %
+ (m_logs[siteInfo['open']], siteInfo['siteName']))
+ self.siteReload(siteInfo)
+ self.saveSiteConfig(siteInfo)
+ self.restart()
+ return mw.returnJson(True, '设置成功!')
+
+ def get_run_logs(self):
+ log_file = self.getServerDir() + '/service.log'
+ return mw.returnJson(True, mw.getLastLine(log_file, 200))
+
+ # 取文件指定尾行数
+ def getNumLines(self, path, num, p=1):
+ pyVersion = sys.version_info[0]
+ try:
+ import cgi
+ 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 = []
+ 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:]
+ try:
+ data.append(json.loads(cgi.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:
+ if type(t_buf) == bytes:
+ t_buf = t_buf.decode('utf-8')
+ buf = t_buf + buf
+ fp.seek(-to_read, 1)
+ if pos - to_read == 0:
+ buf = "\n" + buf
+ if not b:
+ break
+ fp.close()
+ except:
+ return []
+ if len(data) >= 2000:
+ arr = []
+ for d in data:
+ arr.insert(0, json.dumps(d))
+ mw.writeFile(path, "\n".join(arr))
+ return data
+
+ def get_safe_logs(self):
+
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+
+ data = {}
+ path = self.getPluginDir() + '/sites/' + siteName + '/day'
+ data['days'] = self.getDays(path)
+
+ if not data['days']:
+ data['logs'] = []
+ else:
+ p = 1
+ if hasattr(args, 'p'):
+ p = args['p']
+
+ day = data['days'][0]
+ if hasattr(args, 'day'):
+ day = args['day']
+ data['get_day'] = day
+ logs_path = path + '/' + day + '/logs.json'
+ data['logs'] = self.getNumLines(logs_path, 2000, int(p))
+ return mw.returnJson(True, 'ok', data)
+
+ def get_site_find(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ data = self.__getFind(siteName)
+ return mw.returnJson(True, 'ok', data)
+
+ def siteReload(self, siteInfo):
+ cmd = "python3 {} {}".format(
+ mw.getPluginDir() + '/tamper_proof_service.py unlock', siteInfo['path'])
+ mw.execShell(cmd)
+ tip_file = mw.getServerDir() + '/tips/' + siteInfo['siteName'] + '.pl'
+ if os.path.exists(tip_file):
+ os.remove(tip_file)
+
+ def remove_protect_ext(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName', 'protectExt'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ protectExt = args['protectExt'].strip()
+
+ siteInfo = self.__getFind(siteName)
+
+ if not siteInfo:
+ return mw.returnJson(False, '指定站点不存在!')
+ if not protectExt:
+ return mw.returnJson(False, '被删除的保护列表不能为空')
+
+ for protectExt in protectExt.split(','):
+ if not protectExt in siteInfo['protectExt']:
+ continue
+ siteInfo['protectExt'].remove(protectExt)
+ self.writeLog('站点[%s]从受保护列表中删除[.%s]' %
+ (siteInfo['siteName'], protectExt))
+ self.siteReload(siteInfo)
+ self.saveSiteConfig(siteInfo)
+ return mw.returnJson(True, '删除成功!')
+
+ def add_protect_ext(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName', 'protectExt'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ protectExt = args['protectExt'].strip()
+
+ siteInfo = self.__getFind(siteName)
+ if not siteInfo:
+ return mw.returnJson(False, '指定站点不存在!')
+ protectExt = protectExt.lower()
+ for protectExt in protectExt.split("\n"):
+ if protectExt[0] == '/':
+ if os.path.isdir(protectExt):
+ continue
+ if protectExt in siteInfo['protectExt']:
+ continue
+ siteInfo['protectExt'].insert(0, protectExt)
+ self.writeLog('站点[%s]添加文件类型或文件名[.%s]到受保护列表' %
+ (siteInfo['siteName'], protectExt))
+ self.siteReload(siteInfo)
+ self.saveSiteConfig(siteInfo)
+ return mw.returnJson(True, '添加成功!')
+
+ def add_excloud(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName', 'excludePath'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ excludePath = args['excludePath'].strip()
+ siteInfo = self.__getFind(siteName)
+ if not siteInfo:
+ return mw.returnJson(False, '指定站点不存在!')
+
+ if not excludePath:
+ return mw.returnJson(False, '排除内容不能为空')
+
+ for excludePath in excludePath.split('\n'):
+ if not excludePath:
+ continue
+ if excludePath.find('/') != -1:
+ if not os.path.exists(excludePath):
+ continue
+ excludePath = excludePath.lower()
+ if excludePath[-1] == '/':
+ excludePath = excludePath[:-1]
+ if excludePath in siteInfo['excludePath']:
+ continue
+ siteInfo['excludePath'].insert(0, excludePath)
+ self.writeLog('站点[%s]添加排除目录名[%s]到排除列表' %
+ (siteInfo['siteName'], excludePath))
+
+ self.siteReload(siteInfo)
+ self.saveSiteConfig(siteInfo)
+ return mw.returnJson(True, '添加成功!')
+
+ def remove_excloud(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteName', 'excludePath'])
+ if not check[0]:
+ return check[1]
+
+ siteName = args['siteName']
+ siteInfo = self.__getFind(siteName)
+ excludePath = args['excludePath'].strip()
+ if excludePath == '':
+ return mw.returnJson(False, '排除文件或目录不能为空')
+ if not siteInfo:
+ return mw.returnJson(False, '指定站点不存在!')
+
+ for excludePath in excludePath.split(','):
+ if not excludePath:
+ continue
+ if not excludePath in siteInfo['excludePath']:
+ continue
+ siteInfo['excludePath'].remove(excludePath)
+ self.writeLog('站点[%s]从排除列表中删除目录名[%s]' %
+ (siteInfo['siteName'], excludePath))
+ self.siteReload(siteInfo)
+ self.saveSiteConfig(siteInfo)
+ return mw.returnJson(True, '删除成功!')
+
+ def sim_test(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['path'])
+ if not check[0]:
+ return check[1]
+
+ path = args['path'].strip()
+ if not os.path.exists(path):
+ return mw.returnJson(False, "此目录不存在")
+
+ # 判断是否安装php
+ import site_api
+ php_version = site_api.site_api().getPhpVersion()
+ if not php_version:
+ return mw.returnJson(False, "未安装PHP测试失败")
+
+ php_path = '/www/server/php/' + php_version[1]['version'] + '/bin/php'
+ php_name = path + "/" + str(int(time.time())) + ".php"
+ if os.path.exists(php_name):
+ mw.execShell("rm -rf %s" % php_name)
+ # 写入
+ cmd = php_path + \
+ " -r \"file_put_contents('{}','{}');\"".format(php_name, php_name)
+ mw.execShell(cmd)
+ time.sleep(0.5)
+ if os.path.exists(php_name):
+ if os.path.exists(php_name):
+ mw.execShell("rm -rf %s" % php_name)
+ return mw.returnJson(False, "拦截失败,可能未开启防篡改")
+ return mw.returnJson(True, "拦截成功")
+
+ def set_site_status_all(self):
+ args = self.getArgs()
+ check = self.checkArgs(args, ['siteNames', 'siteState'])
+ if not check[0]:
+ return check[1]
+
+ sites = self.getSites()
+ siteState = True if args['siteState'] == '1' else False
+ siteNames = json.loads(args['siteNames'])
+ m_logs = {True: '开启', False: '关闭'}
+ for i in range(len(sites)):
+ if sites[i]['siteName'] in siteNames:
+ sites[i]['open'] = siteState
+ self.writeLog('%s站点[%s]防篡改保护' %
+ (m_logs[siteState], sites[i]['siteName']))
+ self.writeSites(sites)
+ return mw.returnJson(True, '批量设置成功')
+
+ def get_index(self):
+ self.syncSites()
+ args = self.getArgs()
+ day = None
+ if 'day' in args:
+ day = args['day']
+
+ ser_status = self.status()
+ ser_status_bool = False
+ if ser_status == 'start':
+ ser_status_bool = True
+ data = {}
+ data['open'] = ser_status_bool
+ data['total'] = self.getTotal()
+ data['sites'] = self.getSites()
+ for i in range(len(data['sites'])):
+ data['sites'][i]['total'] = self.getTotal(
+ data['sites'][i]['siteName'], day)
+ return mw.returnJson(True, 'ok', data)
+
+ def get_speed(self):
+ print("12")
+
+
+if __name__ == "__main__":
+ func = sys.argv[1]
+ classApp = App()
+ try:
+ data = eval("classApp." + func + "()")
+ print(data)
+ except Exception as e:
+ print(mw.getTracebackInfo())
diff --git a/plugins/tamper_proof_py/info.json b/plugins/tamper_proof_py/info.json
new file mode 100644
index 000000000..4ed8adda4
--- /dev/null
+++ b/plugins/tamper_proof_py/info.json
@@ -0,0 +1,15 @@
+{
+ "title": "网站防篡改程序[PY]",
+ "tip": "lib",
+ "name": "tamper_proof_py",
+ "type": "soft",
+ "ps": "事件型防篡改程序,可有效保护网站重要文件不被木马篡改",
+ "versions": "1.0",
+ "shell": "install.sh",
+ "checks": "server/tamper_proof_py",
+ "path": "server/tamper_proof_py",
+ "author": "midoks",
+ "home": "",
+ "date":"2022-01-18",
+ "pid":"4"
+}
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl
new file mode 100644
index 000000000..75d25b072
--- /dev/null
+++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.service.tpl
@@ -0,0 +1,15 @@
+[Unit]
+Description=tamper_proof_py server daemon
+After=network.target
+
+[Service]
+Type=forking
+ExecStart={$SERVER_PATH}/init.d/tamper_proof_py start
+ExecStop={$SERVER_PATH}/init.d/tamper_proof_py stop
+ExecReload={$SERVER_PATH}/init.d/tamper_proof_py reload
+ExecRestart={$SERVER_PATH}/init.d/tamper_proof_py restart
+KillMode=process
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl
new file mode 100644
index 000000000..ae38bab4c
--- /dev/null
+++ b/plugins/tamper_proof_py/init.d/tamper_proof_py.tpl
@@ -0,0 +1,95 @@
+#!/bin/bash
+# chkconfig: 2345 55 25
+# description:tamper_proof_service
+
+### BEGIN INIT INFO
+# Provides: tamper_proof_service
+# Required-Start: $all
+# Required-Stop: $all
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: starts tamper_proof_service
+# Description: starts the tamper_proof_service
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+mw_path={$SERVER_PATH}
+rootPath=$(dirname "$mw_path")
+PATH=$PATH:$mw_path/bin
+
+if [ -f $rootPath/mdserver-web/bin/activate ];then
+ source $rootPath/mdserver-web/bin/activate
+fi
+
+# cd /www/server/mdserver-web && python3 plugins/tamper_proof_py/tamper_proof_service.py start
+sys_start()
+{
+ isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep |grep -v 'tamper_proof_py/tamper_proof_service.py start' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' | grep -v systemctl | grep -v '/bin/sh' | grep -v '/bin/bash' | awk '{print $2}'|xargs)
+ if [ "$isStart" == '' ];then
+ echo -e "Starting tamper_proof_service... \c"
+ cd $rootPath/mdserver-web
+ nohup python3 plugins/tamper_proof_py/tamper_proof_service.py start &> $mw_path/service.log &
+ sleep 0.5
+ isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|awk '{print $2}'|xargs)
+ if [ "$isStart" == '' ];then
+ echo -e "\033[31mfailed\033[0m"
+ echo '------------------------------------------------------'
+ cat $mw_path/service.log
+ echo '------------------------------------------------------'
+ echo -e "\033[31mError: tamper_proof_service startup failed.\033[0m"
+ return;
+ fi
+ echo -e "\033[32mdone\033[0m"
+ else
+ echo "Starting tamper_proof_service (pid $isStart) already running"
+ fi
+}
+
+sys_stop()
+{
+ echo -e "Stopping tamper_proof_service... \c";
+ pids=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v '/bin/bash'|grep -v systemctl | grep -v 'tamper_proof_py/tamper_proof_service.py stop' | grep -v 'tamper_proof_py/tamper_proof_service.py reload' | grep -v 'tamper_proof_py/tamper_proof_service.py restart' |awk '{print $2}'|xargs)
+ arr=($pids)
+ for p in ${arr[@]}
+ do
+ kill -9 $p
+ done
+ cd $rootPath/mdserver-web
+ python3 plugins/tamper_proof_py/tamper_proof_service.py stop
+ echo -e "\033[32mdone\033[0m"
+}
+
+sys_status()
+{
+ isStart=$(ps aux |grep -E "(tamper_proof_service)"|grep -v grep|grep -v "init.d/tamper_proof_py"|grep -v systemctl|awk '{print $2}'|xargs)
+ if [ "$isStart" != '' ];then
+ echo -e "\033[32mtamper_proof_service (pid $isStart) already running\033[0m"
+ else
+ echo -e "\033[32mtamper_proof_service not running\033[0m"
+ fi
+}
+
+case "$1" in
+ 'start')
+ sys_start
+ ;;
+ 'stop')
+ sys_stop
+ ;;
+ 'restart')
+ sys_stop
+ sleep 0.2
+ sys_start
+ ;;
+ 'reload')
+ sys_stop
+ sleep 0.2
+ sys_start
+ ;;
+ 'status')
+ sys_status
+ ;;
+ *)
+ echo "Usage: systemctl {start|stop|restart|reload} tamper_proof_service"
+ ;;
+esac
\ No newline at end of file
diff --git a/plugins/tamper_proof_py/install.sh b/plugins/tamper_proof_py/install.sh
new file mode 100755
index 000000000..efa2d12f7
--- /dev/null
+++ b/plugins/tamper_proof_py/install.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
+export PATH
+
+curPath=`pwd`
+rootPath=$(dirname "$curPath")
+rootPath=$(dirname "$rootPath")
+serverPath=$(dirname "$rootPath")
+
+pip install pyinotify
+if [ -f ${rootPath}/bin/activate ];then
+ source ${rootPath}/bin/activate
+fi
+pip install pyinotify
+
+install_tmp=${rootPath}/tmp/mw_install.pl
+SYSOS=`uname`
+VERSION=$2
+APP_NAME=tamper_proof_py
+
+Install_App()
+{
+ echo '正在安装脚本文件...' > $install_tmp
+ mkdir -p $serverPath/tamper_proof_py
+ echo "$VERSION" > $serverPath/tamper_proof_py/version.pl
+ echo 'install complete' > $install_tmp
+
+ #初始化
+ cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py start $VERSION
+ cd ${serverPath}/mdserver-web && python3 plugins/tamper_proof_py/index.py initd_install $VERSION
+}
+
+Uninstall_App()
+{
+ if [ -f /usr/lib/systemd/system/${APP_NAME}.service ] || [ -f /lib/systemd/system/${APP_NAME}.service ] ;then
+ systemctl stop ${APP_NAME}
+ systemctl disable ${APP_NAME}
+ rm -rf /usr/lib/systemd/system/${APP_NAME}.service
+ rm -rf /lib/systemd/system/${APP_NAME}.service
+ systemctl daemon-reload
+ fi
+
+ rm -rf $serverPath/tamper_proof_py
+ echo "uninstall completed" > $install_tmp
+}
+
+action=$1
+if [ "${1}" == 'install' ];then
+ Install_App
+else
+ Uninstall_App
+fi
diff --git a/plugins/tamper_proof_py/tamper_proof_service.py b/plugins/tamper_proof_py/tamper_proof_service.py
new file mode 100644
index 000000000..61645db3a
--- /dev/null
+++ b/plugins/tamper_proof_py/tamper_proof_service.py
@@ -0,0 +1,552 @@
+
+# +--------------------------------------------------------------------
+# | 事件型防篡改
+# +--------------------------------------------------------------------
+import sys
+import os
+import pyinotify
+import json
+import shutil
+import time
+import psutil
+import threading
+import datetime
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+
+
+class MyEventHandler(pyinotify.ProcessEvent):
+ _PLUGIN_PATH = '/www/server/tamper_proof_py'
+ _CONFIG = '/config.json'
+ _SITES = '/sites.json'
+ _SITES_DATA = None
+ _CONFIG_DATA = None
+ _DONE_FILE = None
+ bakcupChirdPath = []
+
+ def __init__(self):
+ self._PLUGIN_PATH = self.getServerDir()
+
+ def getPluginName(self):
+ return 'tamper_proof_py'
+
+ def getPluginDir(self):
+ return mw.getPluginDir() + '/' + self.getPluginName()
+
+ def getServerDir(self):
+ return mw.getServerDir() + '/' + self.getPluginName()
+
+ def rmdir(self, filename):
+ try:
+ shutil.rmtree(filename)
+ except:
+ pass
+
+ def check_site_logs(self, Stiename, datetime_time):
+ ret = []
+ cur_month = datetime_time.month
+ cur_day = datetime_time.day
+ cur_year = datetime_time.year
+ cur_hour = datetime_time.hour
+ cur_minute = datetime_time.minute
+ cur_second = int(datetime_time.second)
+ months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07',
+ 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
+ logs_data = self.get_site_logs(Stiename)
+ if not logs_data:
+ return False
+ for i2 in logs_data:
+ try:
+ i = i2.split()
+ # 判断状态码是否为200
+ if int(i[8]) not in [200, 500]:
+ continue
+ # 判断是否为POST
+ day_time = i[3].split('/')[0].split('[')[1]
+ if int(cur_day) != int(day_time):
+ continue
+ month_time = i[3].split('/')[1]
+ if int(months[month_time]) != int(cur_month):
+ continue
+ year_time = i[3].split('/')[2].split(':')[0]
+ if int(year_time) != int(cur_year):
+ continue
+ hour_time = i[3].split('/')[2].split(':')[1]
+ if int(hour_time) != int(cur_hour):
+ continue
+ minute_time = i[3].split('/')[2].split(':')[2]
+ if int(minute_time) != int(cur_minute):
+ continue
+ second_time = int(i[3].split('/')[2].split(':')[3])
+ if cur_second - second_time > 10:
+ continue
+ ret.append(i2)
+ except:
+ continue
+ ret2 = []
+ if len(ret) > 20:
+ for i2 in logs_data:
+ try:
+ i = i2.split()
+ if i[6] != 'POST':
+ continue
+ if int(i[8]) not in [200, 500]:
+ continue
+ # 判断是否为POST
+ day_time = i[3].split('/')[0].split('[')[1]
+ if int(cur_day) != int(day_time):
+ continue
+ month_time = i[3].split('/')[1]
+ if int(months[month_time]) != int(cur_month):
+ continue
+ year_time = i[3].split('/')[2].split(':')[0]
+ if int(year_time) != int(cur_year):
+ continue
+ hour_time = i[3].split('/')[2].split(':')[1]
+ if int(hour_time) != int(cur_hour):
+ continue
+ minute_time = i[3].split('/')[2].split(':')[2]
+ if int(minute_time) != int(cur_minute):
+ continue
+ ret2.append(i2)
+ except:
+ continue
+ if ret2:
+ ret = ret2
+ if len(ret) > 20:
+ return ret[0:20]
+ return ret
+
+ def get_site_logs(self, Stiename):
+ try:
+ pythonV = sys.version_info[0]
+ path = '/www/wwwlogs/' + Stiename + '.log'
+ num = 500
+ if not os.path.exists(path):
+ return []
+ p = 1
+ start_line = (p - 1) * num
+ count = start_line + num
+ fp = open(path, 'rb')
+ buf = ""
+ try:
+ fp.seek(-1, 2)
+ except:
+ return []
+ if fp.read(1) == "\n":
+ fp.seek(-1, 2)
+ data = []
+ b = True
+ n = 0
+ c = 0
+ while c < count:
+ while True:
+ newline_pos = str.rfind(buf, "\n")
+ pos = fp.tell()
+ if newline_pos != -1:
+ if n >= start_line:
+ line = buf[newline_pos + 1:]
+ if line:
+ try:
+ data.append(line)
+ except:
+ c -= 1
+ n -= 1
+ pass
+ else:
+ c -= 1
+ n -= 1
+ buf = buf[:newline_pos]
+ n += 1
+ c += 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 pythonV == 3:
+ t_buf = t_buf.decode('utf-8', errors="ignore")
+ buf = t_buf + buf
+ fp.seek(-to_read, 1)
+ if pos - to_read == 0:
+ buf = "\n" + buf
+ if not b:
+ break
+ fp.close()
+ except:
+ data = []
+ return data
+
+ def process_IN_CREATE(self, event):
+ siteInfo = self.get_SITE_CONFIG(event.pathname)
+ if not self.check_FILE(event, siteInfo, True):
+ return False
+ self._DONE_FILE = event.pathname
+ if event.dir:
+ if os.path.exists(event.pathname):
+ self.rmdir(event.pathname)
+ self.write_LOG('create', siteInfo[
+ 'siteName'], event.pathname, datetime.datetime.now())
+
+ else:
+ if os.path.exists(event.pathname):
+ try:
+ src_path = os.path.dirname(event.pathname)
+ os.system("chattr -a {}".format(src_path))
+ os.remove(event.pathname)
+ os.system("chattr +a {}".format(src_path))
+ self.write_LOG('create', siteInfo[
+ 'siteName'], event.pathname, datetime.datetime.now())
+ except:
+ pass
+
+ def process_IN_MOVED_TO(self, event):
+ # 检查是否受保护
+ siteInfo = self.get_SITE_CONFIG(event.pathname)
+ if not self.check_FILE(event, siteInfo):
+ return False
+
+ if not getattr(event, 'src_pathname', None):
+ if os.path.isdir(event.pathname):
+ self.rmdir(event.pathname)
+ else:
+ os.remove(event.pathname)
+ self.write_LOG('move', siteInfo[
+ 'siteName'], '未知 -> ' + event.pathname)
+ return True
+
+ # 是否为标记文件
+ if event.src_pathname == self._DONE_FILE:
+ return False
+
+ if not os.path.exists(event.src_pathname):
+ # 标记
+ self._DONE_FILE = event.pathname
+ # 还原
+ os.renames(event.pathname, event.src_pathname)
+
+ # 记录日志
+ self.write_LOG('move', siteInfo['siteName'],
+ event.src_pathname + ' -> ' + event.pathname)
+
+ def check_FILE(self, event, siteInfo, create=False):
+ if not siteInfo:
+ return False
+ if self.exclude_PATH(event.pathname):
+ return False
+ if event.dir and create:
+ return True
+ if not event.dir:
+ if not self.protect_EXT(event.pathname):
+ return False
+ return True
+
+ def protect_EXT(self, pathname):
+ if pathname.find('.') == -1:
+ return False
+ extName = pathname.split('.')[-1].lower()
+ siteData = self.get_SITE_CONFIG(pathname)
+ if siteData:
+ if extName in siteData['protectExt']:
+ return True
+ return False
+
+ def exclude_PATH(self, pathname):
+ if pathname.find('/') == -1:
+ return False
+ siteData = self.get_SITE_CONFIG(pathname)
+ return self.exclude_PATH_OF_SITE(pathname, siteData['excludePath'])
+
+ def exclude_PATH_OF_SITE(self, pathname, excludePath):
+ pathname = pathname.lower()
+ dirNames = pathname.split('/')
+ if excludePath:
+ if pathname in excludePath:
+ return True
+ if pathname + '/' in excludePath:
+ return True
+ for ePath in excludePath:
+ if ePath in dirNames:
+ return True
+ if pathname.find(ePath) == 0:
+ return True
+ return False
+
+ def get_SITE_CONFIG(self, pathname):
+ if not self._SITES_DATA:
+ self._SITES_DATA = json.loads(
+ mw.readFile(self._PLUGIN_PATH + self._SITES))
+ for site in self._SITES_DATA:
+ length = len(site['path'])
+ if len(pathname) < length:
+ continue
+ if site['path'] != pathname[:length]:
+ continue
+ return site
+ return None
+
+ def get_CONFIG(self):
+ if self._CONFIG_DATA:
+ return self._CONFIG_DATA
+ self._CONFIG_DATA = json.loads(
+ mw.readFile(self._PLUGIN_PATH + self._CONFIG))
+
+ def list_DIR(self, path, siteInfo): # path 站点路径
+ if not os.path.exists(path):
+ return
+ lock_files = []
+ lock_dirs = []
+ explode_a = ['log', 'logs', 'cache', 'templates', 'template', 'upload', 'img',
+ 'image', 'images', 'public', 'static', 'js', 'css', 'tmp', 'temp', 'update', 'data']
+ for name in os.listdir(path):
+ try:
+ filename = "{}/{}".format(path, name).replace('//', '/')
+ lower_name = name.lower()
+ lower_filename = filename.lower()
+ if os.path.isdir(filename): # 是否为目录
+ if lower_name in siteInfo['excludePath']:
+ continue # 是否为排除的文件名
+ # 是否为排除目录
+ if not self.exclude_PATH_OF_SITE(filename, siteInfo['excludePath']):
+ if not lower_name in explode_a: # 是否为固定不锁定目录
+ lock_dirs.append('"' + name + '"')
+ self.list_DIR(filename, siteInfo)
+ continue
+
+ # 是否为受保护的文件名或文件全路径
+ if not lower_name in siteInfo['protectExt'] and not lower_filename in siteInfo['protectExt']:
+ if not self.get_EXT_NAME(lower_name) in siteInfo['protectExt']:
+ continue # 是否为受保护文件类型
+
+ if lower_filename in siteInfo['excludePath']:
+ continue # 是否为排除文件
+ if lower_name in siteInfo['excludePath']:
+ continue # 是否为排除的文件名
+ lock_files.append('"' + name + '"')
+ except:
+ print(mw.getTracebackInfo())
+ if lock_files:
+ self.thread_exec(lock_files, path, 'i')
+ if lock_dirs:
+ self.thread_exec(lock_dirs, path, 'a')
+
+ _thread_count = 0
+ _thread_max = 2 * psutil.cpu_count()
+
+ def thread_exec(self, file_list, cwd, i='i'):
+ while self._thread_count > self._thread_max:
+ time.sleep(0.1)
+
+ self._thread_count += 1
+ cmd = "cd {} && chattr +{} {} > /dev/null".format(
+ cwd, i, ' '.join(file_list))
+ p = threading.Thread(target=self.run_thread, args=(cmd,))
+ p.start()
+
+ def run_thread(self, cmd):
+ os.system(cmd)
+ self._thread_count -= 1
+
+ def get_EXT_NAME(self, fileName):
+ return fileName.split('.')[-1]
+
+ def write_LOG(self, eventType, siteName, pathname, datetime):
+ # 获取网站时间的top100 记录
+ site_log = '/www/wwwlogs/%s.log' % siteName
+ logs_data = []
+ if os.path.exists(site_log):
+ logs_data = self.check_site_logs(siteName, datetime)
+ dateDay = time.strftime("%Y-%m-%d", time.localtime())
+ logPath = self._PLUGIN_PATH + '/sites/' + \
+ siteName + '/day/' + dateDay
+ if not os.path.exists(logPath):
+ os.makedirs(logPath)
+ logFile = os.path.join(logPath, 'logs.json')
+ logVar = [int(time.time()), eventType, pathname, logs_data]
+ fp = open(logFile, 'a+')
+ fp.write(json.dumps(logVar) + "\n")
+ fp.close()
+ logFiles = [
+ logPath + '/total.json',
+ self._PLUGIN_PATH + '/sites/' + siteName + '/day/total.json',
+ self._PLUGIN_PATH + '/sites/total.json'
+ ]
+
+ for totalLogFile in logFiles:
+ if not os.path.exists(totalLogFile):
+ totalData = {"total": 0, "delete": 0,
+ "create": 0, "modify": 0, "move": 0}
+ else:
+ dataTmp = mw.readFile(totalLogFile)
+ if dataTmp:
+ totalData = json.loads(dataTmp)
+ else:
+ totalData = {"total": 0, "delete": 0,
+ "create": 0, "modify": 0, "move": 0}
+
+ totalData['total'] += 1
+ totalData[eventType] += 1
+ mw.writeFile(totalLogFile, json.dumps(totalData))
+
+ # 设置.user.ini
+ def set_user_ini(self, path, up=0):
+ os.chdir(path)
+ useriniPath = path + '/.user.ini'
+ if os.path.exists(useriniPath):
+ os.system('chattr +i ' + useriniPath)
+ for p1 in os.listdir(path):
+ try:
+ npath = path + '/' + p1
+ if not os.path.isdir(npath):
+ continue
+ useriniPath = npath + '/.user.ini'
+ if os.path.exists(useriniPath):
+ os.system('chattr +i ' + useriniPath)
+ if up < 3:
+ self.set_user_ini(npath, up + 1)
+ except:
+ continue
+ return True
+
+ def unlock(self, path):
+ os.system('chattr -R -i {} &> /dev/null'.format(path))
+ os.system('chattr -R -a {} &> /dev/null'.format(path))
+ self.set_user_ini(path)
+
+ def close(self, reload=False):
+ # 解除锁定
+ sites = self.get_sites()
+ print("")
+ print("=" * 60)
+ print("【{}】正在关闭防篡改,请稍候...".format(mw.formatDate()))
+ print("-" * 60)
+ for siteInfo in sites:
+ tip = self._PLUGIN_PATH + '/tips/' + siteInfo['siteName'] + '.pl'
+ if not siteInfo['open'] and not os.path.exists(tip):
+ continue
+ if reload and siteInfo['open']:
+ continue
+ if sys.version_info[0] == 2:
+ print(
+ "【{}】|-解锁网站[{}]".format(mw.formatDate(), siteInfo['siteName'])),
+ else:
+ os.system(
+ "echo -e '【{}】|-解锁网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
+ #print("【{}】|-解锁网站[{}]".format(mw.format_date(),siteInfo['siteName']),end=" ")
+ self.unlock(siteInfo['path'])
+ if os.path.exists(tip):
+ os.remove(tip)
+ print("\t=> 完成")
+ print("-" * 60)
+ print('|-防篡改已关闭')
+ print("=" * 60)
+ print(">>>>>>>>>>END<<<<<<<<<<")
+
+ # 获取网站配置列表
+ def get_sites(self):
+ siteconf = self._PLUGIN_PATH + '/sites.json'
+ d = mw.readFile(siteconf)
+ if not os.path.exists(siteconf) or not d:
+ mw.writeFile(siteconf, "[]")
+ data = json.loads(mw.readFile(siteconf))
+
+ # 处理多余字段开始 >>>>>>>>>>
+ is_write = False
+ rm_keys = ['lock', 'bak_open']
+ for i in data:
+ i_keys = i.keys()
+ if not 'open' in i_keys:
+ i['open'] = False
+ for o in rm_keys:
+ if o in i_keys:
+ if i[o]:
+ i['open'] = True
+ i.pop(o)
+ is_write = True
+ if is_write:
+ mw.writeFile(siteconf, json.dumps(data))
+ # 处理多余字段结束 <<<<<<<<<<<<<
+ return data
+
+ def __enter__(self):
+ self.close()
+
+ def __exit__(self, a, b, c):
+ self.close()
+
+
+def run():
+ # 初始化inotify对像
+ event = MyEventHandler()
+ watchManager = pyinotify.WatchManager()
+ starttime = time.time()
+ mode = pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO
+
+ # 处理网站属性
+ sites = event.get_sites()
+ print("=" * 60)
+ print("【{}】正在启动防篡改,请稍候...".format(mw.formatDate()))
+ print("-" * 60)
+ tip_path = event._PLUGIN_PATH + '/tips/'
+ if not os.path.exists(tip_path):
+ os.makedirs(tip_path)
+ speed_file = event._PLUGIN_PATH + '/speed.pl'
+ for siteInfo in sites:
+ s = time.time()
+ tip = tip_path + siteInfo['siteName'] + '.pl'
+ if not siteInfo['open']:
+ continue
+ if sys.version_info[0] == 2:
+ print("【{}】|-网站[{}]".format(mw.formatDate(),
+ siteInfo['siteName'])),
+ else:
+ os.system(
+ "echo -e '【{}】|-网站[{}]\c'".format(mw.formatDate(), siteInfo['siteName']))
+ # print("【{}】|-网站[{}]".format(public.format_date(),siteInfo['siteName']),end=" ")
+ mw.writeFile(speed_file, "正在处理网站[{}],请稍候...".format(
+ siteInfo['siteName']))
+ if not os.path.exists(tip):
+ event.list_DIR(siteInfo['path'], siteInfo)
+ try:
+ watchManager.add_watch(
+ siteInfo['path'], mode, auto_add=True, rec=True)
+ except:
+ print(mw.getTracebackInfo())
+ tout = round(time.time() - s, 2)
+ mw.writeFile(tip, '1')
+ print("\t\t=> 完成,耗时 {} 秒".format(tout))
+
+ # 启动服务
+ endtime = round(time.time() - starttime, 2)
+ mw.writeLog('防篡改程序', "网站防篡改服务已成功启动,耗时[%s]秒" % endtime)
+ notifier = pyinotify.Notifier(watchManager, event)
+ print("-" * 60)
+ print('|-防篡改服务已启动')
+ print("=" * 60)
+ end_tips = ">>>>>>>>>>END<<<<<<<<<<"
+ print(end_tips)
+ mw.writeFile(speed_file, end_tips)
+ notifier.loop()
+
+
+if __name__ == '__main__':
+ if len(sys.argv) > 1:
+ if 'stop' in sys.argv:
+ event = MyEventHandler()
+ event.close()
+ elif 'start' in sys.argv:
+ run()
+ elif 'unlock' in sys.argv:
+ event = MyEventHandler()
+ event.unlock(sys.argv[2])
+ elif 'reload' in sys.argv:
+ event = MyEventHandler()
+ event.close(True)
+ else:
+ print('error')
+ else:
+ run()