#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Windows面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# +-------------------------------------------------------------------
import os, sys
panelPath = os.getenv('BT_PANEL')

os.chdir(panelPath)
if not panelPath + "/class/" in sys.path:
    sys.path.insert(0, panelPath + "/class/")

import public,re,apache,json,time,base64


try:
    from flask import request
    from BTPanel import session,admin_path_checks,cache
except : pass

try:
    import pyotp
except :
    os.system(public.get_run_pip("[PIP] install pyotp"))
    import pyotp

class config:
    _setup_path = os.getenv('BT_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'
    __mail_list = []
    __weixin_user = []

    def __init__(self):
        pass

    def install_msg_module(self,get):
        """
        安装/更新消息通道模块
        @name 需要安装的模块名称
        """
        module_name = get.name
        down_url = public.get_url()

        local_path = '{}/class/msg'.format(panelPath)
        if not os.path.exists(local_path): os.makedirs(local_path)

        sfile = '{}/{}_msg.py'.format(local_path,module_name)
        public.downloadFileByWget('{}/win/panel/msg/{}_msg.py'.format(down_url,module_name),sfile)
        if not os.path.exists(sfile): return public.returnMsg(False, '【{}】模块安装失败'.format(module_name))
        if os.path.getsize(sfile) < 1024: return public.returnMsg(False, '【{}】模块安装失败'.format(module_name))

        sfile = '{}/class/msg/{}.html'.format(panelPath,module_name)
        public.downloadFileByWget('{}/win/panel/msg/{}.html'.format(down_url,module_name),sfile)

        public.set_module_logs('msg_push','install_module',1)
        return public.returnMsg(True, '【{}】模块安装成功.'.format(module_name))



    def uninstall_msg_module(self,get):
        """
        卸载消息通道模块
        @name 需要卸载的模块名称
        """
        module_name = get.name
        sfile = '{}/class/msg/{}_msg.py'.format(panelPath,module_name)
        if os.path.exists(sfile): os.remove(sfile)

        return public.returnMsg(True, '【{}】模块卸载成功'.format(module_name))


    def get_module_template(self,get):
        """
        获取模块模板
        """
        sfile = '{}/class/msg/{}.html'.format(panelPath,get.module_name)
        if not os.path.exists(sfile):
            return public.returnMsg(False, '模板文件不存在.')

        shtml = public.readFile(sfile)
        return public.returnMsg(True, shtml)

    def set_msg_config(self,get):
        """
        设置消息通道配置
        """
        obj =  public.init_msg(get.name)
        if not obj: return public.returnMsg(False, '设置失败，【{}】未安装'.format(get.name))
        return obj.set_config(get)

    def get_msg_fun(self,get):
        """
        @获取消息模块指定方法
        """
        module_name = get.module_name
        fun_name = get.fun_name

        m_objs = public.init_msg(module_name)
        if not m_objs: return public.returnMsg(False, '设置失败，【{}】未安装'.format(module_name))

        return getattr(m_objs,fun_name)(get)


    def get_msg_configs_by(self,get):
        """
        获取单独消息通道配置
        """
        name = get.name
        res = {}
        res['data'] = {}
        res['setup'] = False
        res['info'] = False

        try:
            obj =  public.init_msg(name)
            if obj:
                res['setup'] = True
                res['data'] = obj.get_config(None)
                res['info'] = obj.get_version_info(None);
        except: pass
        print(public.check_auth_ip())
        return res


    def get_msg_push_list(self,get):
        """
        获取消息通道配置列表
        """
        cpath = 'data/msg.json'
        try:
            if 'force' in get or not os.path.exists(cpath):
                if not 'download_url' in session: session['download_url'] = public.get_url()
                public.downloadFileByWget('{}/win/panel/msg/msg.json'.format(session['download_url']),cpath)
        except : pass

        data = {}
        if os.path.exists(cpath):
            msgs = json.loads(public.readFile(cpath))

            for x in msgs:
                x['setup'] = False
                x['info'] = False
                key = x['name']
                try:
                    obj =  public.init_msg(x['name'])
                    if obj:
                        x['setup'] = True
                        x['info'] = obj.get_version_info(None);

                except :
                    print(public.get_error_info())
                    pass
                data[key] = x
        return data

    def get_msg_configs(self,get):
        """
        获取消息通道配置列表
        """
        cpath = 'data/msg.json'
        try:
            if 'force' in get or not os.path.exists(cpath):
                if not 'download_url' in session: session['download_url'] = public.get_url()
                public.downloadFileByWget('{}/win/panel/msg/msg.json'.format(session['download_url']),cpath)
        except : pass

        data = {}
        if os.path.exists(cpath):
            msgs = json.loads(public.readFile(cpath))

            for x in msgs:
                x['data'] = {}
                x['setup'] = False
                x['info'] = False
                key = x['name']
                try:
                    obj =  public.init_msg(x['name'])
                    if obj:
                        x['setup'] = True
                        x['data'] = obj.get_config(None)
                        x['info'] = obj.get_version_info(None);

                except :
                    print(public.get_error_info())
                    pass
                data[key] = x
        return data

    def getPanelState(self,get):
        return os.path.exists(os.getenv("BT_PANEL") + '/data/close.pl');

    def reload_session(self):

        userInfo = public.M('users').where("id=?",(1,)).field('username,password').find()
        token = public.Md5(userInfo['username'] + '/' + userInfo['password'])
        public.writeFile('data/login_token.pl',token)

        skey = 'login_token'
        cache.set(skey,token)

        sess_path = 'data/sess_files'
        if not os.path.exists(sess_path): os.makedirs(sess_path,384)

        self.clean_sess_files(sess_path)
        sess_key = public.get_sess_key()
        sess_file = os.path.join(sess_path,sess_key)

        public.writeFile(sess_file,str(int(time.time()+86400)))
        session['login_token'] = token


    def clean_sess_files(self,sess_path):
        '''
            @name 清理过期的sess_file
            @auther hwliang<2020-07-25>
            @param sess_path(string) sess_files目录
            @return void
        '''
        s_time = time.time()
        for fname in os.listdir(sess_path):
            try:
                if len(fname) != 32: continue
                sess_file = os.path.join(sess_path,fname)
                if not os.path.isfile(sess_file): continue
                sess_tmp = public.ReadFile(sess_file)
                if not sess_tmp:
                    if os.path.exists(sess_file):
                        os.remove(sess_file)
                if s_time > int(sess_tmp):
                    os.remove(sess_file)
            except:
                pass

    def setPassword(self,get):
        get.password1 = public.url_decode(public.rsa_decrypt(get.password1))
        get.password2 = public.url_decode(public.rsa_decrypt(get.password2))

        if get.password1 != get.password2: return public.returnMsg(False,'USER_PASSWORD_CHECK')
        if len(get.password1) < 5: return public.returnMsg(False,'USER_PASSWORD_LEN')
        if not self.check_password_safe(get.password1): return public.returnMsg(False,'密码复杂度验证失败，要求：长度大于8位，数字、大写字母、小写字母、特殊字符最少3项组合')
        public.M('users').where("username=?",(session['username'],)).setField('password',public.password_salt(public.md5(get.password1.strip()),username=session['username']))
        public.WriteLog('TYPE_PANEL','USER_PASSWORD_SUCCESS',(session['username'],))
        self.reload_session()

        # 密码过期时间
        expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl'
        if os.path.exists(expire_time_file): os.remove(expire_time_file)
        self.get_password_config(None)
        if session.get('password_expire',False):
            session['password_expire'] = False
        return public.returnMsg(True,'USER_PASSWORD_SUCCESS')

    def setUsername(self,get):
        get.username1 = public.url_decode(public.rsa_decrypt(get.username1))
        get.username2 = public.url_decode(public.rsa_decrypt(get.username2))

        if get.username1 != get.username2: return public.returnMsg(False,'USER_USERNAME_CHECK')
        if len(get.username1) < 3: return public.returnMsg(False,'USER_USERNAME_LEN')
        public.M('users').where("username=?",(session['username'],)).setField('username',get.username1.strip())
        public.WriteLog('TYPE_PANEL','USER_USERNAME_SUCCESS',(session['username'],get.username2))
        session['username'] = get.username1
        self.reload_session()
        return public.returnMsg(True,'USER_USERNAME_SUCCESS')

    def check_dir(self,path):
        nDirs = ('C:/Windows', 'C:/Windows/System32', public.format_path(public.get_soft_path()),public.format_path(public.get_panel_path()))
        for dir in nDirs:
            print(path,dir)
            if(public.format_path(path).upper().find(dir.upper()) >= 0):
                return False
        return True


    def setPanel(self,get):

        if not public.IsRestart(): return public.returnMsg(False,'EXEC_ERR_TASK');
        if get.admin_path == '': get.admin_path = '/'
        if get.admin_path != '/':
            if len(get.admin_path) < 6: return public.returnMsg(False,'安全入口地址长度不能小于6位!')
            if get.admin_path in admin_path_checks: return public.returnMsg(False,'该入口已被面板占用,请使用其它入口!')
            if not re.match("^/[\w\./-_]+$",get.admin_path):  return public.returnMsg(False,'入口地址格式不正确,示例: /my_panel')
            if get.admin_path[0] != '/': return public.returnMsg(False,'入口地址格式不正确,示例: /my_panel')

        if get.domain:
            reg = "^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$";
            if not re.match(reg, get.domain): return public.returnMsg(False,'SITE_ADD_ERR_DOMAIN');
        isReWeb = False

        oldPort = public.readFile('data/port.pl');
        if not 'port' in get:
            get.port = oldPort

        newPort = get.port;
        if not re.match('^\d+$',get.port):
            return public.returnMsg(False,get.port + '不是有效的端口(1-65535)!')

        if oldPort != get.port:
            get.port = str(int(get.port))
            if self.IsOpen(get.port):
                return public.returnMsg(False,'PORT_CHECK_EXISTS',(get.port,))
            if int(get.port) >= 65535 or  int(get.port) < 100: return public.returnMsg(False,'PORT_CHECK_RANGE');
            public.writeFile('data/port.pl',get.port)
            import firewalls
            get.ps = public.getMsg('PORT_CHECK_PS');
            fw = firewalls.firewalls();
            fw.AddAcceptPort(get);
            get.port = oldPort;
            get.id = public.M('firewall').where("port=?",(oldPort,)).getField('id');
            fw.DelAcceptPort(get);
            isReWeb = True

        if get.webname != session['title']:
            session['title'] = get.webname
            public.SetConfigValue('title',get.webname)

        admin_path_file = 'data/admin_path.pl'
        admin_path = '/'
        if os.path.exists(admin_path_file): admin_path = public.readFile(admin_path_file).strip()
        if get.admin_path != admin_path:
            public.writeFile(admin_path_file,get.admin_path)
            isReWeb = True

        limitip = public.readFile('data/limitip.conf');
        if get.limitip != limitip: public.writeFile('data/limitip.conf',get.limitip);

        public.writeFile('data/domain.conf',get.domain.strip())
        public.writeFile('data/iplist.txt',get.address)

        if len(get.backup_path.strip()) <= 3 or len(get.sites_path.strip()) <= 3:
            return public.returnMsg(False,'网站路径和备份路径不能以磁盘为目录.')

        if not self.check_dir(get.backup_path.strip()) or not self.check_dir(get.sites_path.strip()):
            return public.returnMsg(False,'网站路径和备份路径不合法，请重新填写.')

        public.M('config').where("id=?",('1',)).save('backup_path,sites_path',(get.backup_path,get.sites_path))
        session['config']['backup_path'] = get.backup_path
        session['config']['sites_path'] = get.sites_path

        public.SetConfigValue('root_path',get.sites_path)
        if not os.path.exists(get.backup_path):
            os.makedirs(get.backup_path)
            public.set_file_access(get.backup_path,"IIS_IUSRS",public.file_exec)
            public.set_file_access(get.backup_path,"www",public.file_exec)

            sql_path = get.backup_path + '/database/sqlserver'
            if not os.path.exists(sql_path): os.makedirs(sql_path)
            public.set_file_access(sql_path,"Authenticated Users",public.file_all)

        if not os.path.exists(get.sites_path):
            os.makedirs(get.sites_path)
            public.set_file_access(get.sites_path,"IIS_IUSRS",public.file_exec)
            public.set_file_access(get.sites_path,"www",public.file_exec)

        mhost = public.GetHost()
        if get.domain.strip(): mhost = get.domain
        data = {'uri':request.path,'host':mhost+':'+newPort,'status':True,'isReWeb':isReWeb,'msg':public.getMsg('PANEL_SAVE')}
        public.WriteLog('TYPE_PANEL','PANEL_SAVE',(newPort,get.domain,get.backup_path,get.sites_path,get.address,get.limitip))
        if isReWeb: public.restart_panel()
        return data


    def set_admin_path(self,get):
        get.admin_path = public.rsa_decrypt(get.admin_path.strip()).strip()

        if get.admin_path == '': get.admin_path = '/'
        if get.admin_path != '/':
            if len(get.admin_path) < 6: return public.returnMsg(False,'安全入口地址长度不能小于6位!')
            if get.admin_path in admin_path_checks: return public.returnMsg(False,'该入口已被面板占用,请使用其它入口!')
            if not re.match("^/[\w\./-_]+$",get.admin_path):  return public.returnMsg(False,'入口地址格式不正确,示例: /my_panel')
        else:
            get.domain = public.readFile('data/domain.conf')
            if not get.domain: get.domain = '';
            get.limitip = public.readFile('data/limitip.conf')
            if not get.limitip: get.limitip = '';
            if not get.domain.strip() and not get.limitip.strip(): return public.returnMsg(False,'警告，关闭安全入口等于直接暴露你的后台地址在外网，十分危险，至少开启以下一种安全方式才能关闭：<a style="color:red;"><br>1、绑定访问域名<br>2、绑定授权IP</a>')

        admin_path_file = 'data/admin_path.pl'
        admin_path = '/'
        if os.path.exists(admin_path_file): admin_path = public.readFile(admin_path_file).strip()
        if get.admin_path != admin_path:
            public.writeFile(admin_path_file,get.admin_path)
            public.restart_panel()
        return public.returnMsg(True,'修改成功!');

    def setPathInfo(self,get):
        #设置PATH_INFO
        version = get.version
        type = get.type

        path = public.GetConfigValue('setup_path')+'/php/'+version+'/php.ini';
        conf = public.readFile(path);
        rep = "\n*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n";
        status = '0'
        if type == 'on':status = '1'
        conf = re.sub(rep,"\ncgi.fix_pathinfo = "+status+"\n",conf)
        public.writeFile(path,conf)
        public.WriteLog("TYPE_PHP", "PHP_PATHINFO_SUCCESS",(version,type));
        public.phpReload(version);
        return public.returnMsg(True,'SET_SUCCESS');

    #设置文件上传大小限制
    def setPHPMaxSize(self,get):
        version = get.version
        max = get.max

        if int(max) < 2: return public.returnMsg(False,'PHP_UPLOAD_MAX_ERR')

        #设置PHP
        path = public.GetConfigValue('setup_path')+'/php/'+version+'/php.ini'
        conf = public.readFile(path)
        rep = u"\nupload_max_filesize\s*=\s*[0-9]+M"
        conf = re.sub(rep,u'\nupload_max_filesize = '+max+'M',conf)
        rep = u"\npost_max_size\s*=\s*[0-9]+M"
        conf = re.sub(rep,u'\npost_max_size = '+max+'M',conf)
        public.writeFile(path,conf)

        if public.get_webserver() == 'iis':
            max_limit = int(max)
            if max_limit > 400: max_limit = 400

            shell = '%s set config -section:requestFiltering -requestLimits.maxAllowedContentLength:%s -commitpath:apphost' % (self.get_appcmd(),max_limit * 1024 * 1024)
            os.system(shell)

        public.serviceReload()
        public.phpReload(version);
        public.WriteLog("TYPE_PHP", "PHP_UPLOAD_MAX",(version,max))
        return public.returnMsg(True,'SET_SUCCESS')

    #设置禁用函数
    def setPHPDisable(self,get):
        filename = public.GetConfigValue('setup_path') + '/php/' + get.version + '/php.ini'
        if not os.path.exists(filename): return public.returnMsg(False,'PHP_NOT_EXISTS');
        phpini = public.readFile(filename);
        rep = "disable_functions\s*=\s*.*\n"
        phpini = re.sub(rep, 'disable_functions = ' + get.disable_functions + "\n", phpini);
        public.WriteLog('TYPE_PHP','PHP_DISABLE_FUNCTION',(get.version,get.disable_functions))
        public.writeFile(filename,phpini);
        public.phpReload(get.version);
        return public.returnMsg(True,'SET_SUCCESS');

    def get_appcmd(self):
        appcmd = os.getenv("SYSTEMDRIVE") + '\\Windows\\System32\\inetsrv\\appcmd.exe'
        return appcmd

    #设置PHP超时时间
    def setPHPMaxTime(self,get):
        time = get.time
        version = get.version;
        if int(time) < 30 or int(time) > 86400: return public.returnMsg(False,'PHP_TIMEOUT_ERR');

        file = public.GetConfigValue('setup_path') + '/php/'+version+'/php.ini';
        phpini = public.readFile(file);
        rep = "max_execution_time\s*=\s*([0-9]+)\r?\n";
        phpini = re.sub(rep,"max_execution_time = "+time+"\n",phpini);
        rep = "max_input_time\s*=\s*([0-9]+)\r?\n";
        phpini = re.sub(rep,"max_input_time = "+time+"\n",phpini);
        public.writeFile(file,phpini)

        if public.get_webserver() == 'iis':
            cgiPath = public.to_path(public.GetConfigValue('setup_path') + '/php/'+ version +'/php-cgi.exe')
            public.ExecShell("%s set config -section:system.webServer/fastCgi /[fullPath='%s',arguments=''].activityTimeout:%s /commit:apphost" % (self.get_appcmd(),cgiPath,get.time))
            public.ExecShell("%s set config -section:system.webServer/fastCgi /[fullPath='%s',arguments=''].requestTimeout:%s  /commit:apphost" % (self.get_appcmd(),cgiPath,get.time))

        public.WriteLog("TYPE_PHP", "PHP_TIMEOUT",(version,time));
        public.serviceReload()
        public.phpReload(version);
        return public.returnMsg(True, 'SET_SUCCESS');

    #取FPM设置
    def getFpmConfig(self,get):
        version = get.version;
        file = public.GetConfigValue('setup_path')+"/php/"+version+"/etc/php-fpm.conf";
        conf = public.readFile(file);
        data = {}
        rep = "\s*pm.max_children\s*=\s*([0-9]+)\s*";
        tmp = re.search(rep, conf).groups();
        data['max_children'] = tmp[0];

        rep = "\s*pm.start_servers\s*=\s*([0-9]+)\s*";
        tmp = re.search(rep, conf).groups();
        data['start_servers'] = tmp[0];

        rep = "\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*";
        tmp = re.search(rep, conf).groups();
        data['min_spare_servers'] = tmp[0];

        rep = "\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*";
        tmp = re.search(rep, conf).groups();
        data['max_spare_servers'] = tmp[0];

        rep = "\s*pm\s*=\s*(\w+)\s*";
        tmp = re.search(rep, conf).groups();
        data['pm'] = tmp[0];

        return data

    #设置
    def setFpmConfig(self,get):
        version = get.version
        max_children = get.max_children
        start_servers = get.start_servers
        min_spare_servers = get.min_spare_servers
        max_spare_servers = get.max_spare_servers
        pm = get.pm

        file = public.GetConfigValue('setup_path')+"/php/"+version+"/etc/php-fpm.conf";
        conf = public.readFile(file);

        rep = "\s*pm.max_children\s*=\s*([0-9]+)\s*";
        conf = re.sub(rep, "\npm.max_children = "+max_children, conf);

        rep = "\s*pm.start_servers\s*=\s*([0-9]+)\s*";
        conf = re.sub(rep, "\npm.start_servers = "+start_servers, conf);

        rep = "\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*";
        conf = re.sub(rep, "\npm.min_spare_servers = "+min_spare_servers, conf);

        rep = "\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*";
        conf = re.sub(rep, "\npm.max_spare_servers = "+max_spare_servers+"\n", conf);

        rep = "\s*pm\s*=\s*(\w+)\s*";
        conf = re.sub(rep, "\npm = "+pm+"\n", conf);

        public.writeFile(file,conf)
        public.phpReload(version);
        public.WriteLog("TYPE_PHP",'PHP_CHILDREN', (version,max_children,start_servers,min_spare_servers,max_spare_servers));
        return public.returnMsg(True, 'SET_SUCCESS');

    #同步时间
    def syncDate(self,get):
        dateStr = public.HttpGet(public.GetConfigValue('home') + '/api/index/get_win_date')
        arrs = dateStr.split(' ')
        os.system('date {} && time {}'.format(arrs[0],arrs[1]))

        public.WriteLog("TYPE_PANEL", "DATE_SUCCESS");
        return public.returnMsg(True,"DATE_SUCCESS");

    def IsOpen(self,port):
        #检查端口是否占用
        import socket
        s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        try:
            s.connect(('127.0.0.1',int(port)))
            s.shutdown(2)
            return True
        except:
            return False

    def SetControl(self,get):
        try:
            if hasattr(get,'day'):
                get.day = int(get.day)
                if(get.day < 1):
                    return public.returnMsg(False,"CONTROL_ERR")
        except:
            return public.returnMsg(False,"CONTROL_ERR")

        filename = 'data/control.conf'
        if get.type == '1':
            public.writeFile(filename,str(get.day))
            public.WriteLog("TYPE_PANEL",'CONTROL_OPEN',(str(get.day),))
        elif get.type == '0':
            if os.path.exists(filename): os.remove(filename)
            public.WriteLog("TYPE_PANEL", "CONTROL_CLOSE")
        elif get.type == 'del':
            if not public.IsRestart():
                return public.returnMsg(False,'EXEC_ERR_TASK')
            os.system('net stop btTask')
            os.remove("{}/data/system.db".format(public.get_panel_path()))
            os.remove("{}/data/sql_index.pl".format(public.get_panel_path()))
            import db
            sql = db.Sql()
            sql.dbfile('system').create('system')
            os.system('net start btTask')
            public.WriteLog("TYPE_PANEL", "CONTROL_CLEAR")
            return public.returnMsg(True,"清除成功！")

        else:
            data = {}
            if os.path.exists(filename):
                try:
                    data['day'] = int(public.readFile(filename))
                except:
                    data['day'] = 30
                data['status'] = True
            else:
                data['day'] = 30
                data['status'] = False
            return data

        return public.returnMsg(True,"SET_SUCCESS")

    #关闭面板
    def ClosePanel(self,get):
        filename = 'data/close.pl'
        if os.path.exists(filename):
            os.remove(filename)
            return public.returnMsg(True,'开启成功')
        import firewalls
        data = firewalls.firewalls().GetSshInfo(get)
        print(data)
        if not data['status']:
            return public.returnMsg(False,'请勿同时关闭远程桌面和面板！');
        public.writeFile(filename,'True');
        return public.returnMsg(True,'PANEL_CLOSE');



    #设置二级密码
    def SetPanelLock(self,get):
        path = 'data/lock';
        if not os.path.exists(path):
            public.ExecShell('mkdir ' + path);
            public.ExecShell("chmod 600 " + path);
            public.ExecShell("chown root.root " + path);

        keys = ['files','tasks','config'];
        for name in keys:
            filename = path + '/' + name + '.pl';
            if hasattr(get,name):
                public.writeFile(filename,'True');
            else:
                if os.path.exists(filename): os.remove(filename);


    #设置模板
    def SetTemplates(self,get):
        public.writeFile('data/templates.pl',get.templates);
        return public.returnMsg(True,'SET_SUCCESS');

    #生成Token
    def SetToken(self,get):
        data = {}
        data[''] = public.GetRandomString(24);


    #获取PHP配置参数
    def GetPHPConf(self,get):
        gets = [
                {'name':'short_open_tag','type':1,'ps':public.getMsg('PHP_CONF_1')},
                {'name':'asp_tags','type':1,'ps':public.getMsg('PHP_CONF_2')},
                {'name':'max_execution_time','type':2,'ps':public.getMsg('PHP_CONF_4')},
                {'name':'max_input_time','type':2,'ps':public.getMsg('PHP_CONF_5')},
                {'name':'memory_limit','type':2,'ps':public.getMsg('PHP_CONF_6')},
                {'name':'post_max_size','type':2,'ps':public.getMsg('PHP_CONF_7')},
                {'name':'file_uploads','type':1,'ps':public.getMsg('PHP_CONF_8')},
                {'name':'upload_max_filesize','type':2,'ps':public.getMsg('PHP_CONF_9')},
                {'name':'max_file_uploads','type':2,'ps':public.getMsg('PHP_CONF_10')},
                {'name':'default_socket_timeout','type':2,'ps':public.getMsg('PHP_CONF_11')},
                {'name':'error_reporting','type':3,'ps':public.getMsg('PHP_CONF_12')},
                {'name':'display_errors','type':1,'ps':public.getMsg('PHP_CONF_13')},
                {'name':'cgi.fix_pathinfo','type':0,'ps':public.getMsg('PHP_CONF_14')},
                {'name':'date.timezone','type':3,'ps':public.getMsg('PHP_CONF_15')}
                ]
        phpini = public.readFile(public.GetConfigValue('setup_path') + '/php/' + get.version + '/php.ini');

        result = []
        for g in gets:
            rep = g['name'] + '\s*=\s*\"?([/0-9A-Za-z_& ~]+)(\"?|\s*;?|\r?\n)';
            tmp = re.search(rep,phpini)
            if not tmp: continue;
            g['value'] = tmp.groups()[0].strip();
            result.append(g);

        return result;


    def get_php_config(self,get):
        #取PHP配置
        get.version = get.version.replace('.','')
        file = public.GetConfigValue('setup_path') + "/php/"+get.version+"/php.ini"
        phpini = public.readFile(file)
        data = {}
        try:
            rep = "upload_max_filesize\s*=\s*([0-9]+)M"
            tmp = re.search(rep,phpini).groups()
            data['max'] = tmp[0]
        except:
            data['max'] = '50'

        try:
            rep = 'max_execution_time\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)'
            tmp = re.search(rep,phpini).groups()
            data['maxTime'] = tmp[0]
        except :
            data['maxTime'] =0

        try:
            rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n"
            tmp = re.search(rep,phpini).groups()

            if tmp[0] == '1':
                data['pathinfo'] = True
            else:
                data['pathinfo'] = False
        except:
            data['pathinfo'] = False

        return data

    #提交PHP配置参数
    def SetPHPConf(self,get):
        gets = ['display_errors','cgi.fix_pathinfo','date.timezone','short_open_tag','asp_tags','max_execution_time','max_input_time','memory_limit','post_max_size','file_uploads','upload_max_filesize','max_file_uploads','default_socket_timeout','error_reporting']
        filename = public.GetConfigValue('setup_path') + '/php/' + get.version + '/php.ini';
        phpini = public.readFile(filename);
        for g in gets:
            if get[g]:
                rep = g + '\s*=\s*(.+)\r?\n';
                val = g+' = ' + get[g] + '\n';
                phpini = re.sub(rep,val,phpini);

        public.writeFile(filename,phpini);
        return public.returnMsg(True,'SET_SUCCESS');


    #获取配置
    def get_config(self,get):


        data = {}
        if 'config' in session:
            session['config']['distribution'] = public.get_system_version(True)
            session['webserver'] = public.get_webserver()
            session['config']['webserver'] = session['webserver']
            data = session['config']
        if not data:
            data = public.M('config').where("id=?",('1',)).field('webserver,sites_path,backup_path,status,mysql_root').find()

        data['webserver'] = public.get_webserver()
        data['distribution'] = public.get_system_version(True)

        data['request_iptype'] = self.get_request_iptype()
        data['request_type'] = self.get_request_type()


        return data

    #取面板错误日志
    def get_error_logs(self,get):
        return public.GetNumLines('logs/error.log',2000)

    def GetApacheValue(self,get):
        a = apache.apache()
        return a.GetApacheValue()

    def SetApacheValue(self,get):
        a = apache.apache()
        return a.SetApacheValue(get)

    def get_ipv6_listen(self,get):
        return os.path.exists('data/ipv6.pl')

    def set_ipv6_status(self,get):
        ipv6_file = 'data/ipv6.pl'
        if self.get_ipv6_listen(get):
            os.remove(ipv6_file)
            public.WriteLog('面板设置','关闭面板IPv6兼容!')
        else:
            public.writeFile(ipv6_file,'True')
            public.WriteLog('面板设置','开启面板IPv6兼容!')
        public.restart_panel()
        return public.returnMsg(True,'设置成功!')

    #设置debug模式
    def set_debug(self,get):
        debug_path = 'data/debug.pl'
        if os.path.exists(debug_path):
            t_str = '关闭'
            os.remove(debug_path)
        else:
            t_str = '开启'
            public.writeFile(debug_path,'True')
        public.WriteLog('面板配置','%s开发者模式(debug)' % t_str)
        public.restart_panel()
        return public.returnMsg(True,'设置成功!')

    def get_token(self,get):
        try:
            import panelApi
            return panelApi.panelApi().get_token(get)
        except ImportError as e:
            return public.returnMsg("无法导入panelApi模块:", e)
        except Exception as e:
            return public.returnMsg("发生异常:", e)
    def set_token(self,get):
        import panelApi
        return panelApi.panelApi().set_token(get)

    def get_tmp_token(self,get):
        import panelApi
        return panelApi.panelApi().get_tmp_token(get)

    #获取BasicAuth状态
    def get_basic_auth_stat(self,get):
        path = 'config/basic_auth.json'
        is_install = True
        if not os.path.exists(path): return {"basic_user":"","basic_pwd":"","open":False,"is_install":is_install}
        ba_conf = json.loads(public.readFile(path))
        ba_conf['is_install'] = is_install
        return ba_conf

    #设置BasicAuth
    def set_basic_auth(self,get):
        is_open = False
        if get.open == 'True': is_open = True
        tips = '_bt.cn'
        path = 'config/basic_auth.json'
        ba_conf = None
        if os.path.exists(path):
            ba_conf = json.loads(public.readFile(path))

        if not ba_conf:
            ba_conf = {"basic_user":public.md5(get.basic_user.strip() + tips),"basic_pwd":public.md5(get.basic_pwd.strip() + tips),"open":is_open}
        else:
            if get.basic_user: ba_conf['basic_user'] = public.md5(get.basic_user.strip() + tips)
            if get.basic_pwd: ba_conf['basic_pwd'] = public.md5(get.basic_pwd.strip() + tips)
            ba_conf['open'] = is_open

        public.writeFile(path,json.dumps(ba_conf))

        public.WriteLog('面板设置','设置BasicAuth状态为: %s' % is_open)
        public.set_server_status("btTask","restart");
        public.writeFile('data/reload.pl','True')
        return public.returnMsg(True,"设置成功!")

    #二次认证
    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, "秘钥不存在，请开启后再试")
        if not username:
            return public.returnMsg(False, "用户名不存在，请开启后再试")
        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, "请输入操作方式")
        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,"生成key或username失败，请检查硬盘空间是否不足或目录无法写入[ {} ]".format(self._setup_path+"/data/"))
            try:
                try:
                    panel_name = json.loads(public.readFile(self._setup_path+'/config/config.json'))['title']
                except:
                    panel_name = '宝塔Windows面板'
                data = pyotp.totp.TOTP(secret_key).provisioning_uri(username, issuer_name=panel_name+'--'+local_ip)
                public.writeFile(self._core_fle_path+'/qrcode.txt',str(data))
                return public.returnMsg(True, "开启成功")
            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, "关闭成功")

    # 检测是否开启双因素验证
    def check_two_step(self,get):
        secret_key = public.readFile(self._key_file)
        if not secret_key:
            return public.returnMsg(False, "没有开启二步验证")
        return public.returnMsg(True, "已经开启二步验证")

    # 读取二维码data
    def get_qrcode_data(self,get):
        data = public.readFile(self._core_fle_path + '/qrcode.txt')
        if data:
            return data
        return public.returnMsg(True, "没有二维码数据，请重新开启")


    #取用户列表
    def get_users(self,args):
        data = public.M('users').field('id,username').select()
        return data

    # 创建新用户
    def create_user(self,args):
        if session['uid'] != 1: return public.returnMsg(False,'没有权限!')
        if len(args.username) < 2: return public.returnMsg(False,'用户名不能少于2位')
        if len(args.password) < 8: return public.returnMsg(False,'密码不能少于8位')
        pdata = {
            "username": args.username.strip(),
            "password": public.password_salt(public.md5(args.password.strip()),username=args.username.strip())
        }

        if(public.M('users').where('username=?',(pdata['username'],)).count()):
            return public.returnMsg(False,'指定用户名已存在!')

        if(public.M('users').insert(pdata)):
            public.WriteLog('用户管理','创建新用户{}'.format(pdata['username']))
            return public.returnMsg(True,'创建新用户{}成功!'.format(pdata['username']))
        return public.returnMsg(False,'创建新用户失败!')

    # 删除用户
    def remove_user(self,args):
        if session['uid'] != 1: return public.returnMsg(False,'没有权限!')
        if int(args.id) == 1: return public.returnMsg(False,'不能删除初始默认用户!')
        username = public.M('users').where('id=?',(args.id,)).getField('username')
        if not username: return public.returnMsg(False,'指定用户不存在!')
        if(public.M('users').where('id=?',(args.id,)).delete()):
            public.WriteLog('用户管理','删除用户[{}]'.format(username))
            return public.returnMsg(True,'删除用户{}成功!'.format(username))
        return public.returnMsg(False,'用户删除失败!')

    # 修改用户
    def modify_user(self,args):
        if session['uid'] != 1: return public.returnMsg(False,'没有权限!')
        username = public.M('users').where('id=?',(args.id,)).getField('username')
        pdata = {}
        if 'username' in args:
            if len(args.username) < 2: return public.returnMsg(False,'用户名不能少于2位')
            pdata['username'] = args.username.strip()

        if 'password' in args:
            if args.password:
                if len(args.password) < 8: return public.returnMsg(False,'密码不能少于8位')
                pdata['password'] = public.password_salt(public.md5(args.password.strip()),username=username)

        if(public.M('users').where('id=?',(args.id,)).update(pdata)):
            public.WriteLog('用户管理',"编辑用户{}".format(username))
            return public.returnMsg(True,'修改成功!')
        return public.returnMsg(False,'没有提交修改!')


    # 是否显示软件推荐
    def show_recommend(self,get):
        pfile = 'data/not_recommend.pl'
        if os.path.exists(pfile):
            os.remove(pfile)
        else:
            public.writeFile(pfile,'True')
        return public.returnMsg(True,'设置成功!')


    def auto_update_panel(self,get):
        """
        开启和关闭自动更新
        """
        echo = public.md5('auto_update_panel')

        cronPath = '{}/cron/{}'.format(public.GetConfigValue('setup_path'),echo)
        shell = public.get_run_python('[PYTHON] -u %s/tools.py auto_update_panel ' % (public.GetConfigValue('setup_path')))

        crontab = public.M('crontab').where('echo=?',(echo,)).find()
        if not crontab:
            public.writeFile(cronPath,shell)
            public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("自动更新面板【宝塔面板】",'day','','0','10',echo,time.strftime('%Y-%m-%d %X',time.localtime()),1,'','localhost','toShell','',shell,''))

            return public.returnMsg(True,'开启自动更新成功，通过计划任务可以设置检查时间段.')
        else:
            public.M('crontab').where('echo=?',(echo,)).delete()
            if os.path.exists(cronPath): os.remove(cronPath)

            return public.returnMsg(True,'关闭自动更新成功!')


        #获取临时登录列表
    def get_temp_login(self,args):
        '''
            @name 获取临时登录列表
            @author hwliang<2020-09-2>
            @return dict
        '''
        if 'tmp_login_expire' in session: return public.returnMsg(False,'没有权限')
        public.M('temp_login').where('state=? and expire<?',(0,int(time.time()))).setField('state',-1)
        callback = ''
        if 'tojs' in args:
            callback = args.tojs
        p = 1
        if 'p' in args:
            p = int(args.p)
        rows =12
        if 'rows' in args:
            rows = int(args.rows)
        count = public.M('temp_login').count()
        data = {}
        page_data = public.get_page(count,p,rows,callback)
        data['page'] = page_data['page']
        data['data'] = public.M('temp_login').limit(page_data['shift'] + ',' + page_data['row']).order('id desc').field('id,addtime,expire,login_time,login_addr,state').select()
        for i in range(len(data['data'])):
            data['data'][i]['online_state'] = os.path.exists('data/session/{}'.format(data['data'][i]['id']))
        return data

    #设置临时登录
    def set_temp_login(self,args):
        '''
            @name 设置临时登录
            @author hwliang<2020-09-2>
            @return dict
        '''
        if 'tmp_login_expire' in session: return public.returnMsg(False,'没有权限')
        s_time = int(time.time())
        public.M('temp_login').where('state=? and expire>?',(0,s_time)).delete()
        token = public.GetRandomString(48)
        salt = public.GetRandomString(12)

        pdata = {
            'token': public.md5(token + salt),
            'salt': salt,
            'state':0,
            'login_time':0,
            'login_addr':'',
            'expire':s_time + 3600,
            'addtime':s_time
        }

        if not public.M('temp_login').count():
            pdata['id'] = 101

        if public.M('temp_login').insert(pdata):
            public.WriteLog('面板设置','生成临时连接,过期时间:{}'.format(public.format_date(times = pdata['expire'])))
            return {'status':True,'msg':"临时连接已生成",'token':token,'expire':pdata['expire']}
        return public.returnMsg(False,'连接生成失败')

    #删除临时登录
    def remove_temp_login(self,args):
        '''
            @name 删除临时登录
            @author hwliang<2020-09-2>
            @param args<dict_obj>{
                id: int<临时登录ID>
            }
            @return dict
        '''
        if 'tmp_login_expire' in session: return public.returnMsg(False,'没有权限')
        id = int(args.id)
        if public.M('temp_login').where('id=?',(id,)).delete():
            public.WriteLog('面板设置','删除临时登录连接')
            return public.returnMsg(True,'删除成功')
        return public.returnMsg(False,'删除失败')


    #强制弹出指定临时登录
    def clear_temp_login(self,args):
        '''
            @name 强制登出
            @author hwliang<2020-09-2>
            @param args<dict_obj>{
                id: int<临时登录ID>
            }
            @return dict
        '''
        if 'tmp_login_expire' in session: return public.returnMsg(False,'没有权限')
        id = int(args.id)
        s_file = 'data/session/{}'.format(id)
        if os.path.exists(s_file):
            os.remove(s_file)
            public.WriteLog('面板设置','强制弹出临时用户：{}'.format(id))
            return public.returnMsg(True,'已强制退出临时用户：{}'.format(id))
        public.returnMsg(False,'指定用户当前不是登录状态!')


    #查看临时授权操作日志
    def get_temp_login_logs(self,args):
        '''
            @name 查看临时授权操作日志
            @author hwliang<2020-09-2>
            @param args<dict_obj>{
                id: int<临时登录ID>
            }
            @return dict
        '''
        if 'tmp_login_expire' in session: return public.returnMsg(False,'没有权限')
        id = int(args.id)
        data = public.M('logs').where('uid=?',(id,)).order('id desc').select()
        return data


    # 是否显示工单
    def show_workorder(self,get):
        pfile = 'data/not_workorder.pl'
        if os.path.exists(pfile):
            os.remove(pfile)
        else:
            public.writeFile(pfile,'True')
        return public.returnMsg(True,'设置成功!')


    """
    引用云厂商模块
    @cloud_name 模块名称
    @s 函数名
    """
    def get_cloud_data(self,get):
        try:
            spath = '{}/class/cloud'.format(panelPath)
            if not os.path.exists(spath): return public.returnMsg(False,'模块不存在1')

            cloud_name = get['cloud_name']
            sys.path.insert(0, spath)
            module = __import__(cloud_name)
            try:
                public.mod_reload(module)
            except: pass
            moduleObj = eval('{}.{}()'.format(module,module));
            if not hasattr(moduleObj,get.s): return public.returnMsg(False,"缺少函数{}".format(get.s));

            execStr = 'moduleObj.' + get.s + '(get)'
            return eval(execStr);
        except :
            return public.returnMsg(False,public.get_error_info())


    """
    获取面板配置
    """
    def get_panel_config(self,get):
        import system
        data = system.system().GetConcifInfo()
        data['workorder'] = not os.path.exists('data/not_workorder.pl')
        data['recommend'] = not os.path.exists('data/not_recommend.pl')
        data['basic_auth'] = self.get_basic_auth_stat(None)
        data['lan'] = public.GetLan('config')
        import wxapp
        data['wx'] = wxapp.wxapp().get_user_info(None)['msg']
        data['api'] = ''
        if self.get_token(None)['open']: data['api'] = 'checked'

        return data


    #获取面板证书
    def GetPanelSSL(self,get):
        cert = {}
        key_file = 'ssl/privateKey.pem'
        cert_file = 'ssl/certificate.pem'
        if not os.path.exists(key_file):
            self.CreateSSL()
        cert['privateKey'] = public.readFile(key_file)
        cert['certPem'] = public.readFile(cert_file)
        cert['download_root'] = False
        cert['info'] = {}
        if not cert['privateKey']:
            cert['privateKey'] = ''
            cert['certPem'] = ''
        else:
            cert['info'] = public.get_cert_data(cert_file)
            if not cert['info']:
                self.CreateSSL()
                cert['info'] = public.get_cert_data(cert_file)
            if cert['info']:
                if cert['info']['issuer'] == '宝塔面板':
                    if os.path.exists('ssl/baota_root.pfx'):
                        cert['download_root'] = True
                        cert['root_password'] = public.readFile('ssl/root_password.pl')
        cert['rep'] = os.path.exists('ssl/input.pl')
        return cert

    #保存面板证书
    def SavePanelSSL(self,get):
        keyPath = 'ssl/privateKey.pem'
        certPath = 'ssl/certificate.pem'
        checkCert = 'data/cert.pl'
        public.writeFile(checkCert,get.certPem)
        if get.privateKey:
            public.writeFile(keyPath,get.privateKey)
        if get.certPem:
            public.writeFile(certPath,get.certPem)

        if not public.get_cert_data(certPath): return public.returnMsg(False,'证书错误,请检查!')
        public.writeFile('ssl/input.pl','True')
        return public.returnMsg(True,'证书已保存!')

     #设置面板SSL
    def SetPanelSSL(self,get):
        path = '{}/ssl'.format(panelPath)
        if not os.path.exists(path): os.makedirs(path)

        sslConf = 'data/ssl.pl'
        if hasattr(get,"certPem"):
            ret = self.SavePanelSSL(get)
            if not ret['status']: return ret

            public.writeFile(sslConf,'True')
            return public.returnMsg(True,'PANEL_SSL_OPEN')
        else:
            if os.path.exists(sslConf):
                os.remove(sslConf)
                return public.returnMsg(True,'PANEL_SSL_CLOSE')
            else:
                try:
                    if not self.CreateSSL(): return public.returnMsg(False,'PANEL_SSL_ERR')
                    public.writeFile(sslConf,'True')
                except:
                    return public.returnMsg(False,'PANEL_SSL_ERR')
                return public.returnMsg(True,'PANEL_SSL_OPEN')
    #自签证书
    def CreateSSL(self):
        userInfo = public.get_user_info()
        if userInfo:
            domains = []
            req_host = public.GetHost()
            server_ip = public.get_ip()
            domains.append(req_host)
            if server_ip != req_host and not server_ip in ['127.0.0.1','::1','localhost']:
                domains.append(server_ip)
            pdata = {
                "action":"get_domain_cert",
                "company":"宝塔面板",
                "domain":','.join(domains),
                "uid":userInfo['uid'],
                "access_key":userInfo['access_key'],
                "panel":1
            }
            cert_api = 'https://api.bt.cn/bt_cert'
            result = json.loads(public.httpPost(cert_api,{'data': json.dumps(pdata)}))
            if 'status' in result:
                if result['status']:
                    public.writeFile('ssl/certificate.pem',result['cert'])
                    public.writeFile('ssl/privateKey.pem',result['key'])
                    public.writeFile('ssl/baota_root.pfx',base64.b64decode(result['pfx']),'wb+')
                    public.writeFile('ssl/root_password.pl',result['password'])
                    return True

        if os.path.exists('ssl/input.pl'): return True
        import OpenSSL
        key = OpenSSL.crypto.PKey()
        key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
        cert = OpenSSL.crypto.X509()
        cert.set_serial_number(0)
        cert.get_subject().CN = public.GetLocalIp()
        cert.set_issuer(cert.get_subject())
        cert.gmtime_adj_notBefore( 0 )
        cert.gmtime_adj_notAfter(86400 * 3650)
        cert.set_pubkey( key )
        cert.sign( key, 'md5' )
        cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
        private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)

        if len(cert_ca) > 100 and len(private_key) > 100:
            public.writeFile('ssl/certificate.pem',cert_ca,'wb+')
            public.writeFile('ssl/privateKey.pem',private_key,'wb+')
            return True
        return False


    def set_ssl_verify(self,get):
        """
        设置双向认证
        """
        sslConf = 'data/ssl_verify.pl'
        status = int(get.status)
        if status:
            if not os.path.exists('data/ssl.pl'): return public.returnMsg(False,'需要先开启面板SSL功能!')
            public.writeFile(sslConf,'True')
        else:
            if os.path.exists(sslConf): os.remove(sslConf)

        if 'crl' in get and 'ca' in get:
            crl = 'ssl/crl.pem'
            ca = 'ssl/ca.pem'

            if get.crl:
                public.writeFile(crl,get.crl.strip())
            if get.ca:
                public.writeFile(ca,get.ca.strip())
            return public.returnMsg(True,'面板双向认证证书已保存!')
        else:
            msg = '开启'
            if not status:msg = '关闭'
            return public.returnMsg(True,'面板双向认证{}成功!'.format(msg))

    def get_ssl_verify(self,get):
        """
        获取双向认证
        """
        result = {'status':False,'ca':'','crl':''}
        sslConf = 'data/ssl_verify.pl'
        if os.path.exists(sslConf): result['status'] = True

        ca = 'ssl/ca.pem'
        crl = 'ssl/crl.pem'
        if os.path.exists(crl):
            result['crl'] = public.readFile(crl)
        if os.path.exists(crl):
            result['ca'] = public.readFile(ca)
        return result


    def get_login_type(self,get):
        """
        获取所有登录方式
        """
        return public.get_login_type()

    def set_login_type(self,get):
        """
        设置登录方式开关
        """
        if not 'login_type' in get:
            return public.returnMsg(False,'缺少必要参数login_type!')

        data = public.get_login_type()
        data[get.login_type] = int(get.status)
        public.writeFile('data/login_type.pl',json.dumps(data))

        return public.returnMsg(True,'保存成功.')


    def get_request_iptype(self,get = None):
        '''
            @name 获取云端请求线路
            @author hwliang<2022-02-09>
            @return auto/ipv4/ipv6
        '''

        v4_file = '{}/data/v4.pl'.format(public.get_panel_path())
        if not os.path.exists(v4_file): return 'auto'
        iptype = public.readFile(v4_file).strip()
        if not iptype: return 'auto'
        if iptype == '-4': return 'ipv4'
        return 'ipv6'


    def set_request_iptype(self,get):
        '''
            @name 设置云端请求线路
            @author hwliang<2022-02-09>
            @param iptype<str> auto/ipv4/ipv6
            @return dict
        '''
        v4_file = '{}/data/v4.pl'.format(public.get_panel_path())
        if not 'iptype' in get:
            return public.returnMsg(False,'参数错误!')
        if get.iptype == 'auto':
            public.writeFile(v4_file,' ')
        elif get.iptype == 'ipv4':
            public.writeFile(v4_file,' -4 ')
        else:
            public.writeFile(v4_file,' -6 ')
        public.WriteLog('面板设置','将云端请求线路设置为:{}'.format(get.iptype))
        return public.returnMsg(True,'设置成功!')


    def get_request_type(self,get= None):
        '''
            @name 获取云端请求方式
            @author hwliang<2022-02-09>
            @return python/curl/php
        '''
        http_type_file = '{}/data/http_type.pl'.format(public.get_panel_path())
        if not os.path.exists(http_type_file): return 'python'
        http_type = public.readFile(http_type_file).strip()
        if not http_type:
            os.remove(http_type_file)
            return 'python'
        return http_type


    def set_request_type(self,get):
        '''
            @name 设置云端请求方式
            @author hwliang<2022-02-09>
            @param http_type<str> python/curl/php
            @return dict
        '''
        http_type_file = '{}/data/http_type.pl'.format(public.get_panel_path())
        if not 'http_type' in get:
            return public.returnMsg(False,'参数错误!')
        if get.http_type == 'php':
            if not os.listdir('{}/php'.format(public.get_setup_path())): return public.returnMsg(False,'没有可用的PHP版本!')

        public.writeFile(http_type_file,get.http_type)
        public.WriteLog('面板设置','将云端请求方式设置为:{}'.format(get.http_type))
        return public.returnMsg(True,'设置成功!')


    def set_click_logs(self,get):

        path = '{}/logs/click'.format(public.get_panel_path())
        if not os.path.exists(path): os.makedirs(path)

        file = "{}/{}.json".format(path,public.format_date(format = "%Y-%m-%d"))
        try:
            ndata = json.loads(get['ndata'])
        except :ndata = {}

        try:
            data = json.loads(public.readFile(file))
        except : data = {}

        for x in ndata:
            if not x in data: data[x] = 0
            data[x] += ndata[x]

        public.writeFile(file,json.dumps(data))
        return data

    def get_node_config(self,get):
        '''
            @name 获取节点配置
            @author hwliang<2022-03-16>
            @return list
        '''
        node_list = [{"node_name":"自动选择","node_id":0,"node_ip":"","status":1}]
        node_file = '{}/config/api_node.json'.format(public.get_panel_path())
        if not os.path.exists(node_file): return node_list
        node_list = json.loads(public.readFile(node_file))
        for node in node_list:
            # node['speed'] = self.test_node(node['node_ip'])
            del(node['node_ip'])
        return node_list


    def test_node(self,node_ip):
        '''
            @name 测试节点
            @author hwliang<2022-03-16>
            @param node_ip<str> 节点IP
            @return int 节点延迟
        '''
        if not node_ip: node_ip = "api.bt.cn"
        s_time = time.time()
        import requests
        headers = {"Host":"api.bt.cn","User-Agent":"BT-Panel"}
        try:
            res = requests.get('https://{}'.format(node_ip),headers=headers,timeout=1,verify=False).text
        except:
            return 0
        e_time = time.time()
        if res != 'ok': return 0
        return int((e_time - s_time) * 1000)

    def set_node_config(self,get):
        '''
            @name 设置节点配置
            @author hwliang<2022-03-16>
            @param node_id<str> 节点ID
            @return dict
        '''
        node_file = '{}/config/api_node.json'.format(public.get_panel_path())
        if not 'node_id' in get:
            return public.returnMsg(False,'参数错误!')
        node_id = int(get.node_id)
        if not os.path.exists(node_file):
            return public.returnMsg(False,'节点配置文件不存在!')
        node_list = json.loads(public.readFile(node_file))
        node_name = '自动选择'
        for node in node_list:
            if node['node_id'] == node_id:
                node['status'] = 1
                node_name = node['node_name']
                continue
            node['status'] = 0
        public.writeFile(node_file,json.dumps(node_list))
        self.sync_node_config()
        public.WriteLog('面板设置','将节点配置设置为:{}'.format(node_name))
        return public.returnMsg(True,'设置成功!')

    def sync_cloud_node_list(self):
        '''
            @name  同步云端的节点列表
            @author hwliang<2022-03-16>
            @return void
        '''
        node_file = '{}/config/api_node.json'.format(public.get_panel_path())
        if not os.path.exists(node_file):
            public.writeFile(node_file,'[]')

        node_list = json.loads(public.readFile(node_file))
        try:
            url = "{}/lib/other/api_node.json".format(public.get_url())
            res = public.httpGet(url)
            if not res: return
            cloud_list = json.loads(res)
        except:
            return
        for cloud_node in cloud_list:
            is_insert = True
            for node in node_list:
                if node['node_id'] == cloud_node['node_id']:
                    node['node_name'] = cloud_node['node_name']
                    node['node_ip'] = cloud_node['node_ip']
                    is_insert = False
                    break
            if is_insert: node_list.append(cloud_node)
        public.writeFile(node_file,json.dumps(node_list))
        # self.sync_node_config()


    def sync_node_config(self):
        '''
            @name 同步节点配置
            @author hwliang<2022-03-16>
            @return void
        '''

        node_file = '{}/config/api_node.json'.format(public.get_panel_path())
        if not os.path.exists(node_file): return
        node_list = json.loads(public.readFile(node_file))
        node_info = {}
        for node in node_list:
            if node['status'] == 1:
                node_info = node
                break
        if not node_info: return
        if not node_info['node_ip']: return

        path = 'C:/Windows/System32/drivers/etc/hosts'
        if not os.path.exists(path): return public.returnMsg(False,"获取失败，文件不存在！")
        conf = public.readFile(path).strip()
        conf = re.sub('\n.+\s+api.bt.cn','',conf)
        conf = '{}\n{} {}'.format(conf,node_info['node_ip'],"api.bt.cn")
        public.writeFile(path,conf)

    def get_panel_log(self,get):
        """
        @name 获取面板日志
        """
        from html import unescape,escape

        log_path = '{}/logs/request/{}.json'.format(public.get_panel_path(),public.format_date(format='%Y-%m-%d'))
        if not os.path.exists(log_path):
            return public.returnMsg(False,'日志文件不存在!')

        log_list = []
        slist = public.GetNumLines(log_path,2000).split('\r\n')
        for s in slist:
            if not s: continue
            try:
                info = json.loads(unescape(s))
                for key in info:
                    if isinstance(info[key],str):
                        info[key] = escape(info[key])
                log_list.append(info)
            except:pass
        return log_list


    def get_not_auth_status(self):
        '''
            @name 获取未认证时的响应状态
            @author hwliang<2021-12-16>
            @return int
        '''
        try:
            status_code = int(public.read_config('abort'))
            return status_code
        except:
            return 0

    def set_not_auth_status(self,get):
        '''
            @name 设置未认证时的响应状态
            @author hwliang<2021-12-16>
            @param status_code<int> 状态码
            @return dict
        '''
        if not 'status_code' in get:
            return public.returnMsg(False,'参数错误!')

        if re.match("^\d+$", get.status_code):
            status_code = int(get.status_code)
            if status_code != 0:
                if status_code < 100 or status_code > 999:
                    return public.returnMsg(False,'状态码范围错误!')
        else:
            return public.returnMsg(False,'状态码范围错误!')

        public.save_config('abort',get.status_code)
        public.WriteLog('面板设置','将未授权响应状态码设置为:{}'.format(get.status_code))
        return public.returnMsg(True,'设置成功!')


    def get_password_safe_file(self):
        '''
            @name 获取密码复杂度配置文件
            @auther hwliang<2021-10-18>
            @return string
        '''
        return public.get_panel_path() + '/data/check_password_safe.pl'

    def check_password_safe(self,password):
        '''
            @name 密码复杂度验证
            @auther hwliang<2021-10-18>
            @param password(string) 密码
            @return bool
        '''
        # 是否检测密码复杂度
        is_check_file = self.get_password_safe_file()
        if not os.path.exists(is_check_file): return True

        # 密码长度验证
        if len(password) < 8: return False

        num = 0
        # 密码是否包含数字
        if re.search(r'[0-9]+',password): num += 1
        # 密码是否包含小写字母
        if re.search(r'[a-z]+',password): num += 1
        # 密码是否包含大写字母
        if re.search(r'[A-Z]+',password): num += 1
        # 密码是否包含特殊字符
        if re.search(r'[^\w\s]+',password): num += 1
        # 密码是否包含以上任意3种组合
        if num < 3: return False
        return True

    def set_password_safe(self,get):
        '''
            @name 设置密码复杂度
            @auther hwliang<2021-10-18>
            @param get(string) 参数
            @return dict
        '''
        is_check_file = self.get_password_safe_file()
        if os.path.exists(is_check_file):
            os.remove(is_check_file)
            public.WriteLog('TYPE_PANEL','关闭密码复杂度验证')
            return public.returnMsg(True,'已关闭密码复杂度验证')
        else:
            public.writeFile(is_check_file,'True')
            public.WriteLog('TYPE_PANEL','开启密码复杂度验证')
            return public.returnMsg(True,'已开启密码复杂度验证')

    def get_password_safe(self,get):
        '''
            @name 获取密码复杂度
            @auther hwliang<2021-10-18>
            @param get(string) 参数
            @return bool
        '''
        is_check_file = self.get_password_safe_file()
        return os.path.exists(is_check_file)


    def get_password_expire_file(self):
        '''
            @name 获取密码过期配置文件
            @auther hwliang<2021-10-18>
            @return string
        '''
        return public.get_panel_path() + '/data/password_expire.pl'


    def set_password_expire(self,get):
        '''
            @name 设置密码过期时间
            @auther hwliang<2021-10-18>
            @param get<dict_obj>{
                expire: int<密码过期时间> 单位:天,
            }
            @return dict
        '''
        expire = int(get.expire)
        expire_file = self.get_password_expire_file()
        if expire <= 0:
            if os.path.exists(expire_file):
                os.remove(expire_file)
            public.WriteLog('TYPE_PANEL','关闭密码过期验证')
            return public.returnMsg(True,'已关闭密码过期验证')
        min_expire = 10
        max_expire = 365 * 5
        if expire < min_expire: return public.returnMsg(False,'密码过期时间不能小于{}天'.format(min_expire))
        if expire > max_expire: return public.returnMsg(False,'密码过期时间不能大于{}天'.format(max_expire))

        public.writeFile(self.get_password_expire_file(),str(expire))

        if expire > 0:
            expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl'
            public.writeFile(expire_time_file,str(int(time.time()) + (expire * 86400)))

        public.WriteLog('TYPE_PANEL','设置密码过期时间为：[{}]天'.format(expire))
        return public.returnMsg(True,'已设置密码过期时间为：[{}]天'.format(expire))

    def get_password_config(self,get=None):
        '''
            @name 获取密码配置
            @auther hwliang<2021-10-18>
            @param get<dict_obj> 参数
            @return dict{expire:int,expire_time:int,password_safe:bool}
        '''
        expire_file = self.get_password_expire_file()
        expire = 0
        expire_time=0
        if os.path.exists(expire_file):
            expire = public.readFile(expire_file)
            try:
                expire = int(expire)
            except:
                expire = 0

            # 检查密码过期时间文件是否存在
            expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl'
            if not os.path.exists(expire_time_file) and expire > 0:
                public.writeFile(expire_time_file,str(int(time.time()) + (expire * 86400)))

            expire_time = public.readFile(expire_time_file)
            if expire_time:
                expire_time = int(expire_time)
            else:
                expire_time = 0

        data = {}
        data['expire'] = expire
        data['expire_time'] = expire_time
        data['password_safe'] = self.get_password_safe(get)
        data['ps'] = '当前未开启密码过期配置，为了您的面板安全，请考虑开启!'
        if data['expire_time']:
            data['expire_day'] = int((expire_time - time.time()) / 86400)
            if data['expire_day'] < 10:
                if data['expire_day'] <= 0:
                    data['ps'] = '您的密码已经过期，为防止下次无法登录，请立即修改密码!'
                else:
                    data['ps'] = "您的面板密码还有 <span style='color:red;'>{}</span> 天就过期了，为了不影响您正常登录，请尽快修改密码!".format(data['expire_day'])
            else:
                data['ps'] = "您的面板密码离过期时间还有 <span style='color:green;'>{}</span> 天!".format(data['expire_day'])
        return data

    def __get_push_data(self):
        '''
            @name 获取推送数据
            @auther cjxin<2022-11-18>
        '''
        data = {}
        try:
            path = "{}/class/push/push.json".format(public.get_panel_path())
            if os.path.exists(path): data = json.loads(public.readFile(path))
        except:
            pass
        return data

    def __set_push_data(self,name,id,data):
        '''
            @name 设置推送数据
            @auther cjxin<2022-11-18>
        '''
        import panelPush
        push_obj = panelPush.panelPush()

        argv = public.dict_obj()
        argv.name = name
        argv.id = id
        argv.data = data
        res = push_obj.set_push_config(argv)

        return res

    #查看告警
    def get_login_send(self, get):
        """
        @name 获取登录告警配置
        @auther cjxin<2022-11-18>
        """
        send_type = ""
        data = self.__get_push_data()
        if 'panelLogin_push' in data and 'login' in data['panelLogin_push']:
            s_login = data['panelLogin_push']['login']
            if s_login['status']:
                send_type = s_login['module']

        return public.returnMsg(True, send_type)


    def clear_login_send(self,get):
        """
        @name 清除登录告警配置
        @auther cjxin<2022-11-18>
        """
        return self.__set_push_data('panelLogin_push', 'login',  json.dumps({"status":False,"module":'',"interval":600}))

    def set_login_send(self,get):

        type_list = get.type.strip().split(',')
        msg_configs = self.get_msg_configs(get)

        for send_type in type_list:
            if send_type not in msg_configs.keys():
                return public.returnMsg(False,'不支持该发送类型')

            _conf = msg_configs[send_type]
            if "data" not in _conf or not _conf["data"]:
                return public.returnMsg(False, "该通道未配置, 请重新选择。")

            push_obj = public.init_msg(send_type)
            if not push_obj:
                return public.returnMsg(False, "该通道未配置, 请重新选择。")

        return self.__set_push_data('panelLogin_push', 'login',  json.dumps({"status":True,"module":get.type.strip(),"interval":600}))


    def get_login_area(self,get):
        """
        @获取面板登录告警
        @return
            login_status 是否开启面板登录告警
            login_area 是否开启面板异地登录告警
        """
        result = {}
        result['login_status'] = self.get_login_send(get)['msg']

        result['login_area'] = ''
        sfile =  '{}/data/panel_login_area.pl'.format(public.get_panel_path())
        if os.path.exists(sfile):
            result['login_area_status'] = public.readFile(sfile)
        return result


    def set_login_area(self,get):
        """
        @name 设置异地登录告警
        @param get
        """
        sfile =  '{}/data/panel_login_area.pl'.format(public.get_panel_path())
        set_type=get.type.strip()
        obj = public.init_msg(set_type)
        if not obj:
            return public.returnMsg(False, "未安装告警模块。")

        public.writeFile(sfile, set_type)
        return public.returnMsg(True, '设置成功')

    def get_login_area_list(self,get):
        """
        @name 获取面板常用地区登录

        """
        data = {}
        sfile = '{}/data/panel_login_area.json'.format(public.get_panel_path())
        try:
            data = json.loads(public.readFile(sfile))
        except:pass

        result = []
        for key in data.keys():
            result.append({'area':key,'count':data[key]})

        result = sorted(result, key=lambda x: x['count'], reverse=True)
        return result

    def clear_login_list(self,get):
        """
        @name 清理常用登录地区
        """
        sfile = '{}/data/panel_login_area.json'.format(public.get_panel_path())
        if os.path.exists(sfile):
            os.remove(sfile)
        return public.returnMsg(True,'操作成功.')

    # 获取菜单列表
    def get_menu_list(self,get):
        '''
            @name 获取菜单列表
            @author hwliang<2020-08-31>
            @param get<dict_obj>
            @return list
        '''
        menu_file = 'config/menu.json'
        hide_menu_file = 'config/hide_menu.json'
        data = json.loads(public.ReadFile(menu_file))
        if not os.path.exists(hide_menu_file):
            public.writeFile(hide_menu_file,'[]')
        hide_menu = public.ReadFile(hide_menu_file)
        if not hide_menu:
            hide_menu = []
        else:
            hide_menu = json.loads(hide_menu)
        result = []
        for d in data:
            tmp = {}
            tmp['id'] = d['id']
            tmp['title'] = d['title']
            tmp['show'] = not d['id'] in hide_menu
            tmp['sort'] = d['sort']
            result.append(tmp)

        menus = sorted(result, key=lambda x: x['sort'])
        return menus


    # 设置隐藏菜单列表
    def set_hide_menu_list(self,get):
        '''
            @name 设置隐藏菜单列表
            @author hwliang<2020-08-31>
            @param get<dict_obj> {
                hide_list: json<list> 所有不显示的菜单ID
            }
            @return dict
        '''
        hide_menu_file = 'config/hide_menu.json'
        not_hide_id = ["dologin","memuAconfig","memuAsoft","memuA"] #禁止隐藏的菜单

        hide_list = json.loads(get.hide_list)
        hide_menu = []
        for h in hide_list:
            if h in not_hide_id:continue
            hide_menu.append(h)
        public.writeFile(hide_menu_file,json.dumps(hide_menu))
        public.WriteLog('面板设置','修改面板菜单显示列表成功')
        return public.returnMsg(True,'设置成功')

    def update_nps(self, get):
        url = "https://nps.bt.cn/api/index.php"
        data = {
            'action': "list",
            'product_type': get.get('product_type', 0),
            'version': get.get('version', -1)}
        import requests
        res = requests.post(url, data=data)
        if res.status_code == 200:
            data1 = res.json()['data']
            public.writeFile('/www/server/panel/data/nps_{}.json'.format(get.get('product_type', 0)), json.dumps(data1))
            return data1
        return False
    
    
    def get_nps_new(self, get):
        """
        获取问卷
        """
        try:
            product_type = get.get('product_type', 0)
            nps_path = '/www/server/panel/data/nps_{}.json'.format(product_type)
            # request发送post请求并指定form_data参数
            if not os.path.exists(nps_path):
                data = self.update_nps(get)
                if not data:
                    return public.returnMsg(False, "获取问卷失败")
            else:
                data = json.loads(public.readFile(nps_path))
                public.run_thread(self.update_nps, (get,))
            return public.returnMsg(True, data)
        except:
            return public.returnMsg(False, "获取问卷失败")
        
    def write_nps_new(self, get):
        '''
            @name nps 提交
            @param rate 评分
            @param feedback 反馈内容
        '''
        try:
            if not hasattr(get, 'software_name'): public.returnMsg(False, '参数错误')
            software_name = get['software_name']
            public.WriteFile("data/{}_nps.pl".format(software_name), "1")
            user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path())))
            api_url = 'https://nps.bt.cn/api/index.php'
            if not hasattr(get, 'questions'):
                return public.returnMsg(False, "参数错误")
            else:
                try:
                    content = json.loads(get.questions)
                    for _, i in content.items():
                        if len(i) > 512:
                            public.ExecShell("rm -f data/{}_nps.pl".format(software_name))
                            return public.returnMsg(False, "提交的文本太长，请调整后重新提交（MAX：512）")
                except:
                    return public.returnMsg(False, "参数错误")
            if not hasattr(get, 'product_type'):
                return public.returnMsg(False, "参数错误")
            if not hasattr(get, 'rate'):
                return public.returnMsg(False, "参数错误")
            if not hasattr(get, 'reason_tags'):
                get['reason_tags'] = "1"
            if not hasattr(get, 'is_paid'):
                get['is_paid'] = 0  # 是否付费
            if not hasattr(get, 'phone_back'):
                get.phone_back = 0
            if not hasattr(get, 'feedback'):
                get.feedback = ""
            data = {
                'action': "submit",
                'uid': user_info['uid'],  # 用户ID
                'access_key': user_info['access_key'],  # 用户密钥
                'product_type': get['product_type'],  # 产品类型
                'phone_back': get['back_phone'],  # 是否回访
                'panel_version': public.version(),  # 面板版本
                'reason_tags': get['reason_tags'],  # 问题标签
                'rate': get['rate'],  # 评分
                'serverid': user_info['serverid'],  # 服务器ID
                'questions': get['questions'],  # 问题列表
                'is_paid': get['is_paid'],  # 是否付费
                'feedback': get['feedback'],  # 反馈内容
            }
            try:
                import requests
                res = requests.post(api_url, data=data, timeout=10)
                if software_name == "panel":
                    self._stop_panel_nps(is_complete=True)
                if res.status_code == 200:
                    return public.returnMsg(True, "提交成功")
                else:
                    return public.returnMsg(True, "提交成功")
            except:
                return public.returnMsg(True, "提交成功")
        except:
            return public.returnMsg(True, "提交成功")
    
    def _stop_panel_nps(self, is_complete: bool = False):
        nps_data = self._get_panel_nps_data()
        if is_complete:
            nps_data["status"] = "complete"
        else:
            nps_data["status"] = "stopped"

        self._save_panel_nps_data(nps_data)
        
        
    @staticmethod
    def _get_panel_nps_data():
        panel_path = public.get_panel_path()
        try:
            nps_file = "{}/data/btpanel_nps_data".format(panel_path)
            if os.path.exists(nps_file):
                with open(nps_file, mode="r") as fp:
                    nps_data = json.load(fp)
            else:
                time_file = "{}/data/panel_nps_time.pl".format(panel_path)
                post_nps_done = "{}/data/panel_nps.pl".format(panel_path)
                if not os.path.exists(time_file):
                    install_time = time.time()
                else:
                    with open(time_file, mode="r") as fp:
                        install_time = float(fp.read())
                nps_data = {
                    "time": install_time,
                    "status": "complete" if os.path.exists(post_nps_done) else "waiting",
                    "popup_count": 0
                }

                with open(nps_file, mode="w") as fp:
                    fp.write(json.dumps(nps_data))
        except:
            nps_data = {
                "time": time.time(),
                "status": "waiting",
                "popup_count": 0
            }

        return nps_data
    
    @staticmethod
    def _save_panel_nps_data(nps_data):
        panel_path = public.get_panel_path()
        nps_file = "{}/data/btpanel_nps_data".format(panel_path)
        with open(nps_file, mode="w") as fp:
            fp.write(json.dumps(nps_data))
