#!/usr/bin/python
# coding: utf-8
# Author: <lkq>lkq@bt.cn
# pane_sec.py
# code: 基础安全扫描类
# +-------------------------------------------------------------------
import sys, os

panel_path = os.getenv("BT_PANEL")
import public, firewalls, panelSite
import re, json, sys, string, random,time
from BTPanel import session

class panel_sec:
    __string_data = string.ascii_letters + string.digits + "#$&*+=?@^_|~"
    __firewalls = firewalls.firewalls()
    __panelSite = panelSite.panelSite()
    __redis_conf_file = os.getenv("BT_SETUP") + "/redis/redis.windows.conf"

    def __init__(self):
        pass

    def get_users(self, get):
        """
        获取操作系统所有用户列表
        """
        import win32net, win32api, win32netcon
        resume = 0
        users = []
        while True:
            data, total, resume = win32net.NetUserEnum(None, 3, win32netcon.FILTER_NORMAL_ACCOUNT, resume)
            for user in data:
                users.append(user['name'])
            if not resume: break
       
        return users

    def get_err_count(self, pid, day=0):
        """
        获取指定事件的数量
        """
        try:
            data = public.get_func('SetSshPort')
            
            stime = time.time() - (day * 86400) 
            if data['time'] >= stime: stime = data['time']

            t = time.localtime(stime)
            start_time = time.strftime('%Y-%m-%dT%H:%M:%S', t)
        except :
            if day > 0:
                t = time.localtime(time.time() - (day * 86400))
            else:
                t = time.localtime(0)      
            start_time = time.strftime('%Y-%m-%dT00:00:00', t)
   
        e_count = public.ExecShell('wevtutil qe Security "/q:*[System [(EventID={}) and  TimeCreated[@SystemTime >= \'{}\' ]]]" |find /c /v ""'.format(str(pid), start_time))[0]
        if not e_count: e_count = '0'
        return int(e_count);

    def set_password(self, get):
        """
        密码生成器
        """
        ret = []
        for i in range(0, 20):
            ret.append(random.choice(self.__string_data))
        return "".join(ret)
   
    def GetSshInfo(self, get):
        """
        查看远程桌面端口
        """
        return self.__firewalls.GetSshInfo(get)

    def get_run_user(self, get):
        """
        获取web服务器运行用户
        """
        webserver = public.get_webserver()
        ret = public.ExecShell("sc qc {}".format(webserver))[0]
        data = {}
        data['web'] = webserver
        data['user'] = ''
        if ret.find('LocalSystem') >= 0:
            data['user'] = "system"
        elif ret.find('www') >= 0:
            data['user'] = 'www'
        return data

    def get_www(self, get):
        """
        Apache/Nginx 是否是www用户运行
        """
        ret = {"web_user": "", "web": ""}
        data = self.get_run_user(get)
        if data['web'] == 'nginx':
            if data['user'] == 'system':
                ret['web_user'] = 'system'
                ret['web'] = 'nginx'
                return ret
            else:
                return False
        if data['web'] == 'apache':
            if data['user'] == 'system':
                ret['web_user'] = 'system'
                ret['web'] = 'nginx'
                return ret
            else:
                return False
        return False

    def check_waf(self, get):
        """
        检测是否安装waf防火墙
        """
        ret = {}
                    
        data = self.get_run_user(get)
        if public.process_exists('SafeDogGuardCenter'): return {'web':data['web'],'waf':'safedog'}
        if public.process_exists('yunsuo_agent_service'): return {'web':data['web'],'waf':'yunsuo'}

        if data['web'] == 'apache':
            if not os.path.exists(panel_path + '/plugin/waf_apache/info.json'):
                ret['web'] = data['web']
                ret['waf'] = 'apache'
                return ret
            return False
        elif data['web'] == 'iis':
            if not os.path.exists(panel_path + '/plugin/waf_iis/info.json'):
                ret['web'] = data['web']
                ret['waf'] = 'iis'
                return ret
            return False
        elif data['web'] == 'nginx':
            if not os.path.exists(panel_path + '/plugin/waf_nginx/info.json'):
                ret['web'] = data['web']
                ret['waf'] = 'nginx'
                return ret
            return False
        else:
            return False

    # mysql 安全
    def get_mysql(self, get):
        pass

    def GetRedisConf(self, get):
        """
        检测redis是否开启外网访问
        """
        try:
            redis_conf = public.readFile(self.__redis_conf_file)
            result = []
            n = 0
            keys = ["bind", "requirepass"]
            for k in keys:
                v = ""
                rep = "\n%s\s+([\w\.]+)" % k
                group = re.search(rep, redis_conf)
                if group:
                    v = group.group(1)
                else:
                    v = ""
                n = n + 1
                kv = {"name": k, "value": v}
                result.append(kv)
            return result
        except Exception as ex:
            return False


    def get_redis(self, get):
        ret = {"reids_bind": "", "reids_require": ""}
        data = self.GetRedisConf(get)
        if data and data[0]['value'] == '0.0.0.0':
            ret['bind'] = "0.0.0.0"
            ret['reids_require'] = data[1]['value']
            return ret
        if data and data[1]['value'] == '':
            if data[0]['value'] != '127.0.0.1':
                ret['bind'] = data[0]['value']
                ret['reids_require'] = data[1]['value']
                return ret
            return False
        return False
   
    def open_basedir(self, get):
        """
        检测是否开启放开跨站和网站日志
        """
        data = public.M('sites').where("status=? and project_type ='PHP'",(1,)).field('id,name,path,status,ps,addtime,edate,type').select()
        if len(data) == 0: return []
    
        bak = False
        bak_list = public.M('crontab').where("sType='site' and ( sName=? or sName=?)",('all','ALL',)).select()
        if len(bak_list) > 0: bak = True
        ret = []
        for i in data:
            get.path = i['path']
            get.id = i['id']
            data2 = {}        
            info = self.__panelSite.get_site_info(i['name'])
            if info:       
                
                data2['logs'] = info['logs']
                data2['userini'] =  False
                if os.path.exists('{}/.user.ini'.format(info['path'])): data2['userini'] = True
                data2['site'] = i['name']
                
                site_bak = bak
                if not site_bak:
                    slist = public.M('crontab').where("sType = 'site' and sName=?",(i['name'],)).select()                 
                    if len(slist) > 0: site_bak = True
                data2['backup'] = site_bak

                ret.append(data2)
        return ret


    def get_database(self,get):
        """
        检测数据库
        """
        dlist = public.M('databases').field('id,name').select()
        if len(dlist) == 0: return []

        bak = False
        bak_list = public.M('crontab').where("sType='database' and  ( sName=? or sName=?)",('all','ALL')).select()
        if len(bak_list) > 0: bak = True
        ret = []
        for i in dlist:
            data = {}
            data['name'] = i['name']
            db_bak = bak
            if not db_bak:
                slist = public.M('crontab').where("sType = 'database' and sName=?",(i['name'],)).select()                 
                if len(slist) > 0: db_bak = True
            data['backup'] = db_bak
            ret.append(data)
        return ret


    def start_san(self,get):
        """
        开始扫描
        """
        try:
            __key_name = 'panel_sec_' + time.strftime('%Y-%m-%d')
            if not __key_name in session or hasattr(get,'force'): 
                session[__key_name] = '1'

                data = {}
                data['list'] = []
                data['count'] = 0   
                data['time'] = int(time.time())
                #检测系统用户
                users = self.get_users(get)
                obj = { 'name':'检测系统危险用户','status': 1 ,'list':[]}
                for x in users:
                    if re.search('\$',x): 
                        obj['status'] = 0
                        data['count']+=1
                        obj['list'].append({'title':'系统用户中存在隐藏用户[{}],请立即检查是否为后门用户.'.format(x),'level':5,'status':0 })
                data['list'].append(obj)

                mstas = self.GetSshInfo(get)
                if mstas:                
                    #检测远程桌面
                    obj = { 'name':'检测远程桌面端口/爆破信息','status':1 ,'list':[]}
                    master_err = self.get_err_count(4625,7)
                    if master_err > 0:            
                        obj['status'] = 0
                        data['count']+=1
                        obj['list'].append({'title':"近七天远程桌面被爆破 {} 次，建议修改远程桌面端口. <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-50986-1-1.html'>->>教程</a>".format(master_err),'level': 5})
            
                    if  int(mstas['port']) == 3389 and mstas['status']:
                        obj['status'] = 0
                        data['count']+=1
                        obj['list'].append({'title':"远程桌面使用默认端口：3389，建议修改远程桌面端口. <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-50986-1-1.html'>->>教程</a>",'level': 5})
                    data['list'].append(obj)

                #检测web运行用户
                obj = { 'name':'检测Web服务器环境','status':1 ,'list':[]}
                get_www = self.get_www(get)
                if get_www:
                    obj['status'] = 0
                    data['count']+=1
                    obj['list'].append({'title':"当前Web服务器采用 {},如果php未使用opcache等缓存扩展，建议切换至www用户权限 <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-50982-1-1.html'>->>教程</a>".format(get_www['web_user']),'level': 5})

                check_waf = self.check_waf(get)
                if check_waf:          
                    obj['status'] = 0
                    data['count']+=1
                    obj['list'].append({'title':"当前Web服务器{}， 未安装{}防火墙,建议使用付费版本的WAF防火墙. <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-13647-1-1.html'>->>教程</a>".format(check_waf['web'],check_waf['waf']),'level': 4})
                data['list'].append(obj)

                #检测redis
                obj = { 'name':'检测Redis外网环境','status':1 ,'list':[]}
                get_redis = self.get_redis(get)
                if get_redis:
                    obj['status'] = 0
                    data['count']+=1
                    obj['list'].append({'title':"redis软件未设置密码或者开启了0.0.0.0 对外开放,请及时更改.",'level': 5})
                data['list'].append(obj)

                #检测数据库环境
                obj = { 'name':'检测数据库环境','status':1 ,'list':[]}
                db_list = self.get_database(get)
                for i in db_list:
                    if not i['backup']:
                        data['count']+=1
                        obj['list'].append({"title": "数据库 {} 未设置自动备份，请通过计划任务添加数据库备份  <a class='btlink' href='/crontab'>->>立即前往</a> ".format(i['name']),'level': 2})
                if len(obj['list']) > 0: obj['status'] = 0
                data['list'].append(obj)

                #检测网站防跨站
                obj = { 'name':'检测网站环境','status':1 ,'list':[]}
                open_basedir = self.open_basedir(get)
                if open_basedir:
                
                    for i in open_basedir:               
                        if not i['userini']:
                            data['count']+=1
                            obj['list'].append({"title": "网站 {} 未开启防跨站,建议开启防跨站 <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-50988-1-1.html'>->>教程</a>".format(i['site']),'level': 5})

                        if not i['logs']:
                            data['count']+=1
                            obj['list'].append({"title": "网站 {} 未开启访问日志,建议开启访问日志 <a class='btlink' target='_blank' href='https://www.bt.cn/bbs/thread-50988-1-1.html'>->>教程</a>".format(i['site']),'level': 2})
                        if not i['backup']:
                            data['count']+=1
                            obj['list'].append({"title": "网站 {} 未设置自动备份，请通过计划任务添加网站备份 <a class='btlink' href='/crontab'>->>立即前往</a> ".format(i['site']),'level': 2})
                  
                    if len(obj['list']) > 0:
                        obj['status'] = 0
                data['list'].append(obj)
                public.writeFile('data/sec_data.json',json.dumps(data))

                if hasattr(get,'force'):
                    pass
            else:
                try:
                    data = json.loads(public.readFile('data/sec_data.json'))  
                except :
                    data = {}
                
            return data
        except :
            error_info = public.get_error_info()            
            return public.returnMsg(False,error_info);
            
    
    

