update 6.2.6

This commit is contained in:
jose
2019-09-20 15:33:22 +08:00
parent 5663522d80
commit e8aa95f86c
36 changed files with 1933 additions and 380 deletions
+1
View File
@@ -34,6 +34,7 @@ class ajax:
#取Nginx负载状态
self.CheckStatusConf()
result = public.HttpGet('http://127.0.0.1/nginx_status')
return result
tmp = result.split()
data = {}
if "request_time" in tmp:
+1 -1
View File
@@ -27,7 +27,7 @@ class panelSetup:
if ua:
ua = ua.lower();
if ua.find('spider') != -1 or ua.find('bot') != -1: return redirect('https://www.baidu.com');
g.version = '6.2.4'
g.version = '6.2.6'
g.title = public.GetConfigValue('title')
g.uri = request.path
session['version'] = g.version;
+78 -4
View File
@@ -7,11 +7,17 @@
# | Author: 黄文良 <287962566@qq.com>
# +-------------------------------------------------------------------
import public,re,sys,os,nginx,apache,json,time
import public,re,sys,os,nginx,apache,json,time,pyotp
from BTPanel import session,admin_path_checks
from flask import request
class config:
_setup_path = "/www/server/panel"
_key_file = _setup_path+"/data/two_step_auth.txt"
_bk_key_file = _setup_path + "/data/bk_two_step_auth.txt"
_username_file = _setup_path + "/data/username.txt"
_core_fle_path = _setup_path + '/data/qrcode'
def getPanelState(self,get):
return os.path.exists('/www/server/panel/data/close.pl');
@@ -707,8 +713,7 @@ class config:
d=["/tmp","/www/php_session"]
count = 0
for i in d:
if not os.path.exists(i):
continue
if not os.path.exists(i): os.system('mkdir -p %s'%i)
list = os.listdir(i)
for l in list:
if os.path.isdir(i+"/"+l):
@@ -875,6 +880,8 @@ class config:
if not os.path.exists(link_re): return self.auto_cli_php_version(get)
import panelSite
php_versions = panelSite.panelSite().GetPHPVersion(get)
if len(php_versions)==0:
return public.returnMsg(False,'Failed to get php version!')
del(php_versions[0])
for v in php_versions:
if link_re.find(v['version']) != -1: return {"select":v,"versions":php_versions}
@@ -1045,4 +1052,71 @@ class config:
conf = public.readFile(user_ini_file)
if conf and "session.save_path" in conf:
return True
return False
return False
def _create_key(self):
get_token = pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
public.writeFile(self._key_file,get_token)
username = self.get_random()
public.writeFile(self._username_file, username)
def get_key(self,get):
key = public.readFile(self._key_file)
username = public.readFile(self._username_file)
if not key:
return public.returnMsg(False, "The key does not exist. Please turn on and try again.")
if not username:
return public.returnMsg(False, "The username does not exist. Please turn on and try again.")
return {"key":key,"username":username}
def get_random(self):
import random
seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
sa = []
for i in range(8):
sa.append(random.choice(seed))
salt = ''.join(sa)
return salt
def set_two_step_auth(self,get):
if not hasattr(get,"act") or not get.act:
return public.returnMsg(False, "Please enter the operation mode")
if get.act == "1":
if not os.path.exists(self._core_fle_path):
os.makedirs(self._core_fle_path)
username = public.readFile(self._username_file)
if not os.path.exists(self._bk_key_file):
secret_key = public.readFile(self._key_file)
if not secret_key or not username:
self._create_key()
else:
os.rename(self._bk_key_file,self._key_file)
secret_key = public.readFile(self._key_file)
username = public.readFile(self._username_file)
local_ip = public.GetLocalIp()
if not secret_key:
return public.returnMsg(False,"Failed to generate key or username. Please check if the hard disk space is insufficient or the directory cannot be written.[ {} ]".format(self._setup_path+"/data/"))
try:
data = pyotp.totp.TOTP(secret_key).provisioning_uri(username, issuer_name=str(local_ip))
public.writeFile(self._core_fle_path+'/qrcode.txt',str(data))
return public.returnMsg(True, "Open successfully")
except Exception as e:
return public.returnMsg(False, e)
else:
if os.path.exists(self._key_file):
os.rename(self._key_file,self._bk_key_file)
return public.returnMsg(True, "Closed successfully")
# 检测是否开启双因素验证
def check_two_step(self,get):
secret_key = public.readFile(self._key_file)
if not secret_key:
return public.returnMsg(False, "Did not open Google authentication")
return public.returnMsg(True, "Google authentication has been turned on")
# 读取二维码data
def get_qrcode_data(self,get):
data = public.readFile(self._core_fle_path + '/qrcode.txt')
if data:
return data
return public.returnMsg(True, "No QR code data, please re-open")
+1
View File
@@ -412,6 +412,7 @@ echo "--------------------------------------------------------------------------
if not os.path.exists(file): public.writeFile(file,'')
conf = public.readFile(file)
if type(conf)==bool:return public.returnMsg(False,'Failed to read file!')
conf += config + "\n"
if public.writeFile(file,conf):
if not os.path.exists(u_file):
+26 -16
View File
@@ -24,7 +24,7 @@ class database(datatool.datatools):
reg = "^[\w\.-]+$"
if not re.match(reg, data_name): return public.returnMsg(False,'DATABASE_NAME_ERR_T')
if not hasattr(get,'db_user'): get.db_user = data_name;
username = get.db_user.strip();
username = get.db_user.strip()
checks = ['root','mysql','test','sys','panel_logs']
if username in checks or len(username) < 1: return public.returnMsg(False,'DATABASE_USER_NAME_ERR');
if data_name in checks or len(data_name) < 1: return public.returnMsg(False,'DATABASE_NAME_ERR');
@@ -389,14 +389,14 @@ SetLink
name = public.M('databases').where("id=?",(id,)).getField('name')
root = public.M('config').where('id=?',(1,)).getField('mysql_root');
if not os.path.exists(session['config']['backup_path'] + '/database'): os.system('mkdir -p ' + session['config']['backup_path'] + '/database');
self.mypass(True, root);
if not self.mypass(True, root):return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
fileName = name + '_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.sql.gz'
backupName = session['config']['backup_path'] + '/database/' + fileName
public.ExecShell("/www/server/mysql/bin/mysqldump --default-character-set="+ public.get_database_character(name) +" --force --opt \"" + name + "\" | gzip > " + backupName)
if not os.path.exists(backupName): return public.returnMsg(False,'BACKUP_ERROR');
self.mypass(False, root);
if not self.mypass(True, root): return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
sql = public.M('backup')
addTime = time.strftime('%Y-%m-%d %X',time.localtime())
@@ -457,17 +457,17 @@ SetLink
public.ExecShell("cd " + backupPath + " && gunzip -q " + '"'+file+'"')
isgzip = True
if not os.path.exists(backupPath + '/' + tmpFile) or tmpFile == '': return public.returnMsg(False, 'FILE_NOT_EXISTS',(tmpFile,))
self.mypass(True, root);
if not self.mypass(True, root): return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
os.system(public.GetConfigValue('setup_path') + "/mysql/bin/mysql -uroot -p" + root + " --force \"" + name + "\" < " +'"'+ backupPath + '/' +tmpFile+'"')
self.mypass(False, root);
if not self.mypass(True, root): return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
if isgzip:
os.system('cd ' +backupPath+ ' && gzip ' + file.split('/')[-1][:-3]);
else:
os.system("rm -f " + backupPath + '/' +tmpFile)
else:
self.mypass(True, root);
if not self.mypass(True, root): return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
os.system(public.GetConfigValue('setup_path') + "/mysql/bin/mysql -uroot -p" + root + " --force \"" + name + "\" < "+'"' + file+'"')
self.mypass(False, root);
if not self.mypass(True, root): return public.returnMsg(False, 'Database configuration file failed to get checked, please check if MySQL configuration file exists')
public.WriteLog("TYPE_DATABASE", 'DATABASE_INPUT_SUCCESS',(name,))
@@ -508,9 +508,11 @@ SetLink
rep = "\[mysqldump\]\nuser=root"
sea = "[mysqldump]\n"
subStr = sea + "user=root\npassword=\"" + root + "\"\n";
if type(mycnf)==bool:return False
mycnf = mycnf.replace(sea,subStr)
if len(mycnf) > 100: public.writeFile('/etc/my.cnf',mycnf);
return True
#添加到服务器
def ToDataBase(self,find):
#if find['username'] == 'bt_default': return 0
@@ -593,14 +595,16 @@ SetLink
db_name = public.M('databases').where('username=?',(name,)).getField('name');
access = get['access']
password = public.M('databases').where("username=?",(name,)).getField('password')
users = panelMysql.panelMysql().query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'")
mysql_obj = panelMysql.panelMysql()
result = mysql_obj.query("show databases")
isError = self.IsSqlError(result)
if isError != None: return isError
users = mysql_obj.query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'")
for us in users:
panelMysql.panelMysql().execute("drop user '" + name + "'@'" + us[0] + "'")
mysql_obj.execute("drop user '" + name + "'@'" + us[0] + "'")
self.__CreateUsers(db_name,name,password,access)
return public.returnMsg(True, 'SET_SUCCESS')
#获取数据库配置信息
def GetMySQLInfo(self,get):
data = {}
@@ -756,10 +760,15 @@ SetLink
except:pass
for d in data:
for g in gets:
if d[0] == g: result[g] = d[1];
result['Run'] = int(time.time()) - int(result['Uptime'])
try:
if d[0] == g: result[g] = d[1];
except:
pass
if not 'Run' in result and result:
result['Run'] = int(time.time()) - int(result['Uptime'])
tmp = panelMysql.panelMysql().query('show master status');
try:
result['File'] = tmp[0][0];
result['Position'] = tmp[0][1];
except:
@@ -767,6 +776,7 @@ SetLink
result['Position'] = 'OFF';
return result;
#取慢日志
def GetSlowLogs(self,get):
path = self.GetMySQLInfo(get)['datadir'] + '/mysql-slow.log';
+116 -25
View File
@@ -10,6 +10,7 @@
import sys,os,public,time,json,pwd,cgi,shutil
from BTPanel import session,request
class files:
run_path = None
#检查敏感目录
def CheckDir(self,path):
path = path.replace('//','/');
@@ -44,7 +45,66 @@ class files:
public.GetConfigValue('setup_path'))
return not path in nDirs
#网站文件操作前置检测
def site_path_check(self,get):
try:
if not 'site_id' in get: return True
if not self.run_path:
self.run_path,self.path,self.site_name = self.GetSiteRunPath(get.site_id)
if 'path' in get:
if get.path.find(self.path) != 0: return False
if 'sfile' in get:
if get.sfile.find(self.path) != 0: return False
if 'dfile' in get:
if get.dfile.find(self.path) != 0: return False
return True
except: return True
#网站目录后续安全处理
def site_path_safe(self,get):
try:
if not 'site_id' in get: return True
run_path,path,site_name = self.GetSiteRunPath(get.site_id)
if not os.path.exists(run_path): os.makedirs(run_path)
ini_path = run_path + '/.user.ini'
if os.path.exists(ini_path): return True
sess_path = '/www/php_session/%s' % site_name
if not os.path.exists(sess_path): os.makedirs(sess_path)
ini_conf = '''open_basedir={}/:/tmp/:/proc/:{}/
session.save_path={}/
session.save_handler = files'''.format(path,sess_path,sess_path)
public.writeFile(ini_path, ini_conf)
public.ExecShell("chmod 644 %s" % ini_path)
public.ExecShell("chdir +i %s" % ini_path)
return True
except: return False
#取当站点前运行目录
def GetSiteRunPath(self,site_id):
try:
find = public.M('sites').where('id=?',(site_id,)).field('path,name').find();
siteName = find['name']
sitePath = find['path']
if public.get_webserver() == 'nginx':
filename = '/www/server/panel/vhost/nginx/' + siteName + '.conf'
if os.path.exists(filename):
conf = public.readFile(filename)
rep = '\s*root\s*(.+);'
tmp1 = re.search(rep,conf)
if tmp1: path = tmp1.groups()[0];
else:
filename = '/www/server/panel/vhost/apache/' + siteName + '.conf'
if os.path.exists(filename):
conf = public.readFile(filename)
rep = '\s*DocumentRoot\s*"(.+)"\s*\n'
tmp1 = re.search(rep,conf)
if tmp1: path = tmp1.groups()[0];
return path,sitePath,siteName
except:
return sitePath,sitePath,siteName
#检测文件名
def CheckFileName(self,filename):
nots = ['\\','&','*','|',';','"',"'",'<','>']
@@ -83,19 +143,18 @@ class files:
public.WriteLog('TYPE_FILE','FILE_UPLOAD_SUCCESS',(filename,get['path']));
return public.returnMsg(True,'FILE_UPLOAD_SUCCESS');
#上传文件2
def upload(self,args):
if sys.version_info[0] == 2:
args.f_name = args.f_name.encode('utf-8')
args.f_path = args.f_path.encode('utf-8')
if not 'f_name' in args:
args.f_name = request.form.get('f_name')
args.f_path = request.form.get('f_path')
args.f_size = request.form.get('f_size')
args.f_start = request.form.get('f_start')
if sys.version_info[0] == 2:
args.f_name = args.f_name.encode('utf-8')
args.f_path = args.f_path.encode('utf-8')
if args.f_name.find('./') != -1 or args.f_path.find('./') != -1:
return public.returnMsg(False,'错误的参数')
if not os.path.exists(args.f_path):
@@ -118,7 +177,10 @@ class files:
if os.path.exists(new_name):
if new_name.find('.user.ini') != -1:
public.ExecShell("chattr -i " + new_name)
os.remove(new_name)
try:
os.remove(new_name)
except:
os.system("rm -f %s" % new_name)
os.renames(save_path, new_name)
if 'dir_mode' in args and 'file_mode' in args:
mode_tmp1 = args.dir_mode.split(',')
@@ -158,7 +220,9 @@ class files:
if not os.path.isdir(get.path):
get.path = os.path.dirname(get.path)
import pwd
if not os.path.isdir(get.path): return public.returnMsg(False,'This is not a directory!')
import pwd
dirnames = []
filenames = []
@@ -329,14 +393,18 @@ class files:
if count >= max: break;
d = self.xssencode(d)
if d.lower().find(search) != -1:
my_dirs.append(self.__get_stat(d_list[0] + '/' + d,get.path))
filename = d_list[0] + '/' + d
if not os.path.exists(filename): continue
my_dirs.append(self.__get_stat(filename,get.path))
count += 1
for f in d_list[2]:
if count >= max: break;
f = self.xssencode(f)
if f.lower().find(search) != -1:
my_files.append(self.__get_stat(d_list[0] + '/' + f,get.path))
filename = d_list[0] + '/' + f
if not os.path.exists(filename): continue
my_files.append(self.__get_stat(filename,get.path))
count += 1
data = {}
data['DIR'] = sorted(my_dirs)
@@ -367,6 +435,7 @@ class files:
#计算文件数量
def GetFilesCount(self,path,search):
if os.path.isfile(path): return 1
if not os.path.exists(path):return 0
i=0;
for name in os.listdir(path):
if search:
@@ -426,10 +495,13 @@ class files:
if not self.delete_empty(get.path): return public.returnMsg(False,'DIR_ERR_NOT_EMPTY');
if os.path.exists('data/recycle_bin.pl'):
if self.Mv_Recycle_bin(get): return public.returnMsg(True,'DIR_MOVE_RECYCLE_BIN');
if self.Mv_Recycle_bin(get):
self.site_path_safe(get)
return public.returnMsg(True,'DIR_MOVE_RECYCLE_BIN');
import shutil
shutil.rmtree(get.path)
self.site_path_safe(get)
public.WriteLog('TYPE_FILE','DIR_DEL_SUCCESS',(get.path,))
return public.returnMsg(True,'DIR_DEL_SUCCESS')
except:
@@ -453,8 +525,11 @@ class files:
os.system("chattr -i '"+get.path+"'")
try:
if os.path.exists('data/recycle_bin.pl'):
if self.Mv_Recycle_bin(get): return public.returnMsg(True,'FILE_MOVE_RECYCLE_BIN');
if self.Mv_Recycle_bin(get):
self.site_path_safe(get)
return public.returnMsg(True,'FILE_MOVE_RECYCLE_BIN');
os.remove(get.path)
self.site_path_safe(get)
public.WriteLog('TYPE_FILE','FILE_DEL_SUCCESS',(get.path,))
return public.returnMsg(True,'FILE_DEL_SUCCESS')
except:
@@ -566,11 +641,16 @@ class files:
database.database().DeleteTo(path);
continue;
if os.path.isdir(path):
#os.system('rm -rf ' + path);
shutil.rmtree(path);
try:
shutil.rmtree(path);
except:
os.system('rm -rf ' + path);
else:
#os.system('rm -f ' + path);
os.remove(path);
try:
os.remove(path);
except:
os.system('rm -f ' + path);
public.writeSpeed(None,0,0);
public.WriteLog('TYPE_FILE','FILE_CLOSE_RECYCLE_BIN');
return public.returnMsg(True,'FILE_CLOSE_RECYCLE_BIN');
@@ -653,6 +733,7 @@ class files:
return public.returnMsg(False,'FILE_DANGER');
try:
self.move(get.sfile,get.dfile)
self.site_path_safe(get)
if hasattr(get,'rename'):
public.WriteLog('TYPE_FILE','RENAME',(get.sfile,get.dfile))
return public.returnMsg(True,'RENAME_SUCCESS')
@@ -668,6 +749,7 @@ class files:
data = [];
filesx = [];
if not hasattr(get,'filename'):
if not 'selected' in session: return []
filesx = json.loads(session['selected']['data']);
else:
filesx.append(get.filename);
@@ -694,6 +776,7 @@ class files:
public.writeFile(get.path,'');
if os.path.getsize(get.path) > 2097152: return public.returnMsg(False,'CANT_EDIT_ONLINE_FILE');
if not os.path.isfile(get.path): return public.returnMsg(False,'This is not a file!')
fp = open(get.path,'rb')
data = {}
data['status'] = True
@@ -725,11 +808,7 @@ class files:
else:
data['data'] = srcBody.decode(data['encoding'])
else:
if sys.version_info[0] == 2:
data['data'] = srcBody.decode('utf-8').encode('utf-8');
else:
data['data'] = srcBody.decode('utf-8')
data['encoding'] = u'utf-8';
return public.returnMsg(False,'Failed to open file, file may be occupied by other processes!')
if hasattr(get,'filename'): get.path = get.filename
data['historys'] = self.get_history(get.path)
return data;
@@ -739,6 +818,8 @@ class files:
#保存文件
def SaveFileBody(self,get):
if not 'path' in get: return public.returnMsg(False,'path parameter cannot be empty!')
if not 'data' in get: return public.returnMsg(False,'data parameter cannot be empty!')
if sys.version_info[0] == 2: get.path = get.path.encode('utf-8');
if not os.path.exists(get.path):
if get.path.find('.htaccess') == -1:
@@ -969,6 +1050,7 @@ class files:
except:
continue;
public.writeSpeed(None,0,0);
self.site_path_safe(get)
public.WriteLog('TYPE_FILE','FILE_ALL_DEL')
return public.returnMsg(True,'FILE_ALL_DEL')
@@ -978,7 +1060,9 @@ class files:
import shutil
if sys.version_info[0] == 2: get.path = get.path.encode('utf-8');
if not self.CheckDir(get.path): return public.returnMsg(False,'FILE_DANGER');
if not 'selected' in session: return public.returnMsg(False,'The operation failed, please re-copy the copy or cut process')
i = 0;
if not 'selected' in session:return public.returnMsg(False,'The operation failed, please re-operate');
myfiles = json.loads(session['selected']['data'])
l = len(myfiles);
if get.type == '1':
@@ -1016,6 +1100,7 @@ class files:
self.move(sfile,dfile)
except:
continue;
self.site_path_safe(get)
public.WriteLog('TYPE_FILE','FILE_ALL_MOTE',(session['selected']['path'],get.path))
public.writeSpeed(None,0,0);
errorCount = len(myfiles) - i
@@ -1027,6 +1112,7 @@ class files:
if not os.path.exists(sfile): return False
is_dir = os.path.isdir(sfile)
if not os.path.exists(dfile) or not is_dir:
if os.path.exists(dfile): os.remove(dfile)
shutil.move(sfile, dfile)
else:
self.copytree(sfile,dfile)
@@ -1311,6 +1397,7 @@ cd %s
#添加收藏夹分类
def add_files_store_types(self,get):
file_type = get.file_type
if sys.version_info[0] == 2: file_type = file_type.decode('utf-8')
data = self.get_store_data()
if file_type in data: return public.returnMsg(False,'Do not add categories repeatedly!')
@@ -1321,11 +1408,14 @@ cd %s
#删除收藏夹分类
def del_files_store_types(self,get):
file_type = get.file_type
if sys.version_info[0] == 2: file_type = file_type.decode('utf-8')
if file_type == 'Default category': return public.returnMsg(False,'Default category cannot be deleted!')
data = self.get_store_data()
del data[file_type]
self.set_store_data(data)
return public.returnMsg(True,'Delete [' + file_type + '] successfully!')
if file_type in data:
del data[file_type]
self.set_store_data(data)
return public.returnMsg(True,'Delete [' + file_type + '] successfully!')
return public.returnMsg(False,'Delete [' + file_type + '] successfully!')
#获取收藏夹
def get_files_store(self,get):
@@ -1348,6 +1438,7 @@ cd %s
#添加收藏夹
def add_files_store(self,get):
file_type = get.file_type
if sys.version_info[0] == 2: file_type = file_type.decode('utf-8')
path = get.path
if not os.path.exists(path): return public.returnMsg(False,'File or directory does not exist!')
@@ -1361,8 +1452,8 @@ cd %s
#删除收藏夹
def del_files_store(self,get):
file_type = get.file_type
if sys.version_info[0] == 2: file_type = file_type.decode('utf-8')
path = get.path
data = self.get_store_data()
if not file_type in data: return public.returnMsg(False,'Cannot find this favorite category!')
data[file_type].remove(path)
+1 -1
View File
@@ -2,7 +2,7 @@
#-------------------------------------------------------------------
# 宝塔Linux面板
#-------------------------------------------------------------------
# Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved.
# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
#-------------------------------------------------------------------
# Author: 黄文良 <287962566@qq.com>
#-------------------------------------------------------------------
+25 -21
View File
@@ -50,16 +50,31 @@ import public
def extract_zone(domain_name):
domain_name = domain_name.lstrip("*.")
if domain_name.count(".") > 1:
zone, middle, last = str(domain_name).rsplit(".", 2)
root = ".".join([middle, last])
acme_txt = "_acme-challenge.%s" % zone
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
domain_name = domain_name.lstrip("*.")
#处理地区域名
top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn',
'.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn',
'.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn',
'.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn']
m_count = domain_name.count(".")
top_domain = "."+".".join(domain_name.rsplit('.')[-2:])
new_top_domain = "." + top_domain.replace(".","")
is_tow_top = False
if top_domain in top_domain_list:
is_tow_top = True
domain_name = domain_name[:-len(top_domain)] + new_top_domain #地区域名后缀去点处理
if domain_name.count(".") > 1:
zone, middle, last = domain_name.rsplit(".", 2)
acme_txt = "_acme-challenge.%s" % zone
if is_tow_top: last = top_domain[1:] #还原地区域名后缀
root = ".".join([middle, last])
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
class AliyunDns(object):
def __init__(self, key, secret, ):
@@ -257,17 +272,6 @@ class CloudxnsDns(object):
class Dns_com(object):
def extract_zone(self,domain_name):
domain_name = domain_name.lstrip("*.")
if domain_name.count(".") > 1:
zone, middle, last = str(domain_name).rsplit(".", 2)
root = ".".join([middle, last])
acme_txt = "_acme-challenge.%s" % zone
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
def get_dns_obj(self):
p_path = '/www/server/panel/plugin/dns'
+35 -17
View File
@@ -57,27 +57,45 @@ class panelLets:
ret = p12.set_ca_certificates((crypto.load_certificate(crypto.FILETYPE_PEM, ca_pem.encode()),) )
if friendly_name:
ret = p12.set_friendlyname(friendly_name.encode())
return p12
return p12
#域名处理
def extract_zone(self,domain_name):
domain_name = domain_name.lstrip("*.")
#处理地区域名
top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn',
'.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn',
'.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn',
'.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn']
m_count = domain_name.count(".")
top_domain = "."+".".join(domain_name.rsplit('.')[-2:])
new_top_domain = "." + top_domain.replace(".","")
is_tow_top = False
if top_domain in top_domain_list:
is_tow_top = True
domain_name = domain_name[:-len(top_domain)] + new_top_domain #地区域名后缀去点处理
if domain_name.count(".") > 1:
zone, middle, last = domain_name.rsplit(".", 2)
acme_txt = "_acme-challenge.%s" % zone
if is_tow_top: last = top_domain[1:] #还原地区域名
root = ".".join([middle, last])
else:
zone = ""
root = domain_name
acme_txt = "_acme-challenge"
return root, zone, acme_txt
#获取根域名
def get_root_domain(self,domain_name):
if domain_name.count(".") != 1:
pos = domain_name.rfind(".", 0, domain_name.rfind("."))
subd = domain_name[:pos]
domain_name = domain_name[pos + 1 :]
return domain_name
d_root,tow_name,acme_txt = self.extract_zone(domain_name)
return d_root
#获取acmename
def get_acme_name(self,domain_name):
domain_name = domain_name.lstrip("*.")
if domain_name.count(".") > 1:
zone, middle, last = str(domain_name).rsplit(".", 2)
root = ".".join([middle, last])
acme_name = "_acme-challenge.%s.%s" % (zone,root)
else:
root = domain_name
acme_name = "_acme-challenge.%s" % root
return acme_name
d_root,tow_name,acme_txt = self.extract_zone(domain_name)
return acme_txt + '.' + d_root
#格式化错误输出
def get_error(self,error):
@@ -286,7 +304,7 @@ class panelLets:
cronPath = public.GetConfigValue('setup_path') + '/cron/' + echo
shell = 'python %s/panel/class/panelLets.py renew_lets_ssl ' % (self.setupPath)
public.writeFile(cronPath,shell)
args_obj.id = public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("续签Let's Encrypt证书",'day','','0','10',echo,time.strftime('%Y-%m-%d %X',time.localtime()),0,'','localhost','toShell','',shell,''))
args_obj.id = public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("Renew the Letter's Encrypt certificate",'day','','0','10',echo,time.strftime('%Y-%m-%d %X',time.localtime()),0,'','localhost','toShell','',shell,''))
crontab.crontab().set_cron_status(args_obj)
else:
cron_path = public.get_cron_path()
+34 -14
View File
@@ -6,7 +6,7 @@
#-------------------------------------------------------------------
# Author: 黄文良 <287962566@qq.com>
#-------------------------------------------------------------------
import public,os,sys,json,time,psutil,py_compile
import public,os,sys,json,time,psutil,py_compile,re
from BTPanel import session,cache
if sys.version_info[0] == 3: from importlib import reload
class mget: pass;
@@ -119,6 +119,7 @@ class panelPlugin:
#安装插件
def install_plugin(self,get):
if not self.check_sys_write(): return public.returnMsg(False,'CANT_WRITE_SYS_DIR')
if not 'sName' in get: return public.returnMsg(False,'Please specify the software name!')
pluginInfo = self.get_soft_find(get.sName);
p_node = '/www/server/panel/install/public.sh'
if os.path.exists(p_node):
@@ -343,6 +344,10 @@ class panelPlugin:
else:
if name1 == 'pure': name1 = 'pure-ftpd';
if name1 == sName: isTask = task['status'];
if isTask == '-1' or isTask == '0':
if task['name'].find('upgrade') != -1: isTask = '-2'
break
return isTask
@@ -712,23 +717,38 @@ class panelPlugin:
versions[i]['setup'] = (version[:vLen] == vTmp)
return versions
#取pids
def get_pids(self):
pids = []
for pid in os.listdir('/proc'):
if re.match("^\d+$",pid): pids.append(pid)
return pids
#进程是否存在
def process_exists(self,pname,exe = None):
try:
if not self.pids: self.pids = psutil.pids()
for pid in self.pids:
try:
p = psutil.Process(pid)
if p.name() == pname:
if not exe:
return True;
else:
if p.exe() == exe: return True
except:pass
return False
except: return True
if not self.pids: self.pids = psutil.pids() #self.get_pids() #
for pid in self.pids:
l = '/proc/%s/exe' % pid
f = '/proc/%s/comm' % pid
p_exe = ''
p_name = ''
if os.path.exists(l):
p_exe = os.readlink(l)
if not p_name: p_name = p_exe.split('/')[-1]
if not p_name and os.path.exists(f):
fp = open(f,'r')
p_name = fp.read().strip()
fp.close()
if not p_name: continue
if p_name == pname:
if not exe:
return True;
else:
if p_exe == exe: return True
return False
#取分页
def get_page(self,data,get):
+18 -9
View File
@@ -289,7 +289,10 @@ class panelSite(panelRedirect):
#创建根目录
if not os.path.exists(self.sitePath):
os.makedirs(self.sitePath)
try:
os.makedirs(self.sitePath)
except Exception as ex:
return public.returnMsg(False,'Failed to create site root directory, %s' % ex)
public.ExecShell('chmod -R 755 ' + self.sitePath);
public.ExecShell('chown -R www:www ' + self.sitePath);
@@ -549,6 +552,7 @@ class panelSite(panelRedirect):
if isError != True:
return public.returnMsg(False,'ERROR: %s<br><br><a style="color:red;">' % public.GetMsg("GET_ERR_IN_CONFILE") +isError.replace("\n",'<br>')+'</a>');
if not 'domain' in get: return public.returnMsg(False,'Please fill in the domain name!')
if len(get.domain) < 3: return public.returnMsg(False,'SITE_ADD_DOMAIN_ERR_EMPTY');
domains = get.domain.replace(' ','').split(',')
@@ -576,8 +580,6 @@ class panelSite(panelRedirect):
if public.M('binding').where('domain=?',(get.domain,)).count():
return public.returnMsg(False,'SITE_ADD_ERR_DOMAIN_EXISTS');
#写配置文件
self.NginxDomain(get)
try:
@@ -720,6 +722,8 @@ class panelSite(panelRedirect):
#删除域名
def DelDomain(self,get):
if not 'id' in get:return public.returnMsg(False,'Please choose a domain name')
if not 'port' in get: return public.returnMsg(False, 'Please choose a port')
sql = public.M('domain');
id=get['id'];
port = get.port;
@@ -846,7 +850,9 @@ class panelSite(panelRedirect):
if not get.id: return False;
if type(get.id) == list: get.id = get.id[0]['id'];
result = self.GetSiteRunPath(get);
return result['runPath'];
if 'runPath' in result:
return result['runPath'];
return False
# 创建Let's Encrypt免费证书
@@ -1838,7 +1844,7 @@ server
public.M('binding').where("id=?",(id,)).delete();
filename = self.setupPath + '/panel/vhost/rewrite/' + siteName + '_' + binding['path'] + '.conf';
if os.path.exists(filename): os.remove(filename)
if os.path.exists(filename): os.system('rm -rf %s'%filename)
public.serviceReload();
public.WriteLog('TYPE_SITE', 'SITE_BINDING_DEL_SUCCESS',(siteName,binding['path']));
return public.returnMsg(True,'DEL_SUCCESS')
@@ -1888,9 +1894,10 @@ server
rep = "\s+index\s+(.+);";
else:
rep = "DirectoryIndex\s+(.+)\n";
tmp = re.search(rep,conf).groups()
return tmp[0].replace(' ',',')
if re.search(rep,conf):
tmp = re.search(rep,conf).groups()
return tmp[0].replace(' ',',')
return public.returnMsg(False,'Failed to get, there is no default document in the configuration file')
#设置默认文档
def SetIndex(self,get):
@@ -2044,7 +2051,8 @@ server
#是否开启目录防御
def GetDirUserINI(self,get):
path = get.path + self.GetRunPath(get);
path = get.path + self.GetRunPath(get)
if not path:return public.returnMsg(False,'Failed to get directory')
id = get.id;
get.name = public.M('sites').where("id=?",(id,)).getField('name');
data = {}
@@ -3011,6 +3019,7 @@ location %s
get.siteName = public.M('sites').where('id=?',(get.id,)).getField('name');
get.configFile = self.setupPath + '/panel/vhost/nginx/' + get.siteName + '.conf';
conf = public.readFile(get.configFile);
if type(conf)==bool:return False
if conf.find('#AUTH_START') != -1: return True;
return False;
+5 -4
View File
@@ -59,7 +59,6 @@ class bt_task:
if type(data) == str:
public.WriteLog('Task queue',data)
return []
if not 'num' in get: get.num = 15
num = int(get.num)
for i in range(len(data)):
@@ -160,11 +159,14 @@ class bt_task:
#开始检测任务
def start_task(self):
noe = False
while True:
n = 0
while True:
try:
time.sleep(1);
if not os.path.exists(self.__task_tips) and noe: continue;
n+=1
if not os.path.exists(self.__task_tips) and noe and n < 60: continue;
if os.path.exists(self.__task_tips): os.remove(self.__task_tips)
n = 0
public.M(self.__table).where('status=?',('-1',)).setField('status',0)
task_list = self.get_task_list(0)
for task_info in task_list:
@@ -385,7 +387,6 @@ class bt_task:
os.system('chown -R www:www ' + filename)
# os.system('chmod -R 755 ' + filename)
a = 'find {filename} -type d |xargs chmod 0755'.format(filename=filename)
public.writeFile("/tmp/2",str(a))
os.system(a)
os.system('find {filename} -type f |xargs chmod 0644'.format(filename=filename))
+4 -3
View File
@@ -243,10 +243,11 @@ class plugin_deployment:
site_name = get.site_name;
php_version = get.php_version;
#取基础信息
find = public.M('sites').where('name=?',(site_name,)).field('id,path,name').find();
path = find['path'];
find = public.M('sites').where('name=?',(site_name,)).field('id,path,name').find()
if not 'path' in find:
return public.returnMsg(False, 'Site not exist!')
path = find['path']
if path.replace('//','/') == '/': return public.returnMsg(False,'Dangerous website root directory!')
#获取包信息
pinfo = self.GetPackageInfo(name);
id = pinfo['id']
+19 -13
View File
@@ -285,8 +285,10 @@ def GetJson(data):
"""
from json import dumps
if data == bytes: data = data.decode('utf-8')
return dumps(data)
try:
return dumps(data)
except:
return dumps(returnMsg(False,"Wrong response: %s" % str(data)))
def getJson(data):
return GetJson(data)
@@ -304,11 +306,18 @@ def ReadFile(filename, mode='r'):
fp = open(filename, mode)
f_body = fp.read()
fp.close()
except:
fp = open(filename, mode, encoding="utf-8")
f_body = fp.read()
fp.close()
except Exception as ex:
if sys.version_info[0] != 2:
try:
fp = open(filename, mode,encoding="utf-8");
f_body = fp.read()
fp.close()
except Exception as ex2:
WriteLog('Open File',str(ex2))
return False
else:
WriteLog('Open File',str(ex))
return False
return f_body
@@ -505,17 +514,14 @@ def GetLocalIp():
filename = 'data/iplist.txt'
ipaddress = readFile(filename)
if not ipaddress:
import urllib2
url = 'http://pv.sohu.com/cityjson?ie=utf-8'
opener = urllib2.urlopen(url)
m_str = opener.read()
ipaddress = re.search('\d+.\d+.\d+.\d+', m_str).group(0)
WriteFile(filename, ipaddress)
m_str = HttpGet(url)
ipaddress = re.search('\d+.\d+.\d+.\d+',m_str).group(0)
WriteFile(filename,ipaddress)
c_ip = check_ip(ipaddress)
if not c_ip: return GetHost()
return ipaddress
except:
return get_error_info()
try:
url = GetConfigValue('home') + '/Api/getIpAddress';
return HttpGet(url)
+23
View File
@@ -0,0 +1,23 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from pyotp.hotp import HOTP # noqa
from pyotp.otp import OTP # noqa
from pyotp.totp import TOTP # noqa
from . import utils # noqa
def random_base32(length=16, random=None,
chars=list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')):
# Use secrets module if available (Python version >= 3.6) per PEP 506
try:
import secrets
random = secrets.SystemRandom()
except ImportError:
import random as _random
random = _random.SystemRandom()
return ''.join(
random.choice(chars)
for _ in range(length)
)
+10
View File
@@ -0,0 +1,10 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
USING_PYTHON2 = True if sys.version_info < (3, 0) else False
if USING_PYTHON2:
str = unicode # noqa
else:
str = str
+58
View File
@@ -0,0 +1,58 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from . import utils
from .otp import OTP
from .compat import str
class HOTP(OTP):
"""
Handler for HMAC-based OTP counters.
"""
def at(self, count):
"""
Generates the OTP for the given count.
:param count: the OTP HMAC counter
:type count: int
:returns: OTP
:rtype: str
"""
return self.generate_otp(count)
def verify(self, otp, counter):
"""
Verifies the OTP passed in against the current counter OTP.
:param otp: the OTP to check against
:type otp: str
:param count: the OTP HMAC counter
:type count: int
"""
return utils.strings_equal(str(otp), str(self.at(counter)))
def provisioning_uri(self, name, initial_count=0, issuer_name=None):
"""
Returns the provisioning URI for the OTP. This can then be
encoded in a QR Code and used to provision an OTP app like
Google Authenticator.
See also:
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
:param name: name of the user account
:type name: str
:param initial_count: starting HMAC counter value, defaults to 0
:type initial_count: int
:param issuer_name: the name of the OTP issuer; this will be the
organization title of the OTP entry in Authenticator
:returns: provisioning URI
:rtype: str
"""
return utils.build_uri(
self.secret,
name,
initial_count=initial_count,
issuer_name=issuer_name,
algorithm=self.digest().name,
digits=self.digits
)
+66
View File
@@ -0,0 +1,66 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import base64
import hashlib
import hmac
from .compat import str
class OTP(object):
"""
Base class for OTP handlers.
"""
def __init__(self, s, digits=6, digest=hashlib.sha1):
"""
:param s: secret in base32 format
:type s: str
:param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
:type digits: int
:param digest: digest function to use in the HMAC (expected to be sha1)
:type digest: callable
"""
self.digits = digits
self.digest = digest
self.secret = s
def generate_otp(self, input):
"""
:param input: the HMAC counter value to use as the OTP input.
Usually either the counter, or the computed integer based on the Unix timestamp
:type input: int
"""
if input < 0:
raise ValueError('input must be positive integer')
hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
hmac_hash = bytearray(hasher.digest())
offset = hmac_hash[-1] & 0xf
code = ((hmac_hash[offset] & 0x7f) << 24 |
(hmac_hash[offset + 1] & 0xff) << 16 |
(hmac_hash[offset + 2] & 0xff) << 8 |
(hmac_hash[offset + 3] & 0xff))
str_code = str(code % 10 ** self.digits)
while len(str_code) < self.digits:
str_code = '0' + str_code
return str_code
def byte_secret(self):
missing_padding = len(self.secret) % 8
if missing_padding != 0:
self.secret += '=' * (8 - missing_padding)
return base64.b32decode(self.secret, casefold=True)
@staticmethod
def int_to_bytestring(i, padding=8):
"""
Turns an integer to the OATH specified
bytestring, which is fed to the HMAC
along with the secret
"""
result = bytearray()
while i != 0:
result.append(i & 0xFF)
i >>= 8
# It's necessary to convert the final result from bytearray to bytes
# because the hmac functions in python 2.6 and 3.3 don't work with
# bytearray
return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))
+92
View File
@@ -0,0 +1,92 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import datetime
import time
from . import utils
from .otp import OTP
from .compat import str
class TOTP(OTP):
"""
Handler for time-based OTP counters.
"""
def __init__(self, *args, **kwargs):
"""
:param interval: the time interval in seconds
for OTP. This defaults to 30.
:type interval: int
"""
self.interval = kwargs.pop('interval', 30)
super(TOTP, self).__init__(*args, **kwargs)
def at(self, for_time, counter_offset=0):
"""
Accepts either a Unix timestamp integer or a datetime object.
:param for_time: the time to generate an OTP for
:type for_time: int or datetime
:param counter_offset: the amount of ticks to add to the time counter
:returns: OTP value
:rtype: str
"""
if not isinstance(for_time, datetime.datetime):
for_time = datetime.datetime.fromtimestamp(int(for_time))
return self.generate_otp(self.timecode(for_time) + counter_offset)
def now(self):
"""
Generate the current time OTP
:returns: OTP value
:rtype: str
"""
return self.generate_otp(self.timecode(datetime.datetime.now()))
def verify(self, otp, for_time=None, valid_window=0):
"""
Verifies the OTP passed in against the current time OTP.
:param otp: the OTP to check against
:type otp: str
:param for_time: Time to check OTP at (defaults to now)
:type for_time: int or datetime
:param valid_window: extends the validity to this many counter ticks before and after the current one
:type valid_window: int
:returns: True if verification succeeded, False otherwise
:rtype: bool
"""
if for_time is None:
for_time = datetime.datetime.now()
if valid_window:
for i in range(-valid_window, valid_window + 1):
if utils.strings_equal(str(otp), str(self.at(for_time, i))):
return True
return False
return utils.strings_equal(str(otp), str(self.at(for_time)))
def provisioning_uri(self, name, issuer_name=None):
"""
Returns the provisioning URI for the OTP. This can then be
encoded in a QR Code and used to provision an OTP app like
Google Authenticator.
See also:
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
:param name: name of the user account
:type name: str
:param issuer_name: the name of the OTP issuer; this will be the
organization title of the OTP entry in Authenticator
:returns: provisioning URI
:rtype: str
"""
return utils.build_uri(self.secret, name, issuer_name=issuer_name,
algorithm=self.digest().name,
digits=self.digits, period=self.interval)
def timecode(self, for_time):
i = time.mktime(for_time.timetuple())
return int(i / self.interval)
+109
View File
@@ -0,0 +1,109 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import unicodedata
try:
from itertools import izip_longest
except ImportError:
from itertools import zip_longest as izip_longest
try:
from urllib.parse import quote, urlencode
except ImportError:
from urllib import quote, urlencode
def build_uri(secret, name, initial_count=None, issuer_name=None,
algorithm=None, digits=None, period=None):
"""
Returns the provisioning URI for the OTP; works for either TOTP or HOTP.
This can then be encoded in a QR Code and used to provision the Google
Authenticator app.
For module-internal use.
See also:
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
:param secret: the hotp/totp secret used to generate the URI
:type secret: str
:param name: name of the account
:type name: str
:param initial_count: starting counter value, defaults to None.
If none, the OTP type will be assumed as TOTP.
:type initial_count: int
:param issuer_name: the name of the OTP issuer; this will be the
organization title of the OTP entry in Authenticator
:type issuer_name: str
:param algorithm: the algorithm used in the OTP generation.
:type algorithm: str
:param digits: the length of the OTP generated code.
:type digits: int
:param period: the number of seconds the OTP generator is set to
expire every code.
:type period: int
:returns: provisioning uri
:rtype: str
"""
# initial_count may be 0 as a valid param
is_initial_count_present = (initial_count is not None)
# Handling values different from defaults
is_algorithm_set = (algorithm is not None and algorithm != 'sha1')
is_digits_set = (digits is not None and digits != 6)
is_period_set = (period is not None and period != 30)
otp_type = 'hotp' if is_initial_count_present else 'totp'
base_uri = 'otpauth://{0}/{1}?{2}'
url_args = {'secret': secret}
label = quote(name)
if issuer_name is not None:
label = quote(issuer_name) + ':' + label
url_args['issuer'] = issuer_name
if is_initial_count_present:
url_args['counter'] = initial_count
if is_algorithm_set:
url_args['algorithm'] = algorithm.upper()
if is_digits_set:
url_args['digits'] = digits
if is_period_set:
url_args['period'] = period
uri = base_uri.format(otp_type, label, urlencode(url_args).replace("+", "%20"))
return uri
def _compare_digest(s1, s2):
differences = 0
for c1, c2 in izip_longest(s1, s2):
if c1 is None or c2 is None:
differences = 1
continue
differences |= ord(c1) ^ ord(c2)
return differences == 0
try:
# Python 3.3+ and 2.7.7+ include a timing-attack-resistant
# comparison function, which is probably more reliable than ours.
# Use it if available.
from hmac import compare_digest
except ImportError:
compare_digest = _compare_digest
def strings_equal(s1, s2):
"""
Timing-attack resistant string comparison.
Normal comparison using == will short-circuit on the first mismatching
character. This avoids that by scanning the whole string, though we
still reveal to a timing attack whether the strings are the same
length.
"""
s1 = unicodedata.normalize('NFKC', s1)
s2 = unicodedata.normalize('NFKC', s2)
return compare_digest(s1.encode("utf-8"), s2.encode("utf-8"))
+7 -6
View File
@@ -71,10 +71,11 @@ class SiteDirAuth:
if not get.name:
return public.returnMsg(False, 'Please enter the Name')
if site_dir[0] == "/":
site_dir = site_dir[1:]
if site_dir[-1] == "/":
site_dir = site_dir[:-1]
if site_dir[0] != "/" or site_dir[-1] != "/":
return public.returnMsg(False, 'Directory format is incorrect')
# site_dir = site_dir[1:]
# if site_dir[-1] == "/":
# site_dir = site_dir[:-1]
passwd = public.hasPwd(get.password)
site_info = self.get_site_info(get.id)
site_name = site_info["site_name"]
@@ -126,7 +127,7 @@ class SiteDirAuth:
file_path = "{setup_path}/panel/vhost/{webserver}/dir_auth/{site_name}"
if i == "nginx":
# 设置nginx
conf = '''location ~* ^/%s/* {
conf = '''location ~* ^%s* {
#AUTH_START
auth_basic "Authorization";
auth_basic_user_file %s;
@@ -134,7 +135,7 @@ class SiteDirAuth:
}''' % (site_dir,auth_file)
else:
# 设置apache
conf = '''<Directory "{site_path}/{site_dir}/">
conf = '''<Directory "{site_path}{site_dir}">
#AUTH_START
AuthType basic
AuthName "Authorization "
+75 -15
View File
@@ -14,7 +14,7 @@ from flask import request,redirect,g
class userlogin:
def request_post(self,post):
if not (hasattr(post, 'username') or hasattr(post, 'password') or hasattr(post, 'code')):
if not hasattr(post, 'username') or not hasattr(post, 'password'):
return public.returnJson(False,'LOGIN_USER_EMPTY'),json_header
self.error_num(False)
@@ -27,6 +27,7 @@ class userlogin:
m_code = cache.get('codeStr')
if 'code' in session:
if session['code']:
if not hasattr(post, 'code'): return public.returnMsg(False,'Verification code can not be empty!')
if not public.checkCode(post.code):
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp()));
return public.returnJson(False,'CODE_ERR'),json_header
@@ -36,18 +37,28 @@ class userlogin:
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()));
num = self.limit_address('+');
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
session['login'] = True;
session['username'] = userInfo['username'];
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()));
self.limit_address('-');
cache.delete('panelNum')
cache.delete('dologin')
sess_input_path = 'data/session_last.pl'
public.writeFile(sess_input_path,str(int(time.time())))
self.set_request_token()
self.login_token()
return public.returnJson(True,'LOGIN_SUCCESS'),json_header
_key_file = "/www/server/panel/data/two_step_auth.txt"
if hasattr(post,'vcode'):
import pyotp
secret_key = public.readFile(_key_file)
if not secret_key:
return public.returnMsg(False, "Did not find the key, please close Google verification on the command line and trun on again")
t = pyotp.TOTP(secret_key)
result = t.verify(post.vcode)
if not result:
num = self.limit_address('++',v="vcode")
return public.returnJson(False, 'Invalid Verification code. You have [{}] times left to try!'.format(num)), json_header
now = int(time.time())
public.writeFile("/www/server/panel/data/dont_vcode_ip.txt",json.dumps({"client_ip":public.GetClientIp(),"add_time":now}))
self.limit_address('--',v="vcode")
return self._set_login_session(userInfo)
acc_client_ip = self.check_two_step_auth()
if not os.path.exists(_key_file) or acc_client_ip:
return self._set_login_session(userInfo)
self.limit_address('-')
return "1"
except Exception as ex:
stringEx = str(ex)
if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1:
@@ -139,10 +150,10 @@ class userlogin:
if num > 6: session['code'] = True;
#IP限制
def limit_address(self,type):
def limit_address(self,type,v=""):
import time
clientIp = public.GetClientIp();
numKey = 'limitIpNum_' + clientIp
numKey = 'limitIpNum_' + v + clientIp
limit = 6;
outTime = 600;
try:
@@ -158,14 +169,63 @@ class userlogin:
self.error_num();
session['code'] = True;
return limit - (num1+1);
#计数验证器
if type == '++':
cache.inc(numKey,1)
self.error_num();
session['code'] = False;
return limit - (num1+1);
#清空
if type == '-':
cache.delete(numKey);
session['code'] = False;
return 1;
#清空验证器
if type == '--':
cache.delete(numKey);
session['code'] = False;
return 1;
return limit - num1;
except:
return limit;
# 登录成功设置session
def _set_login_session(self,userInfo):
try:
session['login'] = True
session['username'] = userInfo['username']
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()))
self.limit_address('-')
cache.delete('panelNum')
cache.delete('dologin')
sess_input_path = 'data/session_last.pl'
public.writeFile(sess_input_path,str(int(time.time())))
self.set_request_token()
self.login_token()
return public.returnJson(True,'LOGIN_SUCCESS'),json_header
except Exception as ex:
stringEx = str(ex)
if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1:
os.system("rm -f /tmp/sess_*")
os.system("rm -f /www/wwwlogs/*log")
public.ServiceReload()
return public.returnJson(False,'USER_INODE_ERR'),json_header
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()))
num = self.limit_address('+');
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
# 检查是否需要进行二次验证
def check_two_step_auth(self):
dont_vcode_ip_info = public.readFile("/www/server/panel/data/dont_vcode_ip.txt")
acc_client_ip = False
if dont_vcode_ip_info:
dont_vcode_ip_info = json.loads(dont_vcode_ip_info)
ip = dont_vcode_ip_info["client_ip"] == public.GetClientIp()
now = int(time.time())
v_time = now - int(dont_vcode_ip_info["add_time"])
if ip and v_time < 86400:
acc_client_ip = True
return acc_client_ip