mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
Fixed known bugs Panel Feature Modification Points to note after updating the panel: 1. The default port of the panel is changed to 7800 (Port 8888 has been flooded) 2. Panel entry error will prompt 404 3. After the panel is bound to the domain name, if the domain access is not used,it will return 401 4. After the panel is set to authorize IP access, other IP access panels will return 401 5. The website welcome page is changed to the nginx welcome page 6. Change the stop page to Nginx 404 page
335 lines
16 KiB
Python
335 lines
16 KiB
Python
#coding: utf-8
|
|
# +-------------------------------------------------------------------
|
|
# | 宝塔Linux面板
|
|
# +-------------------------------------------------------------------
|
|
# | Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved.
|
|
# +-------------------------------------------------------------------
|
|
# | Author: hwliang <hwl@bt.cn>
|
|
# +-------------------------------------------------------------------
|
|
|
|
import public,os,sys,db,time,json,re
|
|
from BTPanel import session,cache,json_header
|
|
from flask import request,redirect,g
|
|
|
|
class userlogin:
|
|
|
|
def request_post(self,post):
|
|
if not hasattr(post, 'username') or not hasattr(post, 'password'):
|
|
return public.returnJson(False,'LOGIN_USER_EMPTY'),json_header
|
|
|
|
self.error_num(False)
|
|
if self.limit_address('?') < 1: return public.returnJson(False,'LOGIN_ERR_LIMIT'),json_header
|
|
post.username = post.username.strip()
|
|
|
|
public.chdck_salt()
|
|
sql = db.Sql()
|
|
user_list = sql.table('users').field('id,username,password,salt').select()
|
|
userInfo = None
|
|
for u_info in user_list:
|
|
if public.md5(u_info['username']) == post.username:
|
|
userInfo = u_info
|
|
if 'code' in session:
|
|
if session['code'] and not 'is_verify_password' in session:
|
|
if not hasattr(post, 'code'): return public.returnJson(False,'Verification code can not be empty!'),json_header
|
|
if not public.checkCode(post.code):
|
|
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp()))
|
|
return public.returnJson(False,'CODE_ERR'),json_header
|
|
try:
|
|
if not userInfo['salt']:
|
|
public.chdck_salt()
|
|
userInfo = sql.table('users').where('id=?',(userInfo['id'],)).field('id,username,password,salt').find()
|
|
|
|
password = public.md5(post.password.strip() + userInfo['salt'])
|
|
if public.md5(userInfo['username']) != post.username or userInfo['password'] != password:
|
|
public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp()))
|
|
num = self.limit_address('+')
|
|
return public.returnJson(False,'LOGIN_USER_ERR',(str(num),)),json_header
|
|
_key_file = "/www/server/panel/data/two_step_auth.txt"
|
|
#登陆告警
|
|
public.run_thread(public.login_send_body,("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))))
|
|
# public.login_send_body("Userinfo",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
|
|
if hasattr(post,'vcode'):
|
|
if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header
|
|
import pyotp
|
|
secret_key = public.readFile(_key_file)
|
|
if not secret_key:
|
|
return public.returnJson(False, "Did not find the key, please close Google verification on the command line and trun on again"),json_header
|
|
t = pyotp.TOTP(secret_key)
|
|
result = t.verify(post.vcode)
|
|
if not result:
|
|
if public.sync_date(): 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")
|
|
self.set_cdn_host(post)
|
|
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:
|
|
self.set_cdn_host(post)
|
|
return self._set_login_session(userInfo)
|
|
self.limit_address('-')
|
|
session['is_verify_password'] = True
|
|
return "1"
|
|
except Exception as ex:
|
|
stringEx = str(ex)
|
|
if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1:
|
|
public.ExecShell("rm -f /tmp/sess_*")
|
|
public.ExecShell("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 request_tmp(self,get):
|
|
try:
|
|
if not hasattr(get,'tmp_token'): return public.returnJson(False,'INIT_ARGS_ERR'),json_header
|
|
if len(get.tmp_token) == 48:
|
|
return self.request_temp(get)
|
|
if len(get.tmp_token) != 64: return public.returnJson(False,'INIT_ARGS_ERR'),json_header
|
|
if not re.match(r"^\w+$",get.tmp_token):return public.returnJson(False,'INIT_ARGS_ERR'),json_header
|
|
save_path = '/www/server/panel/config/api.json'
|
|
data = json.loads(public.ReadFile(save_path))
|
|
if not 'tmp_token' in data or not 'tmp_time' in data: return public.returnJson(False,'VERIFICATION_FAILED'),json_header
|
|
if (time.time() - data['tmp_time']) > 120: return public.returnJson(False,'EXPIRED_TOKEN'),json_header
|
|
if get.tmp_token != data['tmp_token']: return public.returnJson(False,'INIT_TOKEN_ERR'),json_header
|
|
userInfo = public.M('users').where("id=?",(1,)).field('id,username').find()
|
|
session['login'] = True
|
|
session['username'] = userInfo['username']
|
|
session['tmp_login'] = True
|
|
session['uid'] = userInfo['id']
|
|
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))))
|
|
self.limit_address('-')
|
|
cache.delete('panelNum')
|
|
cache.delete('dologin')
|
|
session['session_timeout'] = time.time() + public.get_session_timeout()
|
|
del(data['tmp_token'])
|
|
del(data['tmp_time'])
|
|
public.writeFile(save_path,json.dumps(data))
|
|
self.set_request_token()
|
|
self.login_token()
|
|
self.set_cdn_host(get)
|
|
return redirect('/')
|
|
except:
|
|
return public.returnJson(False,'Login failed,' + public.get_error_info()),json_header
|
|
|
|
|
|
def request_temp(self,get):
|
|
try:
|
|
if len(get.__dict__.keys()) > 2: return public.getMsg('INIT_ARGS_ERR')
|
|
if not hasattr(get,'tmp_token'): return public.getMsg('INIT_ARGS_ERR')
|
|
if len(get.tmp_token) != 48: return public.getMsg('INIT_ARGS_ERR')
|
|
if not re.match(r"^\w+$",get.tmp_token):return public.getMsg('INIT_ARGS_ERR')
|
|
skey = public.GetClientIp() + '_temp_login'
|
|
if not public.get_error_num(skey,10): return public.getMsg('AUTH_FAILED')
|
|
s_time = int(time.time())
|
|
data = public.M('temp_login').where('state=? and expire>?',(0,s_time)).field('id,token,salt,expire').find()
|
|
if not data:
|
|
public.set_error_num(skey)
|
|
return public.getMsg('VERIFICATION_FAILED')
|
|
if not isinstance(data,dict):
|
|
public.set_error_num(skey)
|
|
return public.getMsg('VERIFICATION_FAILED')
|
|
r_token = public.md5(get.tmp_token + data['salt'])
|
|
if r_token != data['token']:
|
|
public.set_error_num(skey)
|
|
return public.getMsg('VERIFICATION_FAILED')
|
|
public.set_error_num(skey,True)
|
|
userInfo = public.M('users').where("id=?",(1,)).field('id,username').find()
|
|
session['login'] = True
|
|
session['username'] = public.getMsg('TEMPORARY_ID',(data['id'],))
|
|
session['tmp_login'] = True
|
|
session['tmp_login_id'] = str(data['id'])
|
|
session['tmp_login_expire'] = time.time() + 3600
|
|
session['uid'] = data['id']
|
|
sess_path = 'data/session'
|
|
if not os.path.exists(sess_path):
|
|
os.makedirs(sess_path,384)
|
|
public.writeFile(sess_path + '/' + str(data['id']),'')
|
|
login_addr = public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))
|
|
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],login_addr))
|
|
public.M('temp_login').where('id=?',(data['id'],)).update({"login_time":s_time,'state':1,'login_addr':login_addr})
|
|
self.limit_address('-')
|
|
cache.delete('panelNum')
|
|
cache.delete('dologin')
|
|
session['session_timeout'] = time.time() + public.get_session_timeout()
|
|
self.set_request_token()
|
|
self.login_token()
|
|
self.set_cdn_host(get)
|
|
public.login_send_body("Temporary authorization",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))
|
|
return redirect('/')
|
|
except:
|
|
return public.getMsg('LOGIN_FAIL')
|
|
|
|
|
|
def login_token(self):
|
|
import config
|
|
config.config().reload_session()
|
|
|
|
def request_get(self,get):
|
|
#if os.path.exists('/www/server/panel/install.pl'): raise redirect('/install');
|
|
if not 'title' in session: session['title'] = public.getMsg('NAME')
|
|
domain = public.readFile('data/domain.conf')
|
|
|
|
if domain:
|
|
if(public.GetHost().lower() != domain.strip().lower()):
|
|
return 404
|
|
# errorStr = public.ReadFile('./BTPanel/templates/' + public.GetConfigValue('template') + '/error2.html')
|
|
# try:
|
|
# errorStr = errorStr.format(public.getMsg('PAGE_ERR_TITLE'),public.getMsg('PAGE_ERR_DOMAIN_H1'),public.getMsg('PAGE_ERR_DOMAIN_P1'),public.getMsg('PAGE_ERR_DOMAIN_P2'),public.getMsg('PAGE_ERR_DOMAIN_P3'),public.getMsg('NAME'),public.getMsg('PAGE_ERR_HELP'))
|
|
# except IndexError:pass
|
|
# return errorStr
|
|
if os.path.exists('data/limitip.conf'):
|
|
iplist = public.readFile('data/limitip.conf')
|
|
if iplist:
|
|
iplist = iplist.strip()
|
|
if not public.GetClientIp() in iplist.split(','):
|
|
errorStr = public.ReadFile('./BTPanel/templates/' + public.GetConfigValue('template') + '/error2.html')
|
|
try:
|
|
errorStr = errorStr.format(public.getMsg('PAGE_ERR_TITLE'),public.getMsg('PAGE_ERR_IP_H1'),public.getMsg('PAGE_ERR_IP_P1',(public.GetClientIp(),)),public.getMsg('PAGE_ERR_IP_P2'),public.getMsg('PAGE_ERR_IP_P3'),public.getMsg('NAME'),public.getMsg('PAGE_ERR_HELP'))
|
|
except IndexError:pass
|
|
return errorStr
|
|
|
|
if 'login' in session:
|
|
if session['login'] == True:
|
|
return redirect('/')
|
|
|
|
if not 'code' in session:
|
|
session['code'] = False
|
|
self.error_num(False)
|
|
|
|
#生成request_token
|
|
def set_request_token(self):
|
|
session['request_token_head'] = public.GetRandomString(48)
|
|
|
|
def set_cdn_host(self,get):
|
|
try:
|
|
if not 'cdn_url' in get: return True
|
|
plugin_path = 'plugin/static_cdn'
|
|
if not os.path.exists(plugin_path): return True
|
|
cdn_url = public.get_cdn_url()
|
|
if not cdn_url or cdn_url == get.cdn_url: return True
|
|
public.set_cdn_url(get.cdn_url)
|
|
except:
|
|
return False
|
|
|
|
#防暴破
|
|
def error_num(self,s = True):
|
|
nKey = 'panelNum'
|
|
num = cache.get(nKey)
|
|
if not num:
|
|
cache.set(nKey,1)
|
|
num = 1
|
|
if s: cache.inc(nKey,1)
|
|
if num > 6: session['code'] = True
|
|
|
|
#IP限制
|
|
def limit_address(self,type,v=""):
|
|
import time
|
|
clientIp = public.GetClientIp()
|
|
numKey = 'limitIpNum_' + v + clientIp
|
|
limit = 6
|
|
outTime = 600
|
|
try:
|
|
#初始化
|
|
num1 = cache.get(numKey)
|
|
if not num1:
|
|
cache.set(numKey,1,outTime)
|
|
num1 = 1
|
|
|
|
#计数
|
|
if type == '+':
|
|
cache.inc(numKey,1)
|
|
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']
|
|
session['uid'] = userInfo['id']
|
|
session['login_user_agent'] = public.md5(request.headers.get('User-Agent',''))
|
|
public.WriteLog('TYPE_LOGIN','LOGIN_SUCCESS',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT'))))
|
|
self.limit_address('-')
|
|
cache.delete('panelNum')
|
|
cache.delete('dologin')
|
|
session['session_timeout'] = time.time() + public.get_session_timeout()
|
|
self.set_request_token()
|
|
self.login_token()
|
|
login_type = 'data/app_login.pl'
|
|
if os.path.exists(login_type):
|
|
os.remove(login_type)
|
|
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:
|
|
public.ExecShell("rm -f /tmp/sess_*")
|
|
public.ExecShell("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
|
|
|
|
# 清理多余SESSION数据
|
|
def clear_session(self):
|
|
try:
|
|
session_file = '/dev/shm/session.db'
|
|
if not os.path.exists(session_file): return False
|
|
s_size = os.path.getsize(session_file)
|
|
if s_size < 1024 * 512: return False
|
|
if s_size > 1024 * 1024 * 10:
|
|
from BTPanel import sdb
|
|
if os.path.exists(session_file): os.remove(session_file)
|
|
sdb.create_all()
|
|
if not os.path.exists(session_file):
|
|
public.writeFile('/www/server/panel/data/reload.pl','True')
|
|
return False
|
|
return True
|
|
except:
|
|
return False
|
|
|