mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
update 6.2.6
This commit is contained in:
+15
-4
@@ -183,6 +183,8 @@ def login():
|
||||
|
||||
if request.method == method_post[0]:
|
||||
result = userlogin.userlogin().request_post(get)
|
||||
if result == "1":
|
||||
return result
|
||||
return is_login(result)
|
||||
|
||||
if request.method == method_get[0]:
|
||||
@@ -460,7 +462,7 @@ def config(pdata = None):
|
||||
if public.is_local(): data['is_local'] = 'checked'
|
||||
return render_template( 'config.html',data=data)
|
||||
import config
|
||||
defs = ('get_php_session_path','set_php_session_path','get_cert_source','set_local','set_debug','get_panel_error_logs','clean_panel_error_logs','get_basic_auth_stat','set_basic_auth','get_cli_php_version','get_tmp_token','set_cli_php_version','DelOldSession', 'GetSessionCount', 'SetSessionConf', 'GetSessionConf','get_ipv6_listen','set_ipv6_status','GetApacheValue','SetApacheValue','GetNginxValue','SetNginxValue','get_token','set_token','set_admin_path','is_pro','get_php_config','get_config','SavePanelSSL','GetPanelSSL','GetPHPConf','SetPHPConf','GetPanelList','AddPanelInfo','SetPanelInfo','DelPanelInfo','ClickPanelInfo','SetPanelSSL','SetTemplates','Set502','setPassword','setUsername','setPanel','setPathInfo','setPHPMaxSize','getFpmConfig','setFpmConfig','setPHPMaxTime','syncDate','setPHPDisable','SetControl','ClosePanel','AutoUpdatePanel','SetPanelLock')
|
||||
defs = ('get_qrcode_data','check_two_step','set_two_step_auth','get_key','get_php_session_path','set_php_session_path','get_cert_source','set_local','set_debug','get_panel_error_logs','clean_panel_error_logs','get_basic_auth_stat','set_basic_auth','get_cli_php_version','get_tmp_token','set_cli_php_version','DelOldSession', 'GetSessionCount', 'SetSessionConf', 'GetSessionConf','get_ipv6_listen','set_ipv6_status','GetApacheValue','SetApacheValue','GetNginxValue','SetNginxValue','get_token','set_token','set_admin_path','is_pro','get_php_config','get_config','SavePanelSSL','GetPanelSSL','GetPHPConf','SetPHPConf','GetPanelList','AddPanelInfo','SetPanelInfo','DelPanelInfo','ClickPanelInfo','SetPanelSSL','SetTemplates','Set502','setPassword','setUsername','setPanel','setPathInfo','setPHPMaxSize','getFpmConfig','setFpmConfig','setPHPMaxTime','syncDate','setPHPDisable','SetControl','ClosePanel','AutoUpdatePanel','SetPanelLock')
|
||||
return publicObject(config.config(),defs,None,pdata);
|
||||
|
||||
@app.route('/ajax',methods=method_all)
|
||||
@@ -503,7 +505,11 @@ def panel_data(pdata = None):
|
||||
|
||||
@app.route('/code')
|
||||
def code():
|
||||
import vilidate,time
|
||||
try:
|
||||
import vilidate,time
|
||||
except:
|
||||
os.system("pip install Pillow==5.4.1 -I")
|
||||
return "Pillow not install!"
|
||||
code_time = cache.get('codeOut')
|
||||
if code_time: return u'Error: Don\'t request validation codes frequently';
|
||||
vie = vilidate.vieCode();
|
||||
@@ -710,7 +716,9 @@ def panel_other(name=None,fun = None,stype=None):
|
||||
if r_type == dict: return public.returnJson(False,public.GetMsg("RETURN_TYPE_ERR") + '[%s]' % r_type), json_header
|
||||
return data
|
||||
except:
|
||||
return public.get_error_info().replace('\n','<br>\n')
|
||||
error_info = public.get_error_info()
|
||||
public.submit_error(error_info)
|
||||
return error_info.replace('\n','<br>\n')
|
||||
|
||||
|
||||
@app.route('/wxapp',methods=method_all)
|
||||
@@ -991,10 +999,13 @@ def publicObject(toObject,defs,action=None,get = None):
|
||||
|
||||
if hasattr(get,'path'):
|
||||
get.path = get.path.replace('//','/').replace('\\','/');
|
||||
if get.path.find('..') != -1: return public.ReturnJson(False,public.GetMsg("UNSAFE_PATH")),json_header
|
||||
if get.path.find('./') != -1: return public.ReturnJson(False,public.GetMsg("UNSAFE_PATH")),json_header
|
||||
if get.path.find('->') != -1:
|
||||
get.path = get.path.split('->')[0].strip();
|
||||
|
||||
if hasattr(toObject,'site_path_check'):
|
||||
if not toObject.site_path_check(get): return public.ReturnJson(False,'Excessive operation!'),json_header
|
||||
|
||||
for key in defs:
|
||||
if key == get.action:
|
||||
fun = 'toObject.'+key+'(get)'
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5233,6 +5233,13 @@ select[disabled]{
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
.aceEditors{
|
||||
border: 1px solid rgb(208, 207, 207);
|
||||
overflow: hidden;
|
||||
}
|
||||
#ace_conter.chrome{
|
||||
background: #ececec;
|
||||
}
|
||||
#ace_conter{
|
||||
background: #444;
|
||||
height: 100%;
|
||||
|
||||
+187
-22
@@ -22,9 +22,175 @@ function SetPanelAutoUpload(){
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$('#panel_verification').click(function(){
|
||||
var _checked = $(this).prop('checked');
|
||||
if(_checked){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['600px','420px'],
|
||||
title: 'Google authentication binding',
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20 pd70 ssl_cert_from google_verify" style="padding:20px 35px;">\
|
||||
<div class="">\
|
||||
<i class="layui-layer-ico layui-layer-ico3"></i>\
|
||||
<h3>Warning! Do not understand this feature, do not open!</h3>\
|
||||
<ul style="width:91%;margin-bottom:10px;margin-top:10px;">\
|
||||
<li style="color:red;">You must use and understand this feature to decide if you want to open it!</li>\
|
||||
<li style="color:red;">If it is not possible to verify, enter "bt 24" on the command line to cancel Google authentication.</li>\
|
||||
<li>Once the service is turned on, bind it immediately to avoid the panel being inaccessible.</li>\
|
||||
<li>After opening, the panel will not be accessible. You can click the link below to find out the solution.</li>\
|
||||
</ul>\
|
||||
</div>\
|
||||
<div class="details" style="width: 90%;margin-bottom:10px;">\
|
||||
<input type="checkbox" id="check_verification">\
|
||||
<label style="font-weight: 400;margin: 3px 5px 0px;" for="check_verification">I already know the details and are willing to take risks</label>\
|
||||
<a target="_blank" class="btlink" href="https://forum.aapanel.com/d/357-how-to-use-google-authenticator-in-the-aapanel">Learn more</a>\
|
||||
</div>\
|
||||
<div class="bt-form-submit-btn">\
|
||||
<button type="button" class="btn btn-sm btn-danger close_verify">Close</button>\
|
||||
<button type="button" class="btn btn-sm btn-success submit_verify">Confirm</button>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(layers,index){
|
||||
$('.submit_verify').click(function(e){
|
||||
var check_verification = $('#check_verification').prop('checked');
|
||||
if(!check_verification){
|
||||
layer.msg('Please check the consent risk first.',{icon:0});
|
||||
return false;
|
||||
}
|
||||
var loadT = layer.msg('Opening Google authentication, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
set_two_step_auth({act:_checked},function(rdata){
|
||||
layer.close(loadT);
|
||||
if (rdata.status) layer.closeAll();
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
if(rdata.status && _checked){
|
||||
$('.open_two_verify_view').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('.close_verify').click(function(){
|
||||
layer.closeAll();
|
||||
$('#panel_verification').prop('checked',!_checked);
|
||||
});
|
||||
},cancel:function () {
|
||||
layer.closeAll();
|
||||
$('#panel_verification').prop('checked',!_checked);
|
||||
}
|
||||
});
|
||||
}else{
|
||||
bt.confirm({
|
||||
title: 'Google authentication',
|
||||
msg: 'Turn off Google authentication, do you want to continue?',
|
||||
cancel: function () {
|
||||
$('#panel_verification').prop('checked',!_checked);
|
||||
}}, function () {
|
||||
var loadT = layer.msg('Google authentication is being turned off, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
set_two_step_auth({act:_checked},function(rdata){
|
||||
layer.close(loadT);
|
||||
if (rdata.status) layer.closeAll();
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
if(rdata.status && _checked){
|
||||
$('.open_two_verify_view').click();
|
||||
}
|
||||
});
|
||||
},function () {
|
||||
$('#panel_verification').prop('checked',!_checked);
|
||||
});
|
||||
}
|
||||
|
||||
// console.log(_data);
|
||||
|
||||
});
|
||||
|
||||
$('.open_two_verify_view').click(function(){
|
||||
var _checked = $('#panel_verification').prop('checked');
|
||||
if(!_checked){
|
||||
layer.msg('Please turn on Google authentication first.',{icon:0});
|
||||
return false;
|
||||
}
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['600px','670px'],
|
||||
title: 'Google authentication binding',
|
||||
closeBtn: 2,
|
||||
shift: 5,
|
||||
shadeClose: false,
|
||||
content: '<div class="bt-form pd20" style="padding:20px 35px;">\
|
||||
<div class="verify_title">Login authentication based on Google Authenticator</div>\
|
||||
<div class="verify_item">\
|
||||
<div class="verify_vice_title">1. Key binding</div>\
|
||||
<div class="verify_conter">\
|
||||
<div class="verify_box">\
|
||||
<div class="verify_box_line">Account:<span class="username"></sapn></div>\
|
||||
<div class="verify_box_line">Key:<span class="userkey"></sapn></div>\
|
||||
<div class="verify_box_line">Type:<span class="usertype">Time based</sapn></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="verify_item">\
|
||||
<div class="verify_vice_title">2. Scan code binding (Using Google Authenticator APP scan)</div>\
|
||||
<div class="verify_conter" style="text-align:center;padding-top:10px;">\
|
||||
<div id="verify_qrcode"></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
<div class="verify_tips">\
|
||||
<p>Tips: Please use the "Google Authenticator APP" binding to support Android, IOS system.<a href="https://forum.aapanel.com/d/357-how-to-use-google-authenticator-in-the-aapanel" class="btlink" target="_blank">Use tutorial</a></p>\
|
||||
<p style="color:red;">Once you have turned on the service, use the Google Authenticator app binding now to avoid having to sign in.</p>\
|
||||
</div>\
|
||||
</div>',
|
||||
success:function(e){
|
||||
get_two_verify(function(res){
|
||||
$('.verify_box_line .username').html(res.username);
|
||||
$('.verify_box_line .userkey').html(res.key);
|
||||
});
|
||||
get_qrcode_data(function(res){
|
||||
jQuery('#verify_qrcode').qrcode({
|
||||
render: "canvas",
|
||||
text: res,
|
||||
height:150,
|
||||
width:150
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
(function(){
|
||||
check_two_step(function(res){
|
||||
$('#panel_verification').prop('checked',res.status);
|
||||
});
|
||||
})()
|
||||
function get_qrcode_data(callback){
|
||||
$.post('/config?action=get_qrcode_data',function(res){
|
||||
if(callback) callback(res);
|
||||
});
|
||||
}
|
||||
|
||||
function check_two_step(callback){
|
||||
$.post('/config?action=check_two_step',function(res){
|
||||
if(callback) callback(res);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function get_two_verify(callback){
|
||||
$.post('/config?action=get_key',function(res){
|
||||
if(callback) callback(res);
|
||||
});
|
||||
}
|
||||
function set_two_step_auth(obj,callback){
|
||||
$.post('/config?action=set_two_step_auth',{act:obj.act?1:0},function(res){
|
||||
if(callback) callback(res);
|
||||
});
|
||||
}
|
||||
|
||||
$(".set-submit").click(function(){
|
||||
var data = $("#set-Config").serialize();
|
||||
console.log(data)
|
||||
layer.msg(lan.config.config_save,{icon:16,time:0,shade: [0.3, '#000']});
|
||||
$.post('/config?action=setPanel',data,function(rdata){
|
||||
layer.closeAll();
|
||||
@@ -35,7 +201,7 @@ $(".set-submit").click(function(){
|
||||
},1500);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -62,8 +228,8 @@ function modify_auth_path() {
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -100,7 +266,7 @@ function Set502(){
|
||||
$.post('/config?action=Set502','',function(rdata){
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//绑定修改宝塔账号
|
||||
@@ -153,7 +319,7 @@ function apiSetup(){
|
||||
var loadT = layer.msg(lan.config.token_get,{icon:16,time:0,shade: [0.3, '#000']});
|
||||
$.get('/api?action=GetToken',function(rdata){
|
||||
layer.close(loadT);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -496,7 +662,7 @@ var weChat = {
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
function open_wxapp(){
|
||||
var rhtml = '<div class="boxConter" style="display: none">\
|
||||
<div class="iconCode" >\
|
||||
@@ -515,7 +681,7 @@ function open_wxapp(){
|
||||
<ul class="userList"></ul>\
|
||||
</div>\
|
||||
</div>'
|
||||
|
||||
|
||||
layer.open({
|
||||
type: 1,
|
||||
title: lan.config.bind_wechat,
|
||||
@@ -524,7 +690,7 @@ function open_wxapp(){
|
||||
shadeClose: false,
|
||||
content:rhtml
|
||||
});
|
||||
|
||||
|
||||
weChat.init();
|
||||
}
|
||||
|
||||
@@ -615,7 +781,7 @@ function SetPanelApi(t_type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
if (rdata.msg == lan.config.open_successfully) {
|
||||
GetPanelApi();
|
||||
@@ -657,19 +823,19 @@ function modify_basic_auth() {
|
||||
if (rdata.open) {
|
||||
show_basic_auth(rdata);
|
||||
} else {
|
||||
m_html = '<div><i class="layui-layer-ico layui-layer-ico3"></i>'
|
||||
+ '<h3 style="margin-left: 40px;margin - bottom:10px;"> Danger! This feature does not know how to open!</h3>'
|
||||
+ '<ul style="border: 1px solid #ececec;border-radius: 10px; margin: 0px auto;margin-top: 20px;margin-bottom: 20px;background: #f7f7f7; width: 100 %;padding: 33px;list-style-type: inherit;">'
|
||||
+ '<li style="color:red;">You must use and understand this feature to decide if you want to open it!</li>'
|
||||
+ '<li>After opening, access the panel in any way, you will be asked to enter the BasicAuth username and password first.</li>'
|
||||
+ '<li>After being turned on, it can effectively prevent the panel from being scanned and found, but it cannot replace the account password of the panel itself.</li>'
|
||||
+ '<li>Please remember the BasicAuth password, but forget that you will not be able to access the panel.</li>'
|
||||
+ '<li>If you forget your password, you can disable BasicAuth authentication by using the bt command in SSH.</li>'
|
||||
m_html = '<div class="risk_form"><i class="layui-layer-ico layui-layer-ico3"></i>'
|
||||
+ '<h3 class="risk_tilte">Warning! Do not understand this feature, do not open!</h3>'
|
||||
+ '<ul>'
|
||||
+ '<li style="color:red;">You must use and understand this feature to decide if you want to open it!</li>'
|
||||
+ '<li>After opening, access the panel in any way, you will be asked to enter the BasicAuth username and password first.</li>'
|
||||
+ '<li>After being turned on, it can effectively prevent the panel from being scanned and found, but it cannot replace the account password of the panel itself.</li>'
|
||||
+ '<li>Please remember the BasicAuth password, but forget that you will not be able to access the panel.</li>'
|
||||
+ '<li>If you forget your password, you can disable BasicAuth authentication by using the bt command in SSH.</li>'
|
||||
+ '</ul></div>'
|
||||
+ '<div class="details">'
|
||||
+ '<input type="checkbox" id="check_basic"><label style="font-weight: 400;margin: 3px 5px 0px;" for="check_basic">I already know the details and are willing to take risks</label>'
|
||||
+ '<a target="_blank" class="btlink" href="https://www.bt.cn/bbs/thread-34374-1-1.html">What is BasicAuth authentication?</a><p></p></div>'
|
||||
var loadT = layer.confirm(m_html, { title: "Risk reminder", area: "600px" }, function () {
|
||||
+ '<input type="checkbox" id="check_basic"><label style="font-weight: 400;margin: 3px 10px 0px;font-size:12px;" for="check_basic">I already know the details and are willing to take risks</label>'
|
||||
+ '<a target="_blank" style="font-size:12px;" class="btlink" href="https://www.bt.cn/bbs/thread-34374-1-1.html">What is BasicAuth authentication?</a><p></p></div>'
|
||||
var loadT = layer.confirm(m_html, { title: "Risk reminder", area: "600px",closeBtn:2 }, function () {
|
||||
if (!$("#check_basic").prop("checked")) {
|
||||
layer.msg("Please read the precautions carefully and check to agree to take risks!");
|
||||
setTimeout(function () { modify_basic_auth();},3000)
|
||||
@@ -683,7 +849,6 @@ function modify_basic_auth() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function show_basic_auth(rdata) {
|
||||
layer.open({
|
||||
type: 1,
|
||||
|
||||
@@ -64,7 +64,7 @@ function getCronData(){
|
||||
<td>"+s_status+"</td>\
|
||||
<td>"+rdata[i].type+"</td>\
|
||||
<td>"+rdata[i].cycle+"</td>\
|
||||
<td>"+(rdata[i].save?rdata[i].save+'份':'-')+"</td>\
|
||||
<td>"+(rdata[i].save?rdata[i].save:'-')+"</td>\
|
||||
<td>"+optName+"</td>\
|
||||
<td>"+rdata[i].addtime+"</td>\
|
||||
<td>\
|
||||
|
||||
@@ -419,5 +419,15 @@ var database = {
|
||||
});
|
||||
}, 100)
|
||||
})
|
||||
},
|
||||
remove_input_file: function (fileName,name) {
|
||||
layer.confirm(lan.get('recycle_bin_confirm', [fileName]), { title: lan.files.del_file, closeBtn: 2, icon: 3 }, function (index) {
|
||||
layer.msg(lan.public.the, { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/files?action=DeleteFile', 'path=' + encodeURIComponent(fileName), function (rdata) {
|
||||
layer.close(index);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
database.input_database(name);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3808,10 +3808,17 @@ bt.soft = {
|
||||
if(type == undefined) type = 0;
|
||||
if(search == undefined) search = '';
|
||||
var force = bt.get_cookie('force');
|
||||
if(force == undefined) force = 0;
|
||||
var loading = bt.load(lan.public.the,1);
|
||||
bt.send('get_soft_list','plugin/get_soft_list',{p:p,type:type,tojs:'soft.get_list',force:force,query:search},function(rdata){
|
||||
loading.close();
|
||||
if (force == undefined) force = 0;
|
||||
p = p + ''
|
||||
if (p.indexOf('not_load') == -1) {
|
||||
var loading = bt.load(lan.public.the, 1);
|
||||
} else {
|
||||
var loading = null;
|
||||
p = p.split("not_load")[0];
|
||||
}
|
||||
|
||||
bt.send('get_soft_list', 'plugin/get_soft_list', { p: p, type: type, tojs: 'soft.get_list', force: force, query: search }, function (rdata) {
|
||||
if (loading) loading.close();
|
||||
bt.set_cookie('force',0);
|
||||
if(callback) callback(rdata);
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
var soft = {
|
||||
get_list: function(page, type, search) {
|
||||
is_install: false,
|
||||
get_list: function (page, type, search) {
|
||||
if (page == undefined) page = 0;
|
||||
if (type == undefined) type = 0;
|
||||
|
||||
@@ -14,9 +15,8 @@ var soft = {
|
||||
soft.get_dep_list(1)
|
||||
return;
|
||||
}
|
||||
|
||||
soft.is_install = false;
|
||||
bt.soft.get_soft_list(page, type, search, function (rdata) {
|
||||
|
||||
if (rdata.pro >= 0) {
|
||||
$("#updata_pro_info").html('');
|
||||
} else if (rdata.pro === -2) {
|
||||
@@ -228,7 +228,7 @@ var soft = {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (item.setup) {
|
||||
if (item.setup && item.task == '1') {
|
||||
if (pay_opt == '') {
|
||||
if (item.versions.length > 1) {
|
||||
for (var i = 0; i < item.versions.length; i++) {
|
||||
@@ -258,9 +258,15 @@ var soft = {
|
||||
}
|
||||
else if (item.task == '-1') {
|
||||
option = '<a class="btlink" onclick="messagebox()" >' + lan.soft.installing + '</a>';
|
||||
soft.is_install = true;
|
||||
}
|
||||
else if (item.task == '0') {
|
||||
option = '<a class="btlink" onclick="messagebox()" >' + lan.soft.wait_install + '</a>';
|
||||
soft.is_install = true;
|
||||
}
|
||||
else if (item.task == '-2') {
|
||||
option = '<a class="btlink" onclick="messagebox()" >Updating</a>';
|
||||
soft.is_install = true;
|
||||
}
|
||||
else {
|
||||
if (pay_opt) {
|
||||
@@ -277,6 +283,14 @@ var soft = {
|
||||
],
|
||||
data: data
|
||||
})
|
||||
bt.set_cookie('load_page', (page+'').split('not_load')[0])
|
||||
bt.set_cookie('load_type', type)
|
||||
bt.set_cookie('load_search', search)
|
||||
if (soft.is_install) {
|
||||
setTimeout(function () {
|
||||
soft.get_list(page + 'not_load', type, search);
|
||||
}, 3000);
|
||||
}
|
||||
})
|
||||
},
|
||||
get_dep_list: function (p) {
|
||||
|
||||
@@ -346,7 +346,7 @@
|
||||
"N3":"Account",
|
||||
"N4":"Password",
|
||||
"N5":"Password",
|
||||
"N6":"Passowrd",
|
||||
"N6":"Password",
|
||||
"N7":"Verification Code",
|
||||
"N8":"Verification Code Error",
|
||||
"N9":"Verification Error",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -52,6 +52,13 @@
|
||||
<label class='btswitch-btn' for='panelLocal' onclick="set_local()"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ss-text pull-left mr50">
|
||||
<em title="基于Google Authenticator对用户进行登录认证"><a href="javascript:;" class="btlink open_two_verify_view">Google authentication</a></em>
|
||||
<div class='ssh-item'>
|
||||
<input class='btswitch btswitch-ios' id='panel_verification' type='checkbox'/>
|
||||
<label class='btswitch-btn' for='panel_verification'></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setbox bgw mtb15">
|
||||
@@ -257,32 +264,104 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personalDetails .userList .addweChat {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
color: #20a53a;
|
||||
font-size: 16px;
|
||||
}
|
||||
.personalDetails .userList .addweChat {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
color: #20a53a;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.personalDetails .userList .addweChat:hover {
|
||||
}
|
||||
.personalDetails .userList .item {
|
||||
height: 70px;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #ececec;
|
||||
margin: 15px 65px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.personalDetails .userList .item {
|
||||
height: 70px;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #ececec;
|
||||
margin: 15px 65px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.personalDetails .userList .cancelBind {
|
||||
height: 50px;
|
||||
width: 60px;
|
||||
float: right;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
.verify_title{
|
||||
color: #555;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.verify_item{
|
||||
padding: 0 35px;
|
||||
}
|
||||
.verify_item .verify_vice_title{
|
||||
font-size: 15.5px;
|
||||
color: #666;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.verify_tips{
|
||||
margin: 0 15px;
|
||||
margin-top: 25px;
|
||||
padding: 20px 25px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
border-top: 1px solid #ececec;
|
||||
}
|
||||
.verify_tips p{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.verify_box{
|
||||
background: #f8f8f8;
|
||||
padding: 15px 25px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
/* width: 200px; */
|
||||
}
|
||||
.verify_box .verify_box_line{
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 14px;
|
||||
color: #444;
|
||||
font-weight: 500;
|
||||
}
|
||||
.verify_box .verify_box_line span{
|
||||
color: #666;
|
||||
}
|
||||
.google_verify ul li{
|
||||
height: auto;
|
||||
line-height: 18px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.risk_form{
|
||||
|
||||
}
|
||||
|
||||
.risk_form ul{
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 10px;
|
||||
font-size:12px;
|
||||
margin: 0px auto;
|
||||
margin:15px 0;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: #f7f7f7;
|
||||
width: 100%;
|
||||
padding: 25px 40px;
|
||||
list-style-type: inherit;
|
||||
}
|
||||
.risk_form ul li{
|
||||
line-height: 18px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.risk_tilte{
|
||||
margin: 0 0 10px 35px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.personalDetails .userList .cancelBind {
|
||||
height: 50px;
|
||||
width: 60px;
|
||||
float: right;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="/static/js/jquery.qrcode.min.js"></script>
|
||||
<script src="/static/js/config.js?date={{g['version']}}"></script>
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="/static/js/crontab.js?date={{g['version']}}"></script>
|
||||
<script src="/static/js/crontab.js?version={{g['version']}}"></script>
|
||||
<script type="text/javascript">
|
||||
setCookie('serverType',"{{session['webserver']}}");
|
||||
toWeek();
|
||||
|
||||
@@ -9,153 +9,8 @@
|
||||
<title>{{g.title}}</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/site.css?date={{g.version}}">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/login.css?date={{g.version}}">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style type="text/css">
|
||||
@media screen and (max-width: 640px) {
|
||||
.scanCode {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.account {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.bg_img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bg_img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-image: url('/static/img/qrCode.png');
|
||||
background-size: 120px 120px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.bg_img:hover {
|
||||
background-position: -60px 0;
|
||||
cursor: pointer;
|
||||
/*transition: all 500ms;*/
|
||||
}
|
||||
|
||||
.bg_img.pc {
|
||||
background-position: 0 -60px;
|
||||
}
|
||||
|
||||
.bg_img.pc:hover {
|
||||
background-position: -60px -60px;
|
||||
}
|
||||
|
||||
.qrCode {
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.scanCode .titles {
|
||||
font-size: 23px;
|
||||
margin-top: 25px;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list_scan {
|
||||
width: 150px;
|
||||
margin: 0 auto;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.list_scan > img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.list_scan span {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.list_scan a:hover .weChatSamll {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.list_scan .weChatSamll {
|
||||
display: none;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
position: absolute;
|
||||
border: 1px solid #ececec;
|
||||
border-radius: 5px;
|
||||
bottom: 105px;
|
||||
right: 80px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.list_scan .weChatSamll img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list_scan .weChatSamll em {
|
||||
position: absolute;
|
||||
border: 7px solid #ececec;
|
||||
border-color: #ececec transparent transparent transparent;
|
||||
width: 0;
|
||||
height: 0;
|
||||
right: 87px;
|
||||
bottom: -14px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
width: 115px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 65px;
|
||||
color: rgb(32, 165, 58);
|
||||
background: #dff0d8;
|
||||
padding: 5px 10px;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tips em {
|
||||
position: absolute;
|
||||
border: 6px solid #dff0d8;
|
||||
border-color: transparent transparent transparent #dff0d8;
|
||||
width: 0;
|
||||
height: 0;
|
||||
right: -11px;
|
||||
top: 8px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.tips img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
margin-top: -1px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.main .login .rlogo {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.entrance{
|
||||
displia
|
||||
}
|
||||
</style>
|
||||
<div class="main">
|
||||
<div class="login">
|
||||
<div class="account">
|
||||
@@ -164,11 +19,11 @@
|
||||
<div class="line"><input class="inputtxt" value="" name="username" datatype="*" nullmsg="{{data['lan']['N1']}}" errormsg="{{data['lan']['N2']}}" placeholder="{{data['lan']['N3']}}" type="text"><div class="Validform_checktip"></div></div>
|
||||
<div class="line"><input class="inputtxt" name="password" value="" datatype="*" nullmsg="{{data['lan']['N4']}}" errormsg="{{data['lan']['N5']}}" placeholder="{{data['lan']['N6']}}" type="password"><div class="Validform_checktip"></div></div>
|
||||
<div style="color: red;position: relative;top: -14px;" id="errorStr"></div>
|
||||
<div class="line yzm" style="top: -5px;
|
||||
{% if not session['code'] %}
|
||||
display:none;
|
||||
{% endif %}
|
||||
">
|
||||
<div class="line yzm" style="top: -5px;
|
||||
{% if not session['code'] %}
|
||||
display:none;
|
||||
{% endif %}
|
||||
">
|
||||
<input type="text" class="inputtxt" name="code" nullmsg="{{data['lan']['N7']}}" errormsg="{{data['lan']['N8']}}" datatype="*" placeholder="{{data['lan']['N9']}}">
|
||||
<div class="Validform_checktip"></div>
|
||||
<img width="100" height="40" class="passcode" onClick="this.src=this.src.split('?')[0] + '?'+new Date().getTime()" src="/code" style="border: 1px solid #ccc; float: right;" title="{{data['lan']['N10']}}">
|
||||
@@ -205,7 +60,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/static/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="/static/language/zh-cn.js"></script>
|
||||
<script type="text/javascript" src="/static/language/zh-cn.js"></script>
|
||||
<script type="text/javascript" src="/static/layer/layer.js"></script>
|
||||
<script type="text/javascript" src="/static/js/jquery.qrcode.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/Validform_v5.3.2_min.js"></script>
|
||||
@@ -225,21 +80,6 @@
|
||||
$(function () {
|
||||
Wreset();
|
||||
var setTime = '';
|
||||
// $.get('/public?name=app&fun=login_qrcode', function (res) {
|
||||
// if (res.status) {
|
||||
// $('#qrcode').qrcode({
|
||||
// render: "canvas",
|
||||
// width: 150,
|
||||
// height: 150,
|
||||
// text: res.msg
|
||||
// });
|
||||
// $('.entrance').show();
|
||||
// }
|
||||
// }).error(function (res, textStatus, errorThrown) {
|
||||
// if (res.status == 404) {
|
||||
// $('.entrance').hide();
|
||||
// }
|
||||
// });
|
||||
function controlTime() {
|
||||
$.get('/public?name=app&fun=is_scan_ok', function (res) {
|
||||
if (res.status) {
|
||||
@@ -303,11 +143,47 @@
|
||||
var data = { username: username, password: md5(md5(password) + '' + '_bt.cn'), code: code }
|
||||
var loadT = layer.msg("{{data['lan']['JS2']}}", { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/login', data, function (rdata) {
|
||||
if(rdata == '1'){
|
||||
layer.close(loadT);
|
||||
layer.open({
|
||||
type:1,
|
||||
title:false,
|
||||
area: ['350px', '285px'],
|
||||
content:'<div class="verification_view">\
|
||||
<div class="v_title">Google authentication</div>\
|
||||
<div class="v_input"><input type="text" class="v_code" name="v_code" placeholder="Verification code"/></div>\
|
||||
<div class="v_btn"><input id="auth_verif_btn" value="log in" type="submit"></div>\
|
||||
<div class="v_tips">* Please use the Google Authenticator app to get the verification code.<br><a target="_blank" href="https://www.bt.cn/bbs/forum.php?mod=viewthread&tid=37437" class="btlink">Unable to verify, click help</a></div>\
|
||||
</div>',
|
||||
success:function(){
|
||||
// 点击验证登录
|
||||
$('[name="v_code"]').keyup(function(e){
|
||||
if(e.keyCode == 13){
|
||||
$('#auth_verif_btn').click();
|
||||
}
|
||||
});
|
||||
$('#auth_verif_btn').click(function(e){
|
||||
data['vcode'] = $('[name="v_code"]').val();
|
||||
var loadT = layer.msg("{{data['lan']['JS2']}}", { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/login', data, function (rdata) {
|
||||
layer.close(loadT);
|
||||
if(!rdata.status){
|
||||
layer.msg(rdata.msg, { icon: 2 });
|
||||
return false;
|
||||
}
|
||||
layer.msg(rdata.msg, { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
window.location.href = '/';
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
layer.close(loadT);
|
||||
if (!rdata.status) {
|
||||
$("#errorStr").html(rdata.msg);
|
||||
$("input[name='password']").val('');
|
||||
num = rdata.msg.substring(rdata.msg.indexOf('[') + 1, rdata.msg.indexOf(']'))
|
||||
num = rdata.msg.substring(rdata.msg.indexOf('[') + 1, rdata.msg.indexOf(']'));
|
||||
if (num < 5) {
|
||||
$(".yzm").show();
|
||||
$(".login").css("height", "332px");
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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'))
|
||||
@@ -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)
|
||||
@@ -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"))
|
||||
@@ -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
@@ -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
|
||||
@@ -409,14 +409,15 @@ def bt_cli(u_input = 0):
|
||||
print("(7) %s (14) %s"% (public.GetMsg("CHANGE_MYSQL_PASS_FORCE"),public.GetMsg("GET_PANEL_DEFAULT_MSG")))
|
||||
print("(22) %s (15) %s"% ("Display panel error log",public.GetMsg("CLEAR_SYS_RUBBISH")))
|
||||
print("(23) %s (16) %s"% ("Turn off BasicAuth authentication","Repair panel (check for errors and update panel files to the latest version)"))
|
||||
print("(0) Cancel (17) Set log cutting on/off compression")
|
||||
print("(24) Turn off Google Authenticator (17) Set log cutting on/off compression")
|
||||
print("(0) Cancel")
|
||||
print(raw_tip)
|
||||
try:
|
||||
u_input = input(public.GetMsg("INPUT_CMD_NUM"))
|
||||
if sys.version_info[0] == 3: u_input = int(u_input)
|
||||
except: u_input = 0
|
||||
|
||||
nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,22,23]
|
||||
nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,22,23,24]
|
||||
if not u_input in nums:
|
||||
print(raw_tip)
|
||||
print(public.GetMsg("CANCELLED"))
|
||||
@@ -543,6 +544,10 @@ def bt_cli(u_input = 0):
|
||||
if os.path.exists(filename): os.remove(filename)
|
||||
os.system('bt reload')
|
||||
print("|-BasicAuth authentication has been turned off")
|
||||
elif u_input == 24:
|
||||
filename = '/www/server/panel/data/two_step_auth.txt'
|
||||
if os.path.exists(filename): os.remove(filename)
|
||||
print("|-Google Authenticator has been turned off")
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user