#!/usr/bin/python
#
#         +-----------------------------------------------------------------------------+
#         | OpenFW UTM Community                                                       |
#         +-----------------------------------------------------------------------------+

import os
import sys
import glob
import shutil
import subprocess
from optparse import OptionParser
from endian.job.engine_control import send_cmd_to_engine

SETTINGS_FILE = '/var/efw/logging/settings'
SYSLOG_D_DIR = '/etc/syslog/syslog.d'
CERTS_DIR = '/etc/syslog/certs'
LOGGING_DIR = '/var/efw/logging'

def read_settings():
    """Reads Endian configuration settings directly from disk."""
    os.system('sync')
    settings = {}
    if os.path.exists(SETTINGS_FILE):
        try:
            f = open(SETTINGS_FILE, 'r')
            for line in f:
                line = line.strip()
                if line and not line.startswith('#') and '=' in line:
                    key, val = line.split('=', 1)
                    settings[key.strip().upper()] = val.strip().strip("'\"")
            f.close()
        except Exception:
            pass
    return settings

def prepare_certs_and_hashes():
    """Copies certs to /etc/syslog/certs/, cleans old hash symlinks, and creates the new hash symlink silently."""
    try:
        if not os.path.exists(CERTS_DIR):
            os.makedirs(CERTS_DIR)

        cert_map = {
            os.path.join(LOGGING_DIR, 'syslog-ca.crt'): os.path.join(CERTS_DIR, 'ca.crt'),
            os.path.join(LOGGING_DIR, 'syslog-cert.crt'): os.path.join(CERTS_DIR, 'cert.crt'),
            os.path.join(LOGGING_DIR, 'syslog-key.key'): os.path.join(CERTS_DIR, 'cert.key')
        }
        
        for src, dst in cert_map.items():
            if os.path.exists(src):
                shutil.copyfile(src, dst)
                os.chmod(dst, 0600)

        for old_link in glob.glob(os.path.join(CERTS_DIR, "*.0")):
            try:
                if os.path.islink(old_link) or os.path.exists(old_link):
                    os.remove(old_link)
            except OSError:
                pass

        ca_file = os.path.join(CERTS_DIR, 'ca.crt')
        if os.path.exists(ca_file):
            devnull = open(os.devnull, 'wb')
            cmd = ['openssl', 'x509', '-hash', '-noout', '-in', ca_file]
            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=devnull)
            stdout, _ = proc.communicate()
            devnull.close()
            
            if stdout:
                cert_hash = stdout.strip()
                if cert_hash:
                    symlink_name = os.path.join(CERTS_DIR, "%s.0" % cert_hash)
                    target_path = os.path.join(CERTS_DIR, 'ca.crt')
                    if not os.path.exists(symlink_name):
                        os.symlink(target_path, symlink_name)
    except Exception:
        pass

def fix_local_audit_tmpl():
    """Removes flags(final); from local config-audit.tmpl so logs can flow to remote syslog."""
    local_tmpl = os.path.join(SYSLOG_D_DIR, 'config-audit.tmpl')
    if os.path.exists(local_tmpl):
        try:
            f = open(local_tmpl, 'r')
            content = f.read()
            f.close()

            if 'flags(final);' in content:
                new_content = content.replace('flags(final);', '')
                f = open(local_tmpl, 'w')
                f.write(new_content)
                f.close()
        except Exception:
            pass

def build_destination_block(dest_name, server, port, use_tls, protocol='tcp'):
    """Builds destination block with dynamic protocol (tcp/udp) and TLS configuration."""
    if use_tls:
        protocol = 'tcp'

    if use_tls:
        return """destination %s {
        %s(
                "%s"
                port(%s)
                persist-name("%s")
                tls(
                        peer-verify(required-trusted)
                        ca-dir("/etc/syslog/certs")
                        cert-file("/etc/syslog/certs/cert.crt")
                        key-file("/etc/syslog/certs/cert.key")
                )
        );
};""" % (dest_name, protocol, server, port, dest_name)
    else:
        return """destination %s {
        %s(
                "%s"
                port(%s)
                persist-name("%s")
        );
};""" % (dest_name, protocol, server, port, dest_name)

def generate_firewall_tmpl(server, port, use_tls, protocol='tcp'):
    dest_block = build_destination_block("d_remote_firewall", server, port, use_tls, protocol)
    proto_str = "TLS/mTLS" if use_tls else protocol.upper()
    header = "# FIREWALL - REMOTE LOGGING VIA %s" % proto_str
    
    return """# ============================================================
%s
# ============================================================

%s

filter f_remote_firewall {
        facility(local2);
};

log {
        source(s_sys);
        filter(f_remote_firewall);
        destination(d_remote_firewall);
};
""" % (header, dest_block)

def generate_suricata_tmpl(server, port, use_tls, protocol='tcp'):
    dest_block = build_destination_block("d_remote_suricata", server, port, use_tls, protocol)
    proto_str = "TLS/mTLS" if use_tls else protocol.upper()
    header = "# SURICATA (IDS/IPS) - REMOTE LOGGING VIA %s" % proto_str

    return """# ============================================================
%s
# ============================================================

%s

filter f_remote_suricata {
        facility(local6) and program(suricata);
};

log {
        source(s_sys);
        filter(f_remote_suricata);
        destination(d_remote_suricata);
};
""" % (header, dest_block)

def generate_webfilter_tmpl(server, port, use_tls, protocol='tcp'):
    dest_block = build_destination_block("d_remote_webfilter", server, port, use_tls, protocol)
    proto_str = "TLS/mTLS" if use_tls else protocol.upper()
    header = "# WEBFILTER (SQUID) - REMOTE LOGGING VIA %s" % proto_str

    return """# ============================================================
%s
# ============================================================

%s

filter f_remote_webfilter {
        (facility(local6) and priority(info) and program(squid)) or
        (facility(local4) and priority(info) and program(squid));
};

log {
        source(s_sys);
        filter(f_remote_webfilter);
        destination(d_remote_webfilter);
};
""" % (header, dest_block)

def generate_audit_config_tmpl(server, port, use_tls, protocol='tcp'):
    dest_block = build_destination_block("d_remote_audit_config", server, port, use_tls, protocol)
    proto_str = "TLS/mTLS" if use_tls else protocol.upper()
    header = "# CONFIG AUDIT - REMOTE LOGGING VIA %s" % proto_str

    return """# ============================================================
%s
# ============================================================

%s

filter f_remote_audit_config {
        facility(daemon) and match("^config-audit:" value("MSG"));
};

log {
        source(s_sys);
        filter(f_remote_audit_config);
        destination(d_remote_audit_config);
};
""" % (header, dest_block)

def process_tls_templates():
    """Manages creation and removal of .tmpl files based on system configuration."""
    settings = read_settings()
    
    enable_remote = (settings.get('ENABLE_REMOTE_LOG', 'off').lower() == 'on')
    enable_tls = (settings.get('ENABLE_REMOTE_LOG_TLS', 'off').lower() == 'on')
    server = settings.get('REMOTE_LOG_ADDR', '').strip()
    
    protocol = settings.get('REMOTE_LOG_PROTO', 'UDP').lower().strip()
    if protocol not in ['tcp', 'udp']:
        protocol = 'udp'
    
    default_port = '6514' if enable_tls else '514'
    port = settings.get('REMOTE_LOG_PORT', '').strip()
    if not port:
        port = default_port

    if not os.path.exists(SYSLOG_D_DIR):
        os.makedirs(SYSLOG_D_DIR)

    fix_local_audit_tmpl()

    if enable_tls:
        prepare_certs_and_hashes()

    all_tmpl_file = os.path.join(SYSLOG_D_DIR, 'remote-all.tmpl')
    if os.path.exists(all_tmpl_file):
        os.remove(all_tmpl_file)

    fw_tmpl_file    = os.path.join(SYSLOG_D_DIR, 'remote-firewall.tmpl')
    ips_tmpl_file   = os.path.join(SYSLOG_D_DIR, 'remote-suricata.tmpl')
    web_tmpl_file   = os.path.join(SYSLOG_D_DIR, 'remote-webfilter.tmpl')
    audit_tmpl_file = os.path.join(SYSLOG_D_DIR, 'config-audit-remote.tmpl')

    enable_all = (settings.get('REMOTE_LOG_ALL', 'off').lower() == 'on')

    if enable_remote and len(server) > 0:
        if enable_all or settings.get('REMOTE_LOG_FIREWALL', 'off').lower() == 'on':
            f = open(fw_tmpl_file, 'w')
            f.write(generate_firewall_tmpl(server, port, enable_tls, protocol))
            f.close()
        elif os.path.exists(fw_tmpl_file):
            os.remove(fw_tmpl_file)

        if enable_all or settings.get('REMOTE_LOG_IPS', 'off').lower() == 'on':
            f = open(ips_tmpl_file, 'w')
            f.write(generate_suricata_tmpl(server, port, enable_tls, protocol))
            f.close()
        elif os.path.exists(ips_tmpl_file):
            os.remove(ips_tmpl_file)

        if enable_all or settings.get('REMOTE_LOG_WEBFILTER', 'off').lower() == 'on':
            f = open(web_tmpl_file, 'w')
            f.write(generate_webfilter_tmpl(server, port, enable_tls, protocol))
            f.close()
        elif os.path.exists(web_tmpl_file):
            os.remove(web_tmpl_file)

        if enable_all or settings.get('REMOTE_LOG_CONFIGAUDIT', 'off').lower() == 'on':
            f = open(audit_tmpl_file, 'w')
            f.write(generate_audit_config_tmpl(server, port, enable_tls, protocol))
            f.close()
        elif os.path.exists(audit_tmpl_file):
            os.remove(audit_tmpl_file)
    else:
        for tmpl in [fw_tmpl_file, ips_tmpl_file, web_tmpl_file, audit_tmpl_file]:
            if os.path.exists(tmpl):
                os.remove(tmpl)

def main():
    usage = "usage: %prog <options>"
    parser = OptionParser(usage)    
    parser.add_option("-d", "--debug", dest="debug", action="store_true",
                      help="Be more verbose", default=False)
    parser.add_option("-f", "--force", dest="force", action="store_true",
                      help="Forces restart", default=False)
    (options, args) = parser.parse_args()
    
    process_tls_templates()

    try:
        send_cmd_to_engine("restart syslogjob", options=options)
    except Exception:
        pass

    sys.exit(0)

if __name__ == "__main__":
    main()