# Copyright 1999-2015. Parallels IP Holdings GmbH. All Rights Reserved.

import subprocess, os, sys, re, tarfile, shutil, tempfile, urllib2
from contextlib import closing

TORTIX_WAF_FILENAME = "tortix_waf.conf"
PLESK_MANAGED_DIRECTIVES = [
    "SecRuleEngine",
    "SecRequestBodyAccess",
    "SecRequestBodyLimit",
    "SecResponseBodyLimit",
    "SecAuditEngine",
    "SecAuditLogRelevantStatus",
    "SecAuditLog",
    "SecAuditLogType",
]

BIN_AUM = "/var/asl/bin/aum"
ASL_CONFIG = "/etc/asl/config"
ASL_LICENSE = "/etc/asl/license.key"
ASL_LICENSE_SAVED = ASL_LICENSE + ".saved_by_plesk"

AUM_EXIT_NO_ERROR = 0
AUM_EXIT_NO_ARGS = 1
AUM_EXIT_HANDLED = 2
AUM_EXIT_FATAL = 3


ATOMIC_MOD_SECURITY_CONF = "/etc/httpd/conf.d/00_mod_security.conf"

AUTOGENERATED_CONFIGS = "#ATTENTION!\n#\n#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,\n#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED.\n"

AUM_PLESK_INSTALLER_NAME = "aum-plesk-installer"
AUM_PLESK_INSTALLER_URL = "http://www.atomicorp.com/installers/" + AUM_PLESK_INSTALLER_NAME
AUM_PLESK_INSTALLER_SIGNATURE_URL = AUM_PLESK_INSTALLER_URL + ".asc"

GPGHOMEDIR = "/var/lib/plesk/modsec/.gnupg"
ATOMICORP_GPG_PUBLIC_KEY_PATH = "/var/lib/plesk/modsec/atomicorp.gpg.key"

def get_apache_version():
    apache_ctl_path = ["/usr/sbin/apache2ctl", "/usr/sbin/apachectl"]
    for apache_ctl in apache_ctl_path:
        if os.path.exists(apache_ctl):
            popen = subprocess.Popen([apache_ctl, "-v"], stdout=subprocess.PIPE)
            (out, err) = popen.communicate()
            result = re.search('Server version:\s+\w+/(\d+\.\d+\.\d+)', out)
            if result is None:
                raise Exception("Unable to parse apache version: %s" % out)
            return [int(x) for x in result.group(1).split('.')]
    raise Exception("Unable to find apachectl unility")

def apache_version_ge_2_4():
    version = get_apache_version()
    return version[0] > 2 or (version[0] == 2 and version[1] >= 4)

def create_include_conf(conf_path):
    inc = "Include"
    if apache_version_ge_2_4():
        inc = "IncludeOptional"
    with open(conf_path, 'w') as conf:
        conf.write(inc + " @ruleset_base_dir@/modsec/" + TORTIX_WAF_FILENAME + "\n")
        conf.write(inc + " @ruleset_base_dir@/modsec/00*exclude.conf\n")
        conf.write(inc + " @ruleset_base_dir@/modsec/*asl*.conf\n")
        conf.write(inc + " @ruleset_base_dir@/modsec/99*exclude.conf\n")

def comment_plesk_managed_directives(tortix_waf):
    args = ["sed", "-i"]
    for i in PLESK_MANAGED_DIRECTIVES:
        args.append("-e")
        args.append("s|^%s\\b|# \\0|g" % i)
    args.append(tortix_waf)
    subprocess.check_call(args)

def verify_gpg_signature(path, signature_url, public_key):
    signature_path = path + ".asc"
    with open(signature_path, "w") as fout:
        with closing(urllib2.urlopen(signature_url)) as fin:
            for line in fin:
                fout.write(line)
    subprocess.check_call(["gpg", "--homedir", GPGHOMEDIR, "--import", public_key])
    subprocess.check_call(["gpg", "--homedir", GPGHOMEDIR, "--verify", signature_path])

def run_aum_plesk_installer():
    installer_dir = None
    try:
        installer_dir = tempfile.mkdtemp()
        installer_path = os.path.join(installer_dir, AUM_PLESK_INSTALLER_NAME)
        with open(installer_path, "w") as fout:
            with closing(urllib2.urlopen(AUM_PLESK_INSTALLER_URL)) as fin:
                for line in fin:
                    fout.write(line)
        verify_gpg_signature(installer_path, AUM_PLESK_INSTALLER_SIGNATURE_URL, ATOMICORP_GPG_PUBLIC_KEY_PATH)
        subprocess.check_call("/bin/bash < %s" % installer_path, shell=True)
    finally:
        if installer_dir:
            shutil.rmtree(installer_dir, ignore_errors=True)

def prepare_ruleset_layout(base_dir):
    modsec_d = os.path.join(base_dir, "modsec")
    if not os.path.isdir(modsec_d):
        os.makedirs(modsec_d)
    open(os.path.join(base_dir, "plesk_init.conf"), "w").close() # file for aum not to create 00_modsecurity.conf
    create_include_conf(os.path.join(base_dir, "plesk_init.conf.tpl"))

def fix_asl_config(configs_dir, username=None, password=None):
    cmd = ["sed", "-i",
        "-e", "s#^MODSEC_RULES_PATH\s*=.*#MODSEC_RULES_PATH=\"%s\"#g" % configs_dir,
        "-e", "s#^RESTART_APACHE\s*=.*#RESTART_APACHE=\"no\"#g",
        "-e", "s#^AUTOMATIC_UPDATES\s*=.*#AUTOMATIC_UPDATES=\"no\"#g"]
    if username and password:
        # atomic subscription ruleset
        cmd.append("-e")
        cmd.append("s#^USERNAME\s*=.*#USERNAME=\"%s\"#g" % username)
        cmd.append("-e")
        cmd.append("s#^PASSWORD\s*=.*#PASSWORD=\"%s\"#g" % password)
        cmd.append("-e")
        cmd.append("s#^FEED_SOURCE\s*=.*#FEED_SOURCE=\"plesk\"#g")
        cmd.append("-e")
        cmd.append("s#^UPDATE_PATH\s*=.*#UPDATE_PATH=\"/channels/rules/subscription\"#g")
# prevent aum from configuring ASL:
        cmd.append("-e")
        cmd.append("s#^CONFIGURED\s*=.*#CONFIGURED=\"yes\"#g")
    cmd.append(ASL_CONFIG)
    subprocess.check_call(cmd)

def deactivate_00_mod_security_conf():
    if os.path.isfile(ATOMIC_MOD_SECURITY_CONF):
        with open(ATOMIC_MOD_SECURITY_CONF, "w") as f:
            f.write(AUTOGENERATED_CONFIGS)
    # W/A for case if our mod_security package was replaced by atomic one:
    plesk_conf_path = "/etc/httpd/conf.d/security2.conf"
    plesk_conf_path_rpmsave = plesk_conf_path + ".rpmsave"
    if os.path.isfile(plesk_conf_path_rpmsave):
        os.rename(plesk_conf_path_rpmsave, plesk_conf_path)

def run_aum():
    p = subprocess.Popen([BIN_AUM, "-uf"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (out, err) = p.communicate()
    if p.returncode == AUM_EXIT_NO_ERROR:
        return
    # handle errors
    if p.returncode == AUM_EXIT_HANDLED:
        sys.stderr.write("Non-fatal error during aum execution.\nstdout: %s\nstderr: %s\nContinue execution." % (out, err))
    else:
        raise Exception("aum failed with exitcode %d.\nstdout: %s\nstderr: %s" % (p.returncode, out, err))

# vim: ts=4 sts=4 sw=4 et :
