#!/usr/bin/python3

import sys
import os

def escaped_split(s, delim):
    # split by escaped, then by not-escaped
    escaped_delim = '\\'+delim
    sections = [p.split(delim) for p in s.split(escaped_delim)]
    ret = []
    prev = None
    for parts in sections: # for each list of "real" splits
        if prev is None:
            if len(parts) > 1:
                # Add first item, unless it's also the last in its section
                ret.append(parts[0])
        else:
            # Add the previous last item joined to the first item
            ret.append(escaped_delim.join([prev, parts[0]]))
        for part in parts[1:-1]:
            # Add all the items in the middle
            ret.append(part)
        prev = parts[-1]
    return ret

def find_token_value(line, token):
    if line.find(token):
        for i in escaped_split(line, ' '):
            if len(i) < len(token):
                continue
            if i[0:len(token)] == token:
                value = i[len(token):]
                return value.replace('\\', '')

class AuditRule:
    filepath = ""
    audit_openr_success = False
    audit_openw_success = False
    audit_exec_success = False
    audit_create_success = False
    audit_remove_success = False
    audit_rename_success = False
    audit_chattr_success = False
    audit_stat_success = False
    audit_read_success = False
    audit_edit_success = False
    audit_openr_failure = False
    audit_openw_failure = False
    audit_exec_failure = False
    audit_create_failure = False
    audit_remove_failure = False
    audit_rename_failure = False
    audit_chattr_failure = False
    audit_stat_failure = False
    audit_read_failure = False
    audit_edit_failure = False
    uid = -1
    gid = -1

    def __repr__(self):
        s = self.filepath + ";"
        if self.audit_openr_success:
            s += "openr_success,"
        if self.audit_openw_success:
            s += "openw_success,"
        if self.audit_exec_success:
            s += "exec_success,"
        if self.audit_create_success:
            s += "create_success,"
        if self.audit_remove_success:
            s += "remove_success,"
        if self.audit_rename_success:
            s += "rename_success,"
        if self.audit_chattr_success:
            s += "chattr_success,"
        if self.audit_stat_success:
            s += "stat_success,"
        if self.audit_read_success:
            s += "read_success,"
        if self.audit_edit_success:
            s += "edit_success,"
        if self.audit_openr_failure:
            s += "openr_failure,"
        if self.audit_openw_failure:
            s += "openw_failure,"
        if self.audit_exec_failure:
            s += "exec_failure,"
        if self.audit_create_failure:
            s += "create_failure,"
        if self.audit_remove_failure:
            s += "remove_failure,"
        if self.audit_rename_failure:
            s += "rename_failure,"
        if self.audit_chattr_failure:
            s += "chattr_failure,"
        if self.audit_stat_failure:
            s += "stat_failure,"
        if self.audit_read_failure:
            s += "read_failure,"
        if self.audit_edit_failure:
            s += "edit_failure,"
        if self.uid >= 0:
            s += "u:%d," % self.uid
        if self.gid >= 0:
            s += "g:%d," % self.gid
        return s

def parse_rule(line):
    rule = AuditRule()

    success = False
    failure = False

    if line.find('success=1') > 0:
        success = True
    elif line.find('success=0') > 0:
        failure = True
    else:
        success = True
        failure = True

    if line[0:2] == '-w':
        if success:
            rule.audit_exec_success = True
        if failure:
            rule.audit_exec_failure = True
        rule.filepath = escaped_split(line, ' ')[1].replace('\\', '')
    elif line.find('-S open') > 0:
        if line.find('-F a1&1') > 0:
            if success:
                rule.audit_openw_success = True
            if failure:
                rule.audit_openw_failure = True
        if line.find('-F a1=0') > 0:
            if success:
                rule.audit_openr_success = True
            if failure:
                rule.audit_openr_failure = True
    elif line.find('-S fstat') > 0:
        if success:
            rule.audit_stat_success = True
        if failure:
            rule.audit_stat_failure = True
    elif line.find('-S read') > 0:
        if success:
            rule.audit_read_success = True
        if failure:
            rule.audit_read_failure = True
    elif line.find('-S write') > 0:
        if success:
            rule.audit_edit_success = True
        if failure:
            rule.audit_edit_failure = True
    elif line.find('-S rename') > 0:
        if success:
            rule.audit_rename_success = True
        if failure:
            rule.audit_rename_failure = True
    elif line.find('-S unlink') > 0:
        if success:
            rule.audit_remove_success = True
        if failure:
            rule.audit_remove_failure = True
    elif line.find('-S creat') > 0:
        if success:
            rule.audit_create_success = True
        if failure:
            rule.audit_create_failure = True
    elif line.find('-S chmod') > 0:
        if success:
            rule.audit_chattr_success = True
        if failure:
            rule.audit_chattr_failure = True

    path = find_token_value(line, 'path=')
    if path:
        rule.filepath = path

    path = find_token_value(line, 'dir=')
    if path:
        rule.filepath = path

    uid = find_token_value(line, 'uid=')
    if uid:
        rule.uid = int(uid)

    gid = find_token_value(line, 'gid=')
    if gid:
        rule.gid = int(gid)

    return rule

class AuditNetRule:
    audit_socket_success = False
    audit_connect_success = False
    audit_data_success = False
    audit_socket_failure = False
    audit_connect_failure = False
    audit_data_failure = False
    uid = -1
    gid = -1

    def __repr__(self):
        s = ''
        if self.uid >= 0:
            s += 'u:'
        elif self.gid >= 0:
            s += 'g:'
        else:
            s += 'a:'

        if not self.audit_socket_success and not self.audit_socket_failure:
            s += '-:'
        elif self.audit_socket_success and not self.audit_socket_failure:
            s += 's:'
        elif self.audit_socket_failure and not self.audit_socket_success:
            s += 'f:'
        else:
            s += 'a:'

        if not self.audit_data_success and not self.audit_data_failure:
            s += '-:'
        elif self.audit_data_success and not self.audit_data_failure:
            s += 's:'
        elif self.audit_data_failure and not self.audit_data_success:
            s += 'f:'
        else:
            s += 'a:'

        if not self.audit_connect_success and not self.audit_connect_failure:
            s += '-:'
        elif self.audit_connect_success and not self.audit_connect_failure:
            s += 's:'
        elif self.audit_connect_failure and not self.audit_connect_success:
            s += 'f:'
        else:
            s += 'a:'

        if self.uid >= 0:
            s += "%d" % self.uid
        elif self.gid >= 0:
            s += "%d" % self.gid
        else:
            s += "0"

        return s


def parse_net_rule(line):
    rule = AuditNetRule()

    success = False
    failure = False
    audit_socket = False
    audit_data = False
    audit_connect = False

    if line.find('success=1') > 0:
        success = True
    elif line.find('success=0') > 0:
        failure = True
    else:
        success = True
        failure = True

    if line.find('-S socketcall') > 0:
        if line.find('-k 128') > 0:
            audit_socket = True
        elif line.find('-k 2048') > 0:
            audit_data = True
        elif line.find('-k 4096') > 0:
            audit_connect = True
    elif line.find('-S socket') > 0:
        audit_socket = True
    elif line.find('-S bind') > 0:
        audit_connect = True
    elif line.find('-S accept') > 0:
        audit_connect = True

    if line.find('-S connect') > 0:
        audit_connect = True

    if line.find('-S sendto') > 0:
        audit_data = True

    if audit_socket:
        if success:
            rule.audit_socket_success = True
        if failure:
            rule.audit_socket_failure = True

    if audit_data:
        if success:
            rule.audit_data_success = True
        if failure:
            rule.audit_data_failure = True

    if audit_connect:
        if success:
            rule.audit_connect_success = True
        if failure:
            rule.audit_connect_failure = True

    uid = find_token_value(line, 'uid=')
    if uid:
        rule.uid = int(uid)

    gid = find_token_value(line, 'gid=')
    if gid:
        rule.gid = int(gid)

    return rule

f = open("/opt/secretnet/etc/audit/audit.rules", "r")
raw_rules = f.read()
f.close()

rules = []
net_rules = []
for line in raw_rules.split('\n'):
    if line[0:2] == "-a" or line[0:2] == "-w":
        if line.find('sock') > 0 or line.find('send') > 0 or line.find('conn') > 0:
            net_rules += [parse_net_rule(line)]
        else:
            rules += [parse_rule(line)]

merged_rules = []
for r in rules:
    found = False
    for mr in merged_rules:
        if r.filepath == mr.filepath and r.uid == mr.uid and r.gid == mr.gid:
            found = True

            if r.audit_openr_success:
                mr.audit_openr_success = True
            if r.audit_openw_success:
                mr.audit_openw_success = True
            if r.audit_exec_success:
                mr.audit_exec_success = True
            if r.audit_create_success:
                mr.audit_create_success = True
            if r.audit_rename_success:
                mr.audit_rename_success = True
            if r.audit_remove_success:
                mr.audit_remove_success = True
            if r.audit_chattr_success:
                mr.audit_chattr_success = True
            if r.audit_stat_success:
                mr.audit_stat_success = True
            if r.audit_read_success:
                mr.audit_read_success = True
            if r.audit_edit_success:
                mr.audit_edit_success = True

            if r.audit_openr_failure:
                mr.audit_openr_failure = True
            if r.audit_openw_failure:
                mr.audit_openw_failure = True
            if r.audit_exec_failure:
                mr.audit_exec_failure = True
            if r.audit_create_failure:
                mr.audit_create_failure = True
            if r.audit_rename_failure:
                mr.audit_rename_failure = True
            if r.audit_remove_failure:
                mr.audit_remove_failure = True
            if r.audit_chattr_failure:
                mr.audit_chattr_failure = True
            if r.audit_stat_failure:
                mr.audit_stat_failure = True
            if r.audit_read_failure:
                mr.audit_read_failure = True
            if r.audit_edit_failure:
                mr.audit_edit_failure = True

    if not found:
        merged_rules += [r]

merged_net_rules = []
for nr in net_rules:
    found = False
    for mnr in merged_net_rules:
        if nr.uid == mnr.uid and nr.uid >= 0 or \
           nr.gid == mnr.gid and nr.gid >= 0 or \
           nr.uid < 0 and nr.gid < 0 and mnr.uid < 0 and mnr.gid < 0:
            found = True

            if nr.audit_socket_success:
                mnr.audit_socket_success = True
            if nr.audit_connect_success:
                mnr.audit_connect_success = True
            if nr.audit_data_success:
                mnr.audit_data_success = True

            if nr.audit_socket_failure:
                mnr.audit_socket_failure = True
            if nr.audit_connect_failure:
                mnr.audit_connect_failure = True
            if nr.audit_data_failure:
                mnr.audit_data_failure = True

    if not found:
        merged_net_rules += [nr]

# Remove old fs rules
os.system('find /proc/secretnet/fs_audit/rules/ -type f -printf "%f\n" '
          + '| while read HASH; do '
          + '      echo ${HASH} > /proc/secretnet/fs_audit/del; '
          + '  done')

# Write new fs rules
for mr in merged_rules:
    f = open('/proc/secretnet/fs_audit/add', 'w')
    f.write(str(mr))
    f.close()

# Remove old net rules
os.system('echo > /proc/secretnet/net_audit/clean')

# Write new net rules
for mnr in merged_net_rules:
    f = open('/proc/secretnet/net_audit/add', 'w')
    f.write(str(mnr))
    f.close()
