from cve_search import db as cve_db
from logging.handlers import TimedRotatingFileHandler
from cve_search.lib.Config import Configuration
from cve_search.lib.ProgressBar import progressbar
from dateutil.parser import parse as parse_datetime
from pymongo import MongoClient
from .cve_handler import CVEHandler
from xml.sax import make_parser
import logging
import sys
import time
import nmap
import requests


# create logger
logHandler = TimedRotatingFileHandler('logs/scanner.log', when="midnight", interval=1, backupCount=7)
logFormatter = logging.Formatter('%(asctime)s %(name)s[%(process)d] %(levelname)s %(message)s')
logHandler.setFormatter(logFormatter)

logger = logging.getLogger()
logger.addHandler(logHandler)
logger.setLevel(logging.DEBUG)

nm = nmap.PortScanner()
client = MongoClient('127.0.0.1', 27017)
db = client['cvedb']
#db_mgmt.py
defaultvalue = {}
defaultvalue['cwe'] = "Unknown"

def search_vulnerabilities(service):
    """Scan vulnerabiliies by given CPE name.
    """
    response = {'cpe': service['cpe'], 'vulnerabilities': []}
    for item in cve_db.cvesForCPE(service['cpe']):
        vulnerability = {'vulnerability': '','vulnerability_text': '", "score": "'}
        vulnerability['vulnerability'] = item['id']
        vulnerability['vulnerability_text'] = item['summary']
        vulnerability['score'] = item['cvss']
        vulnerability['port'] = str(service['port'])
        response['vulnerabilities'].append(vulnerability)
    return response

def save_scan_result(data):
    response = ''
    try:
        r = requests.post('https://api.ersins.com/scan/save', json = data)
        response = r.json()
        logger.info('SAVED SCAN RESULT: {0}\n RESPONSE: {1}'.format(len(data['vulnerabilities']) , response))
    except:
        logger.error('There is a erro on saving scan reulst')
    return response

def verify_ers_auth(data):
    response = ''
    try:
        r = requests.post('https://api.ersins.com/scan/auth', json = data)
        response = r.json()
    except:
        logger.error('There is a erro on saving scan reulst')
    return response

def find_ips():
    try:
        ips = [requests.get('https://api.ersins.com/scan/find').json()]
        logger.info('IPS RETURNED FROM API.ERSINS {}'.format(ips))
        new_ips = []
        for ip in ips:
            if 'ip' in ip.keys():
                new_ips.append(ip)
        ips = new_ips
    except:
        logger.error('There is a erro on retrieving ip from api.ersins.com/scan/find')
        # ips = [{'success': True, 'policy_key': '16f9255fe539f8c060b15978916520ae', 'ip': '127.0.0.1'}]
    if len(ips) == 0:
        time.sleep(3)
        #ips = [{'success': True, 'policy_key': '16f9255fe539f8c060b15978916520ae', 'ip': '208.180.254.15'}]
    return ips

def start_scan(username, password):
    """Scan services and search for vulnerabiliies.
    """
    credentials = {'user': username, 'pass': password}
    auth_response = verify_ers_auth(credentials)
    try:
        if 'role' in auth_response.keys():
            if auth_response['role'] == 'script' and auth_response['verified']:
                ips = find_ips()
                logger.info('IP/s returned from api.ersins.com/scan/find: {}'.format(ips))
                responses = []
                services = []
                if len(ips) > 0:
                    save_scan_status({'status_list': ips}, 'REQUESTED')
                    logger.info('Set scan status to "REQUESTED" each ips address')
                    for ip in ips:
                        logger.info('Starting scan for ip: {0} and policy_key: {1}'.format(ip['ip'], ip['policy_key']))
                        update_scan_status(ip['ip'], 'PENDING', current_time())
                        logger.info('Set scan status to "PENDING" for ip: {}'.format(ip['ip']))
                        services = scan_services_by_ip(ip)
                        logger.info('Services running: {0}\n Services length: {1}'.format(services, len(services['services'])))
                        if len(services['services']) > 0:
                            response_to_save_api = {'ip': '', 'policy_key':'', 'vulnerabilities': []}
                            response_to_save_api['ip'] = ip['ip']
                            response_to_save_api['policy_key'] = ip['policy_key']
                            for service in services['services']:
                                if service['cpe'] != '':
                                    service['cpe'] = service['cpe'][7:]
                                    logger.info('Searching for vulnerabilities for service cpe: {}'.format(service['cpe']))
                                    vulnerabilities = search_vulnerabilities(service)
                                    response_to_save_api['vulnerabilities'] = response_to_save_api['vulnerabilities'] + vulnerabilities['vulnerabilities']
                                    logger.info('Vulnerabilities found: {}'.format(vulnerabilities['vulnerabilities']))
                                    logger.info('Vulnerabilities found length: {}'.format(len(vulnerabilities['vulnerabilities'])))
                                    for vulnerability in vulnerabilities['vulnerabilities']:
                                        response = {}
                                        response['ip'] = ip['ip']
                                        response['policy_key'] = ip['policy_key']
                                        response['port'] = service['port']
                                        response['service'] = service['name']
                                        response['version'] = service['version']
                                        response['vulnerability'] = vulnerability['vulnerability']
                                        response['vulnerability_text'] = vulnerability['vulnerability_text']
                                        response['score'] = vulnerability['score']
                                        responses.append(response)

                            save_scan_result(response_to_save_api)
                        else:
                            logger.warning('Any port/servies detected by nmap')
                            response_to_save_api = {'ip': '', 'policy_key':'', 'vulnerabilities': []}
                            response_to_save_api['ip'] = ip['ip']
                            response_to_save_api['policy_key'] = ip['policy_key']
                            save_scan_result(response_to_save_api)
                        #UPDAE SCAN SSTATUS TO COPLETED
                        update_scan_status(ip['ip'], 'COMPLETED', current_time())
                        logger.info('Set scan status to "COMPLETED" for ip: {}'.format(ip['ip']))

                    try:
                        data = {'history_list': responses}
                        history_bulk_insert(data)
                    except:
                        logger.error('there is an error trying to history bulk insert')
    except:
        return auth_response
    return "ok"

def history_bulk_insert(params):
    logger.info('Saving to history colleciton: {}'.format(params['history_list']))
    """Bulk insert history of scans on database.
    """
    last_id = history_last_id()
    last_id = last_id != None and int(last_id) + 1 or 1
    response = []
    for item in params['history_list']:
        obj = {}
        obj['id'] = last_id
        obj['ip'] = item['ip']
        obj['port'] = item['port']
        obj['score'] = item['score']
        obj['service'] = item['service']
        obj['version'] = item['version']
        obj['vulnerability'] = item['vulnerability']
        obj['vulnerability_text'] = item['vulnerability_text']
        response.append(obj)
    if len(response):
        result = db['history'].insert_many(response)
    return '\nsuccess'

def history_last_id():
    """Retrieve last id of scan results history.
    """
    results = db['history'].find_one(sort=[("id", -1)])
    if results != None:
        results = results["id"]
    return results

def update_collections():
    file_prefix = "nvdcve-2.0-"
    file_suffix = ".xml.gz"
    file_mod = "modified"
    file_rec = "recent"
    getfile = file_prefix + file_mod + file_suffix
    try:
        (f, r) = Configuration.getFile(Configuration.getFeedURL('cve') + getfile)
    except:
        sys.exit("Cannot open url %s. Bad URL or not connected to the internet?"%(Configuration.getFeedURL("cve") + getfile))
    i = cve_db.getInfo("cves")
    last_modified = parse_datetime(r.headers['last-modified'], ignoretz=True)
    if i is not None:
        if last_modified == i['last-modified']:
            logger.info("Not modified")
            return "Not modified"
    cve_db.setColUpdate("cves", last_modified)
    parser = make_parser()
    ch = CVEHandler()
    parser.setContentHandler(ch)
    parser.parse(f)
    for item in ch.cves:
        # check if the CVE already exists.
        x = cve_db.getCVE(item['id'])
        # if so, update the entry.
        if x:
            if 'cvss' not in item:
                item['cvss'] = None
            if 'cwe' not in item:
                item['cwe'] = defaultvalue['cwe']
            cve_db.updateCVE(item)
        else:
            cve_db.insertCVE(item)
    # get the 'recent' file
    getfile = file_prefix + file_rec + file_suffix
    try:
        (f, r) = Configuration.getFile(Configuration.getFeedURL('cve') + getfile)
    except:
        sys.exit("Cannot open url %s. Bad URL or not connected to the internet?"%(Configuration.getFeedURL("cve") + getfile))
    parser = make_parser()
    ch = CVEHandler()
    parser.setContentHandler(ch)
    parser.parse(f)
    for item in progressbar(ch.cves):
        # check if the CVE already exists.
        x = cve_db.getCVE(item['id'])
        # if so, update the entry.
        if x:
            if args.v:
                logger.info("item found : " + item['id'])
            if 'cvss' not in item:
                item['cvss'] = None
            else:
                item['cvss'] = float(item['cvss'])
            if 'cwe' not in item:
                item['cwe'] = defaultvalue['cwe']
            cve_db.updateCVE(item)
        # if not, create it.
        else:
            cve_db.insertCVE(item)
    return 'success'

def status_last_id():
    """Retrieve last id of scan status.
    """
    if("status" in db.collection_names()):
        try:
            results = db['status'].find_one(sort=[("id", -1)])['id']
        except:
            results = None
    else:
        results = None
    return results

def save_scan_status(params, status):
    """Insert status of scans on database. Insert one or many records.
    """
    last_id = status_last_id()
    last_id = last_id != None and int(last_id) + 1 or 1
    response = []
    for item in params['status_list']:
        obj = {}
        obj['id'] = last_id
        obj['ip'] = item['ip']
        obj['policy_key'] = item['policy_key']
        obj['status'] = status
        obj['date_time_requested'] = current_time()
        response.append(obj)
    db['status'].insert_many(response)
    return '\nsuccess'

def update_scan_status(ip, status_param, datetime):
    """Update status of scan to "Ready".
    """
    #params = request.get_json()
    status = db['status'].find_one({"ip": ip}, sort=[("id", -1)])
    response = None
    if(status):
        datekey = ''
        if status_param == 'PENDING':
            datekey = 'date_time_started'
        elif status_param == 'COMPLETED':
            datekey = 'date_time_completed'
        db['status'].update_one({'_id':status['_id']}, {"$set": {"status":status_param,  datekey: datetime}}, upsert=False)
        response = '\success'
    else:
        response = 'Pending status with ip:'+ip+' no found.'
    return response

def scan_services_by_ip(ip):
    """
    Scan ports and services for a given IP address.
    DATA: results = {'ip': '', 'policy_key': '','results': []}
    POST: Returns an object with ip, policy and a list of results
          containing the services with its versions,
          Returns the CPE name.
    """
    params = ip
    nm.scan(str(params['ip']))
    results = {'ip': '', 'policy_key': '','services': []}
    results['ip'] = params['ip']
    results['policy_key'] = params['policy_key']
    try:
        all_ports_detected = nm[params['ip']]['tcp']
        for port in all_ports_detected:
            port_scan_result = all_ports_detected[port]
            port_scan_result['port'] = port
            if (int(port) == 443):
                try:
                    port_scan_result['tls_versions'] = scan_for_tls_support(params['ip'])
                except:
                    port_scan_result['tls_versions'] = []
            results['services'].append(port_scan_result)
    except:
        results['services'] = []
    return results

def current_time():
    localtime = time.asctime(time.localtime(time.time()))
    return localtime

def scan_for_tls_support(ip):
    ssl_results = nm.scan(ip, arguments="-p 443 --script ssl-enum-ciphers")
    ssl_ciphers_results = ssl_results['scan'][ip]['tcp'][443]['script']['ssl-enum-ciphers']
    ssl_tls_version_matches = re.findall('TLSv\d.\d', ssl_ciphers_results, re.DOTALL)
    return ssl_tls_version_matches
