#!/usr/bin/python3
# -*- coding: utf8 -*-

import collections
import os
import sys
import socket
import random
import re
import time

import configparser
import io

# 4096 - установление соединения
# 2048 - прием/передача датаграмм
#  128 - открытие сетевого соединения

class audit_op:
    connect = 4096  # установление соединения
    data = 2048  # прием/передача датаграмм
    socket = 128  # открытие сетевого соединения
    openw = 1024  # открытие файла/каталога на запись
    openr = 512  # открытия файла на чтение
    execute = 256  # запуск приложения
    create = 64  # создание
    remove = 32  # удаление
    rename = 16  # переименование
    chattr = 8  # изменение атрибутов
    edit = 4  # запись файла
    stat = 2  # получение информации
    read = 1  # чтение файла/каталога
    unknown = 0  # неизвестно

def create_fs_log(log_time, result, uid, gid, exe, parents, op, path):
    i = random.randint(0, 100500)
    if result >= 0:
        success = "yes"
    else:
        success = "no"
    s = 'type=SYSCALL msg=snaudit(%f:%d): arch=0 syscall=0' % (log_time, i)
    s += ' success=%s exit=%d uid=%d gid=%d' % (success, result, uid, gid)
    s += ' parents="%s" exe="%s" key="%d"' % (parents, exe, op)
    s += '\n'
    s += 'type=PATH msg=snaudit(%f:%d): name="%s" inode=0' % (log_time, i, path)
    s += '\n'
    return s

def create_dev_log(log):
    (log_time, action, bus, major, minor, name, uniq, uid, gid, comm, res) = log
    i = random.randint(0, 100500)
    s = 'type=SECDEV msg=snaudit(%f:%d): ' % (log_time, i)
    s += ' action=%s bus=0 seq=0 maj=%s min=%s' % (action, major, minor)
    s += ' name="%s" uniq="%s" uid=%s gid=%s comm="%s" res=%s' % (
        name, uniq, uid, gid, comm, res)
    s += '\n'
    return s

def create_secenv_log(log):
    (log_time, result, uid, gid, exe, action, path) = log
    i = random.randint(0, 100500)
    if result >= 0:
        success = "yes"
    else:
        success = "no"
    s = 'type=SECENV msg=snenv(%f:%d):' % (log_time, i)
    s += ' action=%s success=%s exit=%d uid=%d gid=%d' % (action, success, result, uid, gid)
    s += ' exe="%s" name="%s"' % (exe, path)
    s += '\n'
    return s

def create_firewall_log(log):
    #'time action ruleid msg pid exe uid gid proto saddr sport daddr dport'
    i = random.randint(0, 100500)
    #converting time format from string like 11/08/2019-18:28:34.259609 to dmesg timestamp
    datetime = log.time.split('.')[0]
    secs = int(log.time.split('.')[1])
    tstamp = int(time.mktime(time.strptime(datetime, '%m/%d/%Y-%H:%M:%S'))) #- time.timezone

    s = 'type=FIREWALL msg=snaudit(%d.%d:%d):' % (tstamp, secs, i)
    s += ' ruleid="%s" action="%s" fwmsg="%s" proto="%s" src="%s:%d" dst="%s:%d" uid=%d gid=%d count=%d seconds=%d' % \
         (log.ruleid, log.action, log.msg, log.proto, log.saddr, int(log.sport), log.daddr, int(log.dport),
          int(log.uid), int(log.gid), int(log.count if log.count != '' else 1), int(log.seconds if log.seconds != '' else -1))
    s += ' pid=%d exe="%s"' % (int(log.pid), log.exe)
    s += '\n'
    return s

def parse_dev_log(line):
    l = line.split('//')
    log_time = float(l[1].split()[1])  # '1458838668.673329' (%ld.%06ld)
    action = l[2].split()[1]
    bus = l[3].split()[1]
    seq = l[4].split()[1]
    major = l[5].split()[1]
    minor = l[6].split()[1]
    try:
        name = l[7].split()[1]
    except:
        name = ""
    try:
        uniq = l[8].replace('uniq: ', '')
    except:
        uniq = ""
    uid = l[9].split()[1]
    gid = l[10].split()[1]
    comm = l[11].split()[1]
    res = l[12].split()[1]
    return (log_time, action, bus, major, minor, name, uniq, uid, gid, comm, res)

def parse_secenv_log(line):
    l = line.split('//')
    log_time = float(l[1].split()[1])  # '1458838668.673329' (%ld.%06ld)
    log_comm = l[2][l[2].find(':') + 1:].strip()
    log_action = l[3].split()[1]
    log_file = l[4][l[4].find(':') + 1:].strip()
    log_uid = int(l[5].split()[1])
    log_gid = int(l[6].split()[1])
    log_result = int(l[7].split()[1])
    if log_file == 'none:0':
        log_file = 'socket: %d' % log_result
    return (log_time, log_result, log_uid, log_gid, log_comm, log_action, log_file)

def parse_fs_net_log(line):
    l = line.split('//')
    log_type = l[0].split()[1]  # 'FS' or 'NET'
    log_time = float(l[1].split()[1])  # '1458838668.673329' (%ld.%06ld)
    log_comm = l[2][l[2].find(':')+1:].strip()
    log_parents = l[3][l[3].find(':')+1:].strip()
    log_raw_op = l[4].split()[1]
    if log_raw_op == 'sock':
        log_op = audit_op.socket
    elif log_raw_op == 'conn':
        log_op = audit_op.connect
    elif log_raw_op == 'send' or log_raw_op == 'recv':
        log_op = audit_op.data
    elif log_raw_op == 'openw':
        log_op = audit_op.openw
    elif log_raw_op == 'openr':
        log_op = audit_op.openr
    elif log_raw_op == 'exec':
        log_op = audit_op.execute
    elif log_raw_op == 'creat':
        log_op = audit_op.create
    elif log_raw_op == 'remove':
        log_op = audit_op.remove
    elif log_raw_op == 'rename':
        log_op = audit_op.rename
    elif log_raw_op == 'chattr':
        log_op = audit_op.chattr
    elif log_raw_op == 'edit':
        log_op = audit_op.edit
    elif log_raw_op == 'stat':
        log_op = audit_op.stat
    elif log_raw_op == 'read':
        log_op = audit_op.read
    else:
        print("Unknown op: %s" % log_raw_op)
        log_op = audit_op.unknown
    log_file = l[5][l[5].find(':')+1:].strip()
    if log_type == 'FS':
        log_uid = int(l[6].split()[1])
        log_gid = int(l[7].split()[1])
        log_result = int(l[8].split()[1])
    else:
        log_file += ":%d" % int(l[6].split()[1])
        log_uid = int(l[7].split()[1])
        log_gid = int(l[8].split()[1])
        log_result = int(l[9].split()[1])
    if log_file == 'none:0':
        log_file = 'socket: %d' % log_result
    return (log_type, log_time, log_result,
            log_uid, log_gid, log_comm, log_parents, log_op, log_file)


def parse_firewall_log(line):
    FirewallAlertData = collections.namedtuple('FirewallAlertData',
                                               'time action ruleid msg pid exe uid gid count seconds proto saddr sport daddr dport')
    caps = re.findall('(.+)\s+(?:\[(.+)\])?\s+\[\*\*\]\s+\[\d+:([\d\-a-zA-Z]+):\d+\]\s*(\S.*\S|\S+)\s*\[\*\*\].*\[PID:\s*(.*)\s*\]'
                   '\s+\[Exefile:\s*(.*)\s*\]\s+\[UID:\s*(.*)\s*\]\s+\[GID:\s*([^\]]*)\s*\]'
                   '(?:\s+\[Count:\s*(.*)\s*\]\s+\[Seconds:\s*(.*)\s*\])?\s+.*\{(\w+)\}\s+([^:]+):(\d+)\s+->\s+([^:]+):(\d+)',
                   line)

    if len(caps) > 0:
        res = FirewallAlertData(*caps[0])
    else:
        res = []

    if len(res.action) == 0:
        res = res._replace(action = 'Alert')

    return res



def find_current_log(log_path):
    files = [i for i in os.listdir(log_path) if i[0:10] == 'audit.log.']
    if files:
        files.sort()
        return log_path + files[-1]
    else:
        return log_path + 'audit.log.' + str(int(time.time()))

def log_rewriter(sources, output_log_path):

    ini_str = '[global]\n' + open('/opt/secretnet/etc/audit/auditd.conf', 'r').read()
    ini_fp = io.StringIO(ini_str)
    config = configparser.RawConfigParser()
    config.read_file(ini_fp)

    max_log_file = config.getint("global", "max_log_file")
    opensocks = dict()

    while True:
        for src in sources:
            try:
                current_log = find_current_log(output_log_path)
                audit_log = open(current_log, 'a+')
                s = ''

                if src.type == 'proc':
                    dev = open(src.file, 'r')

                    for i in dev.readlines():
                        if i.find('DEV //') > 0:
                            s += create_dev_log(parse_dev_log(i))
                        elif i.find('SNENV FS //') == 0:
                            s += create_secenv_log(parse_secenv_log(i))
                        else:
                            log = parse_fs_net_log(i)
                            log_type, log_time, result, uid, gid, exe, parents, op, path = log
                            s += create_fs_log(log_time, result, uid, gid, exe, parents, op, path)

                    dev.close()

                elif src.type == 'sock_un':
                    if src not in opensocks:
                        if os.path.exists(src.file):
                            os.unlink(src.file)

                        opensocks[src] = sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
                        sock.bind(src.file)
                        sock.settimeout(0.5)
                        #sock.setblocking(0)

                    else:
                        sock = opensocks[src]

                    s = '';

                    while True:
                        try:
                            buf = sock.recv(1024)
                        except socket.timeout:
                            break

                        if len(buf) == 0:
                            break

                        log = parse_firewall_log(buf.decode())

                        if len(log) <= 0:
                            break

                        s += create_firewall_log(log)
                else:
                    continue

                if len(s) > 0:
                    audit_log.write(s)
                    audit_log.flush()

                audit_log.close()

                # Rotation
                size_log = os.path.getsize(current_log) / 1024 / 1024  # byte to MB
                if size_log > max_log_file:
                    old_log = output_log_path + "old/" + os.path.basename(current_log)
                    os.rename(current_log, old_log)

            except Exception as e:
                print("Error: %s" % e)
        time.sleep(1)

if __name__=='__main__':
    LogSource = collections.namedtuple('LogSource', 'file type')  # type: Type[LogSource]

    input_logs = [LogSource('/proc/secretnet/log', 'proc'),
                  LogSource('/opt/secretnet/var/tmp/fw-alerts.log', 'sock_un')]

    log_rewriter(input_logs, '/opt/secretnet/var/log/audit/')
