#!/bin/bash
PATH=/bin:/usr/bin:/sbin:/usr/sbin
# BIT, 2023, support@bit.nl


CHAINS=(
    'filter'   'INPUT'
    'filter'   'FORWARD'
    'filter'   'OUTPUT'
    'nat'      'PREROUTING'
    'nat'      'INPUT'
    'nat'      'OUTPUT'
    'nat'      'POSTROUTING'
    'mangle'   'PREROUTING'
    'mangle'   'INPUT'
    'mangle'   'FORWARD'
    'mangle'   'OUTPUT'
    'mangle'   'POSTROUTING'
    'raw'      'PREROUTING'
    'raw'      'OUTPUT'
    'security' 'INPUT'
    'security' 'FORWARD'
    'security' 'OUTPUT'
)


bfCreateChains()
{
    maxidx=$((${#CHAINS[@]} - 2))
    for idx in $(seq 0 2 $maxidx); do
        nextidx=$((idx + 1))
        table=${CHAINS[$idx]}
        chain=${CHAINS[$nextidx]}

        if ! iptables -t ${table} -nL bf${chain} >/dev/null 2>&1; then
            iptables -t ${table} -N bf${chain}
        else
            echo "[!] iptables bit-firewall chain bf${chain} already exists, continuing, but did you mean 'restart'?"
        fi
        if ! ip6tables -t ${table} -nL bf${chain} >/dev/null 2>&1; then
            ip6tables -t ${table} -N bf${chain}
        else
            echo "[!] ip6tables bit-firewall chain bf${chain} already exists, continuing, but did you mean 'restart'?"
        fi
    done
}


bfUnload()
{
    maxidx=$((${#CHAINS[@]} - 2))
    for idx in $(seq 0 2 $maxidx); do
        nextidx=$((idx + 1))
        table=${CHAINS[$idx]}
        chain=${CHAINS[$nextidx]}

        if iptables -t ${table} -nL ${chain} 2>&1 | grep -q "^bf${chain}"; then
            iptables -t ${table} -P ${chain} ACCEPT
            iptables -t ${table} -D ${chain} -j bf${chain}
        fi
        if ip6tables -t ${table} -nL ${chain} 2>&1 | grep -q "^bf${chain}"; then
            ip6tables -t ${table} -P ${chain} ACCEPT
            ip6tables -t ${table} -D ${chain} -j bf${chain}
        fi

        if iptables -t ${table} -nL bf${chain} >/dev/null 2>&1; then
            iptables -t ${table} -F bf${chain}
            iptables -t ${table} -X bf${chain}
        fi
        if ip6tables -t ${table} -nL bf${chain} >/dev/null 2>&1; then
            ip6tables -t ${table} -F bf${chain}
            ip6tables -t ${table} -X bf${chain}
        fi
    done
}


bfEnforceChains()
{
    maxidx=$((${#CHAINS[@]} - 2))
    for idx in $(seq 0 2 $maxidx); do
        nextidx=$((idx + 1))
        table=${CHAINS[$idx]}
        chain=${CHAINS[$nextidx]}

        if ! iptables -t ${table} --line-numbers -n -L ${chain} | grep -Pq "^1\s+bf${chain}"; then
            iptables -t ${table} -C ${chain} -j bf${chain} >/dev/null 2>&1 && iptables -t ${table} -D ${chain} -j bf${chain}
            iptables -t ${table} -I ${chain} 1 -j bf${chain}
        fi
        if ! ip6tables -t ${table} --line-numbers -n -L ${chain} | grep -Pq "^1\s+bf${chain}"; then
            ip6tables -t ${table} -C ${chain} -j bf${chain} >/dev/null 2>&1 && ip6tables -t ${table} -D ${chain} -j bf${chain}
            ip6tables -t ${table} -I ${chain} 1 -j bf${chain}
        fi
    done
}


bfLoadRules()
{
    case "${DEFAULTRULES}" in [Yy][Ee][Ss])
        iptables -t filter -A bfINPUT -i lo -j ACCEPT
        iptables -t filter -A bfINPUT -p icmp -j ACCEPT
        ip6tables -t filter -A bfINPUT -i lo -j ACCEPT
        ip6tables -t filter -A bfINPUT -p icmpv6 -j ACCEPT

        ### Drop packets with invalid state
        iptables -t filter -A bfINPUT -p tcp -m state --state INVALID -j DROP
        iptables -t filter -A bfINPUT -p udp -m state --state INVALID -j DROP
        ip6tables -t filter -A bfINPUT -p tcp -m state --state INVALID -j DROP
        ip6tables -t filter -A bfINPUT -p udp -m state --state INVALID -j DROP

        ### Allow related & established stuff
        iptables -t filter -A bfINPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
        ip6tables -t filter -A bfINPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

        # RFC1918 is allowed SSH access at least:
        iptables -t filter -A bfINPUT -s 192.168.0.0/16 -p tcp --dport 22 -j ACCEPT
        iptables -t filter -A bfINPUT -s 172.16.0.0/12 -p tcp --dport 22 -j ACCEPT
        iptables -t filter -A bfINPUT -s 10.0.0.0/8 -p tcp --dport 22 -j ACCEPT
        ip6tables -t filter -A bfINPUT -s fded:e128:5901::/48 -p tcp --dport 22 -j ACCEPT

        # Trustlist
        for PREFIX in ${V4TRUSTED}; do
            iptables -t filter -A bfINPUT -s $PREFIX -j ACCEPT
        done
        # Trustlist
        for PREFIX in ${V6TRUSTED}; do
            ip6tables -t filter -A bfINPUT -s $PREFIX -j ACCEPT
        done

        # Blocklist
        for PREFIX in ${V4BLOCKED}; do
            iptables -t filter -A bfINPUT -s $PREFIX -j DROP
            iptables -t filter -A bfFORWARD -s $PREFIX -j DROP
            iptables -t filter -A bfOUTPUT -d $PREFIX -j DROP
        done
        # Blocklist
        for PREFIX in ${V6BLOCKED}; do
            ip6tables -t filter -A bfINPUT -s $PREFIX -j DROP
            ip6tables -t filter -A bfOUTPUT -d $PREFIX -j DROP
        done
    esac

    IPTABLES="iptables"; IP6TABLES="iptables"
    [ -r /etc/bit-firewall/rules ] && . /etc/bit-firewall/rules
    IPTABLES="ip6tables"; IP6TABLES="ip6tables"
    [ -r /etc/bit-firewall/rules6 ] && . /etc/bit-firewall/rules6
}


bfUpdateRules()
{
    for rulefile in rules rules6; do
        if ! grep -qP -- "-[AIDFZ]\s+((IN|OUT)PUT|FORWARD|(POST|PRE)ROUTING)" /etc/bit-firewall/${rulefile}; then
            continue
        fi

        echo "[!] Converting old format of /etc/bit-firewall/${rulefile} file."
        for mode in A I D F Z; do
            for chain in INPUT FORWARD OUTPUT POSTROUTING PREROUTING; do
                sed -E -i -e "s/\s-${mode}\s+${chain}\s/ -${mode} bf${chain} /g" /etc/bit-firewall/${rulefile}
            done
        done
    done
}


bfMonitorChains()
{
    maxidx=$((${#CHAINS[@]} - 2))
    for idx in $(seq 0 2 $maxidx); do
        nextidx=$((idx + 1))
        table=${CHAINS[$idx]}
        chain=${CHAINS[$nextidx]}

        if ! iptables -t ${table} --line-numbers -n -L ${chain} | grep -Pq "^1\s+bf${chain}"; then
            echo "CRITICAL - iptables bf${chain} chain jump in the ${table} table is not the first rule in ${chain} chain"
            exit 2
        fi
        if ! ip6tables -t ${table} --line-numbers -n -L ${chain} | grep -Pq "^1\s+bf${chain}"; then
            echo "CRITICAL - ip6tables bf${chain} chain jump in the ${table} table is not the first rule in ${chain} chain"
            exit 2
        fi

        if [ "$table" != "filter" ]; then
            nrules4=$(grep -Pc -- "-t\s+${table}" /etc/bit-firewall/rules | grep -Pc -- "-[AI]\s+bf${chain}")
            nrules6=$(grep -Pc -- "-t\s+${table}" /etc/bit-firewall/rules6 | grep -Pc -- "-[AI]\s+bf${chain}")
        else
            nrules4=$(grep -Pv -- "-t\s+\w+\s+" /etc/bit-firewall/rules | grep -Pc -- "-[AI]\s+bf${chain}")
            nrules6=$(grep -Pv -- "-t\s+\w+\s+" /etc/bit-firewall/rules6 | grep -Pc -- "-[AI]\s+bf${chain}")
        fi
        nloaded4=$(iptables -t ${table} -S bf${chain} | grep -Pc -- "-[AI]\s+bf${chain}")
        nloaded6=$(ip6tables -t ${table} -S bf${chain} | grep -Pc -- "-[AI]\s+bf${chain}")

        if [ $nrules4 -gt $nloaded4 ]; then
            echo "WARNING - iptables chain bf${chain} in table ${table} loaded rule count is less than rules on disk"
            exit 1
        fi

        if [ $nrules6 -gt $nloaded6 ]; then
            echo "WARNING - ip6tables chain bf${chain} in table ${table} loaded rule count is less than rules6 on disk"
            exit 1
        fi
    done

    case $CHAINWARNING in
        [Yy][Ee][Ss])
            for rulefile in rules rules6; do
                if ! grep -qP -- "-[AIDFZ]\s+((IN|OUT)PUT|FORWARD|(POST|PRE)ROUTING)" /etc/bit-firewall/${rulefile}; then
                    continue
                fi
                echo "WARNING - found rules that use ${chain} instead of the bit-firewall bf${chain} chain in /etc/bit-firewall/${rulefile}"
                exit 1
            done
        ;;
    esac

    if grep -qP "fail2ban.*restart" /etc/bit-firewall/rules; then
        echo "WARNING - fail2ban restart command found in /etc/bit-firewall/rules - this should no longer be necessary!"
        exit 1
    fi

    echo "OK - bit-firewall rules / chains look okay."
    exit 0
}


#
##
###
##
#

if [ `id -u` -ne 0 ]; then
    echo "You are not root. Be root."
    exit 1
fi

[ -r /etc/default/bit-firewall ] && . /etc/default/bit-firewall
[ -r /etc/default/bit-firewall-awx ] && . /etc/default/bit-firewall-awx

case "$1" in
    start)
        echo "Starting BIT firewall ..."
        bfCreateChains
        bfEnforceChains
        bfLoadRules
    ;;

    stop)
        echo "Stopping BIT firewall ..."
        bfUnload
    ;;

    restart)
        echo "Reloading BIT firewall ..."
        bfUnload
        bfCreateChains
        bfEnforceChains
        bfLoadRules
    ;;

    enforce)
        bfEnforceChains
    ;;

    status|monitor)
        bfMonitorChains
    ;;

    update-rules)
        bfUpdateRules
    ;;

    *)
        echo "Usage: $0 {start|stop|restart|status|monitor|enforce|update-rules}"
        exit 1
    ;;
esac
exit 0
