#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

import sys
import os
import re
import subprocess


DB_FILE = "/var/lib/fail2ban/fail2ban.sqlite3"
SETTINGS_FILE = "/var/efw/fail2ban/settings"
WHITELIST_FILE = "/var/efw/fail2ban/whitelist"
ENABLED = ""
BANTIME = ""
MAXRETRYWEBGUI = ""
MAXRETRYSSH = ""
MAXRETRYOPENVPN = ""
PORTOPENVPN = ""
PORTSSH = ""


if os.path.isfile(SETTINGS_FILE):
    with open(SETTINGS_FILE, "r") as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith("#") and "=" in line:
                key, val = line.split("=", 1)
                val = val.strip("\"'")
                if key == "ENABLED":
                    ENABLED = val
                elif key == "BANTIME":
                    BANTIME = val
                elif key == "MAXRETRYWEBGUI":
                    MAXRETRYWEBGUI = val
                elif key == "MAXRETRYSSH":
                    MAXRETRYSSH = val
                elif key == "MAXRETRYOPENVPN":
                    MAXRETRYOPENVPN = val
                elif key == "PORTOPENVPN":
                    PORTOPENVPN = val
                elif key == "PORTSSH":
                    PORTSSH = val


TURNOFF = ENABLED

WHITELIST = ""
if os.path.isfile(WHITELIST_FILE):
    ips = []
    with open(WHITELIST_FILE, "r") as f:
        for line in f:
            line = line.strip()
            if line:
                parts = line.split("|")
                ips.append(parts[0])
    WHITELIST = " ".join(ips)


def is_cgi():
    return "GATEWAY_INTERFACE" in os.environ or "HTTP_HOST" in os.environ


def print_help():
    print "Usage: openfw-fail2ban [OPTIONS].\n"
    print "Options:\n"
    print "   --restart       - Apply Settings/Whitelist configurations and restart Fail2ban"
    print "   --status        - Check if the Fail2Ban service is running"
    print "   --list-bips     - List active blocked IPs for the Unban table in the WebGUI"
    print "   --unban [IP]    - Unban and manually remove an IP from Fail2Ban in real time"
    sys.stdout.flush()


def write_file(path, content):
    with open(path, "w") as f:
        f.write(content)


def restart():
    if TURNOFF in ["on", "1"]:

        if not os.path.isfile("/var/log/openvpn/openvpn.log"):
            subprocess.call(["mkdir", "-p", "/var/log/openvpn"])
            subprocess.call(["touch", "/var/log/openvpn/openvpn.log"])
            subprocess.call(["chmod", "640", "/var/log/openvpn/openvpn.log"])
            subprocess.call(["chown", "nobody:nogroup", "/var/log/openvpn/openvpn.log"])

        # ----------------------------------------------------------------------------------------------------------------

        jail_local_content = """[DEFAULT]
ignoreip = {WHITELIST}
bantime = {BANTIME}
banaction = iptables-multiport

[sshd]
enabled = true
port = {PORTSSH}
protocol = tcp
logpath = /var/log/messages
maxretry = {MAXRETRYSSH}
backend = polling
chain = FAILTOBAN

[sshd-ddos]
enabled = true
port = {PORTSSH}
protocol = tcp
logpath = /var/log/messages
maxretry = {MAXRETRYSSH}
chain = FAILTOBAN

[apache-auth]
enabled  = true
port     = 80,443,10443
protocol = tcp
filter   = apache-auth
logpath  = /var/log/httpd/error_log
maxretry = {MAXRETRYWEBGUI}
chain = FAILTOBAN

[apache-noscript]
enabled  = true
port     = 80,443,10443
protocol = tcp
filter   = apache-noscript
logpath  = /var/log/httpd/error_log
maxretry = {MAXRETRYWEBGUI}
chain = FAILTOBAN

[apache-botsearch]
enabled  = true
port     = 80,443,10443
protocol = tcp
logpath  = /var/log/httpd/error_log
maxretry = {MAXRETRYWEBGUI}
chain = FAILTOBAN

[openvpn-tcp]
enabled  = true
port     = {PORTOPENVPN}
filter   = openvpn
protocol = tcp
logpath  = /var/log/openvpn/openvpn.log
maxretry = {MAXRETRYOPENVPN}
chain = FAILTOBAN

[openvpn-udp]
enabled  = true
port     = {PORTOPENVPN}
filter   = openvpn
protocol = udp
logpath  = /var/log/openvpn/openvpn.log
maxretry = {MAXRETRYOPENVPN}
chain = FAILTOBAN""".format(
            WHITELIST=WHITELIST,
            BANTIME=BANTIME,
            PORTSSH=PORTSSH,
            MAXRETRYSSH=MAXRETRYSSH,
            MAXRETRYWEBGUI=MAXRETRYWEBGUI,
            PORTOPENVPN=PORTOPENVPN,
            MAXRETRYOPENVPN=MAXRETRYOPENVPN
        )
        write_file("/etc/fail2ban/jail.local", jail_local_content)

        iptables_common_content = """[Init]
chain = INPUT
name = default
port = ssh
protocol = tcp
blocktype = REJECT --reject-with icmp-port-unreachable
returntype = ACCEPT
lockingopt =
iptables = iptables <lockingopt>

[Init?family=inet6]
blocktype = REJECT --reject-with icmp6-port-unreachable
iptables = ip6tables <lockingopt>"""
        write_file("/etc/fail2ban/action.d/iptables-common.conf", iptables_common_content)

        fail2ban_conf_content = """[DEFAULT]
loglevel = INFO
logtarget = SYSLOG[facility=local0]
syslogsocket = auto
socket = /var/run/fail2ban/fail2ban.sock
pidfile = /var/run/fail2ban/fail2ban.pid
dbfile = {DB_FILE}
dbpurgeage = 1d
dbmaxmatches = 10

[Definition]

[Thread]""".format(DB_FILE=DB_FILE)
        write_file("/etc/fail2ban/fail2ban.conf", fail2ban_conf_content)

        iptables_multiport_content = """[INCLUDES]
before = iptables-common.conf

[Definition]
actionstart = <iptables> -N <chain> 2>/dev/null || true
              <iptables> -C <chain> -j <returntype> 2>/dev/null || <iptables> -A <chain> -j <returntype>

actionstop = <iptables> -F <chain> 2>/dev/null || true

actioncheck = true

actionban = <iptables> -I <chain> 1 -s <ip> -j <blocktype>
            <iptables> -I <chain> 1 -s <ip> -j NFLOG --nflog-prefix 'FAIL2BAN:REJECT'

actionunban = <iptables> -D <chain> -s <ip> -j <blocktype>
              <iptables> -D <chain> -s <ip> -j NFLOG --nflog-prefix 'FAIL2BAN:REJECT'

[Init]"""
        write_file("/etc/fail2ban/action.d/iptables-multiport.conf", iptables_multiport_content)

        if is_cgi():
            with open(os.devnull, "w") as devnull:
                subprocess.call(["/etc/init.d/fail2ban", "restart"], stdout=devnull, stderr=devnull)
        else:
            subprocess.call(["/etc/init.d/fail2ban", "restart"])

        subprocess.call(["chmod", "755", "/var/run/fail2ban/fail2ban.pid"])
        
        try:
            os.remove("/tmp/nohup-fail2ban-restart.err")
        except OSError:
            pass
            
        try:
            os.remove("/tmp/fail2ban-restart.out")
        except OSError:
            pass

        if os.path.isfile(WHITELIST_FILE):
            ip_pattern = re.compile(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$")
            with open(WHITELIST_FILE, "r") as f:
                for line in f:
                    ip_white = line.strip()
                    if ip_pattern.match(ip_white):
                        with open(os.devnull, "w") as devnull:
                            subprocess.call(["/usr/bin/fail2ban-client", "unban", ip_white], stdout=devnull, stderr=devnull)

        monit_content = """check process fail2ban with pidfile /var/run/fail2ban/fail2ban.pid
    start program = "/etc/init.d/fail2ban start"
    stop program  = "/etc/init.d/fail2ban stop\""""
        write_file("/etc/monit.d/fail2ban", monit_content)

        if is_cgi():
            with open(os.devnull, "w") as devnull:
                subprocess.call(["/etc/init.d/monit", "reload"], stdout=devnull, stderr=devnull)
        else:
            subprocess.call(["/etc/init.d/monit", "reload"])

    else:
        if is_cgi():
            with open(os.devnull, "w") as devnull:
                subprocess.call(["/etc/init.d/fail2ban", "stop"], stdout=devnull, stderr=devnull)
        else:
            subprocess.call(["/etc/init.d/fail2ban", "stop"])

        try:
            os.remove("/etc/monit.d/fail2ban")
        except OSError:
            pass

        if is_cgi():
            with open(os.devnull, "w") as devnull:
                subprocess.call(["/etc/init.d/monit", "reload"], stdout=devnull, stderr=devnull)
        else:
            subprocess.call(["/etc/init.d/monit", "reload"])

    sys.stdout.flush()


def list_bips():
    if os.path.isfile(DB_FILE):
        subprocess.call([
            "/usr/bin/sqlite3",
            "-separator", "|",
            DB_FILE,
            "SELECT jail, ip, timeofban, bantime FROM bips ORDER BY timeofban DESC;"
        ])
        sys.stdout.flush()


def unban_ip(target_ip):
    ip_pattern = re.compile(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$")
    if ip_pattern.match(target_ip):
        if is_cgi():
            with open(os.devnull, "w") as devnull:
                ret = subprocess.call(["/usr/bin/fail2ban-client", "unban", target_ip], stdout=devnull, stderr=devnull)
        else:
            ret = subprocess.call(["/usr/bin/fail2ban-client", "unban", target_ip])
        sys.stdout.flush()
        sys.exit(ret)
    else:
        print "Error: Invalid IP address format."
        sys.stdout.flush()
        sys.exit(1)


def status():
    subprocess.call(["/etc/init.d/fail2ban", "status"])
    sys.stdout.flush()


if __name__ == "__main__":
    arg = sys.argv[1] if len(sys.argv) > 1 else ""

    if arg in ["--help", "-h"]:
        print_help()
        sys.exit(0)
    elif arg == "--restart":
        restart()
        sys.exit(0)
    elif arg == "--status":
        status()
        sys.exit(0)
    elif arg == "--list-bips":
        list_bips()
        sys.exit(0)
    elif arg == "--unban":
        if len(sys.argv) < 3 or not sys.argv[2]:
            print "Error: Please specify an IP address to unban."
            sys.stdout.flush()
            sys.exit(1)
        unban_ip(sys.argv[2])
    else:
        print_help()
        sys.exit(3)