# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lkq <lkq@bt.cn>
# |
# | 日志分析工具
# +-------------------------------------------------------------------
import os,sys
panelPath = os.getenv('BT_PANEL')
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public,time,re,json


class log_analysis:
    path = '{}/logs/web_log/'.format(panelPath)
  
    def __init__(self):
        if not os.path.exists(self.path): os.makedirs(self.path)       


    def speed_log(self, get):
        """
        @获取扫描日志
        @code -1 表示继续接收日志  1：表示日志接收完成
        """
        siteName = get['siteName']

        data = {}
        data['status'] = True
        data['code'] = -1

        r_path = '{}/{}'.format(self.path,siteName)
        data['msg'] = public.GetNumLines('{}/used.log'.format(r_path),100)

        used = '{}/used.json'.format(r_path)
        if os.path.exists(used) or not os.path.exists('{}/used.log'.format(r_path)):
            data['code'] = 1          
        return data

    def log_thread(self,siteName,path):
        """
        @扫描日志线程
        """
        regs = {
            'xss':'(javascript|data:|alert\(|onerror=|%3Cimg%20src=x%20on.+=|%3Cscript|%3Csvg/|%3Ciframe/|%3Cscript%3E)',
            'sql':'(from.+?information_schema.+|select.+(from|limit)|union(.*?)select|extractvalue\(|case when|extractvalue\(|updatexml\(|sleep\()',
            'scan':'(\.\.|WEB-INF|/etc|\w\{1,6\}\.jsp |\w\{1,6\}\.php|\w+\.xml |\w+\.log |\w+\.swp |\w*\.git |\w*\.svn |\w+\.json |\w+\.ini |\w+\.inc |\w+\.rar |\w+\.gz |\w+\.tgz|\w+\.bak |/resin-doc)',
            'php':'(gopher://|php://|file://|phar://|dict://data://|eval\(|file_get_contents\(|phpinfo\(|require_once\(|copy\(|\_POST\[|file_put_contents\(|system\(|base64_decode\(|passthru\(|\/invokefunction\&|=call_user_func_array)',
        }
        size = 0;
        pre_time = time.time()
        t_size = os.path.getsize(path)
        
        r_path = '{}/{}'.format(self.path,siteName)
        
        
        if not os.path.exists(r_path): os.makedirs(r_path)            

        stop_pl = '{}/stop.pl'.format(r_path)
        pre_log = '{}/used.log'.format(r_path)
        used_log = '{}/used.json'.format(r_path)
        public.rmdir(pre_log)
        public.rmdir(used_log)
        
        public.writeFile(pre_log,'正在开始扫描，请稍后...\n')
        fname = os.path.basename(path)

        for filename in os.listdir(r_path):
            if filename.find(fname) >=0 :
                public.rmdir(r_path+'/'+filename)

        result = {'total':0,'start':time.time(),'filename':fname,'filepath':path}     
        
        print(stop_pl)
        for line in open(path,'rb'):  
            if os.path.exists(stop_pl):
                break
                
            for key in regs.keys():
                if not key in result: result[key] = 0
                if re.search(regs[key],line.decode()):                    
                    result[key] += 1
                    public.writeFile2('{}/{}_{}.log'.format(r_path,fname,key),line,'ab')
            result['total'] += 1
            size += len(line)

            if time.time() - pre_time >= 1.5 or size == t_size:
                pre_time = time.time()
                used = round(size * 100 / t_size,2)                      
                public.writeFile(pre_log,'{} % ........... ........... used: {} , total:{}\n'.format(used,public.to_size(size),public.to_size(t_size)),'a+')
            
        result['time'] = time.time() - result['start']

        for key in regs.keys():
            if not key in result: result[key] = 0                
        self.set_used_bysite(siteName,result)
        public.writeFile(used_log,json.dumps(result))   
        public.rmdir(stop_pl)
        return result


    def set_used_bysite(self,siteName,data):
        path = '{}/{}.json'.format(self.path,siteName)        
        try:            
             result = json.loads(public.readFile(path))
        except : 
            result = {}

        result[data['filename']] = data
        public.writeFile(path,json.dumps(result))   

    def get_detailed(self,get):
        """
        @获取指定类型的扫描日志
        """
        log_path = '{}/{}/{}_{}.log'.format(self.path,get.siteName,get.filename,get.type)
        if os.path.exists(log_path):
            data = public.GetNumLines(log_path,200)
            return public.returnMsg(True,data)

        return public.returnMsg(False,'暂无日志.')

    def stop_scan_logs(self,get):  
        """
        停止扫描日志
        """
        path = '{}/{}/stop.pl'.format(self.path,get['siteName'])
        public.writeFile(path,'True')
        return public.returnMsg(True,'停止扫描日志成功。') 

    def start_scan_logs(self,get):
        """
        @开始扫描日志
        """
        public.set_module_logs('win_log_analysis','start_scan_logs')
        path = get['path']
        siteName = get['siteName']
        if not os.path.exists(path): return public.returnMsg(False,'日志文件不存在')
        #大于1gb不分析
        if os.path.getsize(path) > 1024 * 1024 * 1024: 
            return public.returnMsg(False,'日志文件大于1GB，无法分析日志，请通过[计划任务 -> 日志切割]后重新分析。')

        public.run_thread(self.log_thread,(siteName,path,))

        return public.returnMsg(True,'正在扫描，请耐心等待。')

    def get_scan_result(self,get):
        """
        获取扫描结果
        """        
        siteName = get['siteName']        
        path = '{}/{}.json'.format(self.path,siteName)        
        try:            
             result = json.loads(public.readFile(path))
        except : 
            result = {}
        data = []
        for x in result.keys():
            data.append(result[x])   
            
        data = sorted(data, key=lambda x: x['start'], reverse=True)
        return data

if __name__ == "__main__":

    pass