# -*- coding: utf-8 -*-
#
# LOKI Logger

import sys, re
from colorama import Fore, Back, Style
from colorama import init
import codecs
import datetime
import traceback
import rfc5424logging
import logging
from logging import handlers
import socket
from helpers import removeNonAsciiDrop

__version__ = '0.31.0'

# Logger Class -----------------------------------------------------------------
class LokiLogger():

    STDOUT_CSV = 0
    STDOUT_LINE = 1
    FILE_CSV = 2
    FILE_LINE = 3
    SYSLOG_LINE = 4

    no_log_file = False
    log_file = "loki.log"
    csv = False
    hostname = "NOTSET"
    alerts = 0
    warnings = 0
    notices = 0
    messagecount = 0
    only_relevant = False
    remote_logging = False
    debug = False
    linesep = "\n"

    def __init__(self, no_log_file, log_file, hostname, remote_host, remote_port, syslog_tcp, csv, only_relevant, debug, platform, caller, customformatter=None):
        self.version = __version__
        self.no_log_file = no_log_file
        self.log_file = log_file
        self.hostname = hostname
        self.csv = csv
        self.only_relevant = only_relevant
        self.debug = debug
        self.caller = caller
        self.CustomFormatter = customformatter
        if "windows" in platform.lower():
            self.linesep = "\r\n"

        reload(sys)
        sys.setdefaultencoding('utf8')

        # Colorization ----------------------------------------------------
        init()

        # Welcome
        if not self.csv:
            self.print_welcome()

        # Syslog server target
        if remote_host:
            try:
                # Create remote logger
                self.remote_logger = logging.getLogger('LOKI')
                self.remote_logger.setLevel(logging.DEBUG)
                socket_type = socket.SOCK_STREAM if syslog_tcp else socket.SOCK_DGRAM
                remote_syslog_handler = rfc5424logging.Rfc5424SysLogHandler(address=(remote_host, remote_port), facility=handlers.SysLogHandler.LOG_LOCAL3, socktype=socket_type)
                self.remote_logger.addHandler(remote_syslog_handler)
                self.remote_logging = True
            except Exception as e:
                print('Failed to create remote logger: ' + str(e))
                sys.exit(1)

    def log(self, mes_type, module, message):

        # Remove all non-ASCII characters
        # message = removeNonAsciiDrop(message)
        codecs.register(lambda message: codecs.lookup('utf-8') if message == 'cp65001' else None)

        if not self.debug and mes_type == "DEBUG":
            return

        # Counter
        if mes_type == "ALERT":
            self.alerts += 1
        if mes_type == "WARNING":
            self.warnings += 1
        if mes_type == "NOTICE":
            self.notices += 1
        self.messagecount += 1

        if self.only_relevant:
            if mes_type not in ('ALERT', 'WARNING'):
                return

        # to file
        if not self.no_log_file:
            self.log_to_file(message, mes_type, module)

        # to stdout
        try:
            self.log_to_stdout(message.encode('ascii', errors='replace'), mes_type)
        except Exception as e:
            print ("Cannot print certain characters to command line - see log file for full unicode encoded log line")
            self.log_to_stdout(removeNonAsciiDrop(message), mes_type)

        # to syslog server
        if self.remote_logging:
            self.log_to_remotesys(message, mes_type, module)

    def Format(self, type, message, *args):
        if self.CustomFormatter == None:
            return message.format(*args)
        else:
            return self.CustomFormatter(type, message, args)

    def log_to_stdout(self, message, mes_type):
        # check tty encoding
        encoding = ""
        if sys.stdout.encoding is not None:
            encoding = sys.stdout.encoding
        else:
            # fallback on utf-8
            encoding = "utf-8"

        # Prepare Message
        codecs.register(lambda message: codecs.lookup('utf-8') if message == 'cp65001' else None)
        message = message.encode(encoding, errors='replace')

        if self.csv:
            print (self.Format(self.STDOUT_CSV, '{0},{1},{2},{3}', getSyslogTimestamp(), self.hostname, mes_type, message))

        else:

            try:

                key_color = Fore.WHITE
                base_color = Fore.WHITE+Back.BLACK
                high_color = Fore.WHITE+Back.BLACK

                if mes_type == "NOTICE":
                    base_color = Fore.CYAN+''+Back.BLACK
                    high_color = Fore.BLACK+''+Back.CYAN
                elif mes_type == "INFO":
                    base_color = Fore.GREEN+''+Back.BLACK
                    high_color = Fore.BLACK+''+Back.GREEN
                elif mes_type == "WARNING":
                    base_color = Fore.YELLOW+''+Back.BLACK
                    high_color = Fore.BLACK+''+Back.YELLOW
                elif mes_type == "ALERT":
                    base_color = Fore.RED+''+Back.BLACK
                    high_color = Fore.BLACK+''+Back.RED
                elif mes_type == "DEBUG":
                    base_color = Fore.WHITE+''+Back.BLACK
                    high_color = Fore.BLACK+''+Back.WHITE
                elif mes_type == "ERROR":
                    base_color = Fore.MAGENTA+''+Back.BLACK
                    high_color = Fore.WHITE+''+Back.MAGENTA
                elif mes_type == "RESULT":
                    if "clean" in message.lower():
                        high_color = Fore.BLACK+Back.GREEN
                        base_color = Fore.GREEN+Back.BLACK
                    elif "suspicious" in message.lower():
                        high_color = Fore.BLACK+Back.YELLOW
                        base_color = Fore.YELLOW+Back.BLACK
                    else:
                        high_color = Fore.BLACK+Back.RED
                        base_color = Fore.RED+Back.BLACK

                # Colorize Type Word at the beginning of the line
                type_colorer = re.compile(r'([A-Z]{3,})', re.VERBOSE)
                mes_type = type_colorer.sub(high_color+r'[\1]'+base_color, mes_type)
                # Break Line before REASONS
                linebreaker = re.compile('(MD5:|SHA1:|SHA256:|MATCHES:|FILE:|FIRST_BYTES:|DESCRIPTION:|REASON_[0-9]+)', re.VERBOSE)
                message = linebreaker.sub(r'\n\1', message)
                # Colorize Key Words
                colorer = re.compile('([A-Z_0-9]{2,}:)\s', re.VERBOSE)
                message = colorer.sub(key_color+Style.BRIGHT+r'\1 '+base_color+Style.NORMAL, message)

                # Print to console
                if mes_type == "RESULT":
                    res_message = "\b\b%s %s" % (mes_type, message)
                    print (base_color+' '+res_message+' '+Back.BLACK)
                    print (Fore.WHITE+' '+Style.NORMAL)
                else:
                    sys.stdout.write("%s\b\b%s %s%s%s%s\n" % (base_color, mes_type, message, Back.BLACK,Fore.WHITE,Style.NORMAL))

            except Exception as e:
                if self.debug:
                    traceback.print_exc()
                    sys.exit(1)
                print ("Cannot print to cmd line - formatting error")

    def log_to_file(self, message, mes_type, module):
        try:
            # Write to file
            with codecs.open(self.log_file, "a", encoding='utf-8') as logfile:
                if self.csv:
                    logfile.write(self.Format(self.FILE_CSV, u"{0},{1},{2},{3},{4}{5}", getSyslogTimestamp(), self.hostname, mes_type, module, message, self.linesep))
                else:
                    logfile.write(self.Format(self.FILE_LINE, u"{0} {1} LOKI: {2}: MODULE: {3} MESSAGE: {4}{5}", getSyslogTimestamp(), self.hostname, mes_type.title(), module, message, self.linesep))
        except Exception as e:
            if self.debug:
                traceback.print_exc()
                sys.exit(1)
            print("Cannot print line to log file {0}".format(self.log_file))

    def log_to_remotesys(self, message, mes_type, module):
        # Preparing the message
        syslog_message = self.Format(self.SYSLOG_LINE, "LOKI: {0}: MODULE: {1} MESSAGE: {2}", mes_type.title(), module, message)
        try:
            # Mapping LOKI's levels to the syslog levels
            if mes_type == "NOTICE":
                self.remote_logger.info(syslog_message, extra={'msgid': str(self.messagecount)})
            elif mes_type == "INFO":
                self.remote_logger.info(syslog_message, extra={'msgid': str(self.messagecount)})
            elif mes_type == "WARNING":
                self.remote_logger.warning(syslog_message, extra={'msgid': str(self.messagecount)})
            elif mes_type == "ALERT":
                self.remote_logger.critical(syslog_message, extra={'msgid': str(self.messagecount)})
            elif mes_type == "DEBUG":
                self.remote_logger.debug(syslog_message, extra={'msgid': str(self.messagecount)})
            elif mes_type == "ERROR":
                self.remote_logger.error(syslog_message, extra={'msgid': str(self.messagecount)})
        except Exception as e:
            if self.debug:
                traceback.print_exc()
                sys.exit(1)
            print("Error while logging to remote syslog server ERROR: %s" % str(e))

    def print_welcome(self):

        if self.caller == 'main':
            print(Back.GREEN + " ".ljust(79) + Back.BLACK + Fore.GREEN)

            print("      __   ____  __ ______                            ")
            print ("     / /  / __ \/ //_/  _/                            ")
            print ("    / /__/ /_/ / ,< _/ /                              ")
            print ("   /____/\____/_/|_/___/                              ")
            print ("      ________  _____  ____                           ")
            print ("     /  _/ __ \/ ___/ / __/______ ____  ___  ___ ____ ")
            print ("    _/ // /_/ / /__  _\ \/ __/ _ `/ _ \/ _ \/ -_) __/ ")
            print ("   /___/\____/\___/ /___/\__/\_,_/_//_/_//_/\__/_/    ")

            print (Fore.WHITE)
            print ("   Copyright by Florian Roth, Released under the GNU General Public License")
            print ("   Version %s" % __version__)
            print ("  ")
            print ("   DISCLAIMER - USE AT YOUR OWN RISK")
            print ("   Please report false positives via https://github.com/Neo23x0/Loki/issues")
            print ("  ")
            print (Back.GREEN + " ".ljust(79) + Back.BLACK)
            print (Fore.WHITE+''+Back.BLACK)

        else:
            print ("  ")
            print (Back.GREEN + " ".ljust(79) + Back.BLACK + Fore.GREEN)

            print ("  ")
            print ("  LOKI UPGRADER ")

            print ("  ")
            print (Back.GREEN + " ".ljust(79) + Back.BLACK)
            print (Fore.WHITE + '' + Back.BLACK)

def getSyslogTimestamp():
    date_obj = datetime.datetime.utcnow()
    date_str = date_obj.strftime("%Y%m%dT%H:%M:%SZ")
    return date_str