#!/bin/bash

# Copyright: 2023 Virtuozzo International GmbH

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

BACKUP_IPV4='/etc/jelastic/iptables4.backup'
BACKUP_IPV6='/etc/jelastic/iptables6.backup'

CUSTOM_RULES_FILE='/etc/sysconfig/iptables-custom'
CUSTOM_6RULES_FILE='/etc/sysconfig/ip6tables-custom'
JELASTIC_RULES_DIR='/etc/sysconfig'
JELASTIC_4RULES_FILE=${JELASTIC_RULES_DIR}'/iptables4-jelastic'
JELASTIC_4NATRULES_FILE=${JELASTIC_RULES_DIR}'/iptables4-jelastic-nat'
JELASTIC_6RULES_FILE=${JELASTIC_RULES_DIR}'/iptables6-jelastic'
JELASTIC_6NATRULES_FILE=${JELASTIC_RULES_DIR}'/iptables6-jelastic-nat'

# $cmd $proto $ptype $dport $srchost $dsthost $chain $tcptype $raction
#  1    2       3     4        5         6      7       8         9
function addRule() {
        chain="${7^^}"
        proto="${2,,}"
        cmd="${1,^^}"
        if [ "x$cmd" != "x" ] ; then
            cmd="-$cmd"
        else
            cmd="-A"
        fi

        rulecmd="$cmd $chain"
        if [ "x$proto" != "x" ] ; then
            rulecmd="$rulecmd -p $proto"
        fi

        srchost=${5/ALL/0.0.0.0\/0}
        if [ "x$srchost" != "x" ] ; then
            if grep -q '!' <<< $srchost ; then
                srchost=${srchost//'!'/}
                rulecmd="$rulecmd ! -s $srchost"
            else
                rulecmd="$rulecmd -s $srchost"
            fi
        fi

        dsthost=${6/ALL/0.0.0.0\/0}
        if [ "x$dsthost" != "x" ] ; then
            if grep -q '!' <<< $dsthost ; then
                dsthost=${dsthost//'!'/}
                rulecmd="$rulecmd ! -d $dsthost"
            else
                rulecmd="$rulecmd -d $dsthost"
            fi
        fi

        ptype="$3"
        dport="$4"
        if [ "x$ptype" == "xrange" ] ; then
            if [ "x$dport" != "x" ] ; then
                rulecmd="$rulecmd -m multiport --dports $dport"
            fi
        else
            if [ "x$dport" != "x" ] ; then
                rulecmd="$rulecmd --dport $dport"
            fi
        fi
        raction="$9"

        rulecmd="$rulecmd -j $raction"

        if [ "x$proto" == "xall" ] ; then
            tcprule=$($SED -e 's/all/tcp/g' <<< $rulecmd )
            udprule=$($SED -e 's/all/udp/g' <<< $rulecmd )
            rulecmd="${tcprule}\n${udprule}"
        fi

        tcptype="$8"
        if [ "x$tcptype" == "xIPV4" ] ; then
            echo -e $rulecmd >> "$(vzctPath $VZ_CTID_ROOT $JELASTIC_4RULES_FILE)"
        else
            echo -e $rulecmd >> "$(vzctPath $VZ_CTID_ROOT $JELASTIC_6RULES_FILE)"
        fi
}

#just stub because iptables doesn't need additional init
function _doInitTables() {
    return 0
}

function setDefaultPolicy() {
    local policy=${1:-"DROP"}
    $IPTABLES -P INPUT ${policy}
    $IPTABLES -P FORWARD ${policy}
    $IPTABLES -P OUTPUT ACCEPT

    $IP6TABLES -P INPUT ${policy}
    $IP6TABLES -P FORWARD ${policy}
    $IP6TABLES -P OUTPUT ACCEPT
}

function netfilterStop() {
    [[ "${FIREWALL_ENABLED}" -eq "0" ]] || [ -z "${FIREWALL_ENABLED+xxx}" ] || [ -z "${FIREWALL_ENABLED}" -a "${FIREWALL_ENABLED+xxx}" = "xxx" ] && return 0;
    $IPTABLES -F &>>$JEM_CALLS_LOG
    $IPTABLES -F -t nat &>>$JEM_CALLS_LOG
    $IPTABLES -X &>>$JEM_CALLS_LOG
    $IPTABLES -X -t nat &>>$JEM_CALLS_LOG

    $IP6TABLES -F &>>$JEM_CALLS_LOG
    $IP6TABLES -F -t nat &>>$JEM_CALLS_LOG
    $IP6TABLES -X &>>$JEM_CALLS_LOG
    $IP6TABLES -X -t nat &>>$JEM_CALLS_LOG

    setDefaultPolicy "ACCEPT"
}


function netfilterRedirect() {
    __PORT=$1
    __SPORT=$2
    __PROTO=$3

    local rules_file=$(vzctPath $VZ_CTID_ROOT $CUSTOM_RULES_FILE)

    local portExists=$(vzexecRunStdOut "iptables -w -L -n -t nat" | sed -nre "/REDIRECT/{/${__PROTO}/{/dpt:${__SPORT}/{/ports ${__PORT}/p}}}")
    if [ ! -z "$portExists" ] ; then
        writeJSONResponseErr "result=>4099" "message=>This pair SPORT:DPORT already exists"
        return 1
    fi

    if vzexecRun "iptables -t nat -w -I PREROUTING -p $__PROTO -m $__PROTO --dport $__SPORT -j REDIRECT --to-port $__PORT" ; then

        rule="$(vzexecRunStdOut "iptables -w -t nat -S" | sed -nre "/$__PROTO/{/--dport $__SPORT/{/--to-ports $__PORT/p;}}")"

        [ ! -z "$rule" ] && rule=$(sed -nre "/$rule/p" <<< "$rule")

        if [ ! -z "$rule" ] ; then
            if [ ! -e "$rules_file" ] ; then
                echo -e "*nat\n$rule\nCOMMIT\n" > "$rules_file"
            else
                #insert the rule just after chain definition
                sed -i -re "/:PREROUTING/a$rule" $rules_file
            fi

        fi

        writeJSONResponseOut "result=>0" "message=>Port has been redirected" "sport=>$__SPORT" "dport=>$__PORT"
        return 0
    fi
}

function netfilterForward() {
    __IP=$1
    __PORT=$2
    __PROTO=$3
    __SPORT=$4


    if ! isValidIP $__IP ; then
        writeJSONResponseErr "result=>4083" "message=>Invalid IP specified"
        exit 1
    fi

    local port=$(iptables -w -L PREROUTING -t nat -vnx | sed -nre "/($__IP:[0-9]{1,5})/{/$__PROTO/{/:$__PORT\s*$/{s/.*dpt:([0-9]{4,5}).*/\1/;p}}}")

    if [[ ! -z "$port" ]] ; then
        writeJSONResponseErr "result=>4092" "message=>This pair IP:PORT already bind to port $port" "port=>$port"
        exit 1
    fi

    local freeport=$(getFreePort $__SPORT)
    if [[ -z "$freeport" ]] ; then
        writeJSONResponseErr "result=>4084" "message=>Unable to bind IP. There is no free ports"
        exit 1
    fi

    if iptables -w -t nat -A PREROUTING -p $__PROTO --dport $freeport -j DNAT --to-destination ${__IP}:${__PORT} ; then
        service iptables save | $GRAB_OUTPUT;
        writeJSONResponseOut "result=>0" "message=>Port has been added" "port=>$freeport"
        return 0
    fi
}

function checkRule() {
    chainName=$1
    rule=$2
    $IPTABLES -C $chainName $rule 1>/dev/null 2>&1
}

function processRule4File() {
    [ -f "$1" ] || return 0
    echo "CUSTOM RULES FILE $1 CONTENT"  >> $JEM_CALLS_LOG
    cat "$1" >> $JEM_CALLS_LOG
    $IPTABLES_RESTORE $2 -n < "$1" 2>>$ACTIONS_LOG
    return $?
}

function processRule6File() {
    [ -f "$1" ] || return 0
    echo "CUSTOM RULES FILE $1 CONTENT "  >> $JEM_CALLS_LOG
    cat "$1" >> $JEM_CALLS_LOG
    $IP6TABLES_RESTORE $2 -n < "$1" 2>>$ACTIONS_LOG
    return $?
}

function getUsedPorts() {
    echo $(iptables -w -L PREROUTING -t nat -n | sed -nre '/REDIRECT/d; s/.*dpt:([0-9]{4,5}).*/\1/;/[0-9]+/p' | uniq -u)
}

function getPortByIP() {
    : ${1? "Missing IP address" }
    local _IP=$1
    local _port=$2

    echo $(iptables -w -L PREROUTING -t nat -n | sed -nre "/:${_IP}:${_port}$/{/$__PROTO/{s/.*dpt:([0-9]+).*/\1/;p}}")
}

function getDestinationPort() {
    : ${1?"Missing port param"}
    local _PORT=$1
    echo $(iptables -w -L PREROUTING -t nat -n | $SED -nre "/:${_PORT}\s/{s/.*:([0-9]+)\s*$/\1/;p}")
}

function netfilterSNAT() {
    __IP=$1
    __NETWORK=$2
    __PROTO=$3

    if ! isValidIP $__IP ; then
                writeJSONResponseErr "result=>4083" "message=>Invalid IP specified" ; exit 1;
    fi

    # Check if binding already exists {{{
    local port=$(iptables -w -L POSTROUTING -t nat -n | grep -E "$__PROTO.*$__NETWORK.*to:$__IP" )

    if [[ ! -z $port ]] ; then
                writeJSONResponseErr "result=>4092" "message=>This pair IP:NETWORK ($__IP:$__PROTO) already bind"; exit 1;
    fi
    # }}}

    if $iptables -w -t nat -A POSTROUTING -p $__PROTO --dst $__NETWORK -j SNAT --to-source $__IP ; then
        service iptables save &>> $JEM_CALLS_LOG
        writeJSONResponseOut "result=>0" "message=>SNAT has been added"; return 0 ;
    fi
}

function netfilterUnbind() {
    __IP=$1
    __PORT=$2
    __PROTO=$3

    local _ports=($(getPortByIP ${__IP} $__PORT))

    if [[ ! -z ${__PORT+x} ]] ; then
        #single port
        #TODO: This comparation is buggy. Need to refactor it in future
        if [[ -z ${_ports[0]} ]] ; then
            writeJSONResponseErr "result=>4091" "message=>there is no bindings for $__IP:$__PORT"; exit 1;
        fi

        if [[ $NAT_ACTION == "dnat" ]] ; then
            $EXEC "$IPTABLES -w -t nat -D PREROUTING -p $__PROTO --dport $_ports -j DNAT --to-destination $__IP:$__PORT" 2>/dev/null
            res=$?
        else
            $EXEC "$IPTABLES -w -t nat -D PREROUTING -p $__PROTO --dport $_ports -j REDIRECT --to-port $__PORT" 2>/dev/null
            res=$?
        fi

        if [[ $res -eq 0 ]] ; then
            service iptables save | $GRAB_OUTPUT;
            writeJSONResponseOut "result=>0" "message=>Port binding has been removed"; return 0 ;
        else
            writeJSONResponseErr "result=>4093" "message=>Port binding has not been removed"; return 1 ;
        fi

    else

        # multiple ports
        msg=""
        for port in "${_ports[@]}" ; do
            destPort=$(getDestinationPort $port)
            msg="$msg $destPort"
            $IPTABLES -w -t nat -D PREROUTING -p $__PROTO --dport ${port} -j DNAT --to-destination ${__IP}:${destPort}
        done


        service iptables save &>> $JEM_CALLS_LOG
        writeJSONResponseOut "result=>0" "message=>Port bindings for ${__IP} has been removed (${msg} )"; return 0 ;
    fi
}

function netfilterStart() {
    echo "`date +%D.%k:%M:%S.%N`:   $action:$subaction Starting firewall"

    #making backup of current rules
    [ ! -d "/etc/jelastic" ] && mkdir -p "/etc/jelastic" 2>>$JEM_CALLS_LOG
    $IPTABLES_SAVE > $BACKUP_IPV4 2>>$JEM_CALLS_LOG
    $IP6TABLES_SAVE > $BACKUP_IPV6 2>>$JEM_CALLS_LOG

    _rule4Files=($CUSTOM_RULES_FILE $JELASTIC_4RULES_FILE $JELASTIC_4NATRULES_FILE)
    _rule6Files=($CUSTOM_6RULES_FILE $JELASTIC_6RULES_FILE $JELASTIC_6NATRULES_FILE)

    #test rule files
    _testRes=0
    for j in $(seq 4 2 6) ; do
        eval "_ruleFiles=(\${_rule${j}Files[*]})"
        eval "_processRuleFile=processRule${j}File"
        for i in $(seq 0 $((${#_ruleFiles[*]}-1)) ) ; do
            $_processRuleFile  ${_ruleFiles[$i]} "-t"
            _res=$?
            [[ "$_res" -ne 0 ]] && echo "Test contents of ${_ruleFiles[$i]} filed" >>$JEM_CALLS_LOG
            _testRes=$((_testRes+_res))
        done
    done
    if [[ $_testRes -gt 0 ]] ; then
        echo "Current iptables rules not modified due to errors in new rule-files" >>$JEM_CALLS_LOG
        return 2
    fi

    if [ "x$OS" == "xubuntu" ] ; then
        #fix ubuntu with broken links
        [ -e '/etc/iptables/rules.v4' ] && [ -L "$CUSTOM_RULES_FILE" ] && cp -H $CUSTOM_RULES_FILE{,.bkp} 2>> $JEM_CALLS_LOG && rm -f $CUSTOM_RULES_FILE 2>> $JEM_CALLS_LOG
        [ -e '/etc/iptables/rules.v6' ] && [ -L "$CUSTOM_6RULES_FILE" ] && cp -H $CUSTOM_6RULES_FILE{,.bkp} 2>> $JEM_CALLS_LOG && rm -f $CUSTOM_6RULES_FILE 2>> $JEM_CALLS_LOG
    fi

    $IPTABLES -F;
    $IPTABLES -t nat -F;

    if [ -f /proc/net/if_inet6 ] ; then
        $IP6TABLES -F;
        $IP6TABLES -t nat -F 2>/dev/null;
    fi
    _setDefaultPolicy

    #exports
    set -f
    NFSIPS=()
    [ -e '/etc/exports' ] && NFSIPS=($($SED -re 's/.+(\s|\t)+((\*)|([0-9]+\.){3}[0-9]+(\/[0-9]{0,2})?).*/\2/g' -e '/#/d' '/etc/exports' | sort | uniq | $SED -e '/^$/d' -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g'))
    local __allnetwork=0

    # for internal services:
    intips=($(getLANIPs))
    for ip in ${intips[@]} ; do
        for nfsip in ${NFSIPS[@]} ; do
            if [[ "${#NFSPort}" -gt 0 ]] ; then
                if [ "$nfsip" == "*" ] ; then
                    $IPTABLES -A INPUT -p tcp -m multiport --dports $NFSPort -d $ip -j ACCEPT
                    $IPTABLES -A INPUT -p udp -m multiport --dports $NFSPort -d $ip -j ACCEPT
                    __allnetwork=1
                else
                    if [[ "$__allnetwork" -eq 0 ]] ; then
                        $IPTABLES -A INPUT -p tcp -m multiport --dports $NFSPort -d $ip -s $nfsip -j ACCEPT
                        $IPTABLES -A INPUT -p udp -m multiport --dports $NFSPort -d $ip -s $nfsip -j ACCEPT
                    fi
                fi
            fi
            if [[ "${#RPCPort}" -gt 0 ]] ; then
                if [ "$nfsip" == "*" ] ; then
                    $IPTABLES -A INPUT -p tcp -m multiport --dports $RPCPort -d $ip -j ACCEPT
                    $IPTABLES -A INPUT -p udp -m multiport --dports $RPCPort -d $ip -j ACCEPT
                    __allnetwork=1
                else
                    if [[ "$__allnetwork" -eq 0 ]] ; then
                        $IPTABLES -A INPUT -p tcp -m multiport --dports $RPCPort -d $ip -s $nfsip -j ACCEPT
                        $IPTABLES -A INPUT -p udp -m multiport --dports $RPCPort -d $ip -s $nfsip -j ACCEPT
                    fi
                fi
            fi
        done
    done;
    set +f

    $IPTABLES -A INPUT -i tap -j ACCEPT;

    redirPorts=($(isFunction _get$($SED -re 's/-/_/g' <<< ${COMPUTE_TYPE})RedirPorts && { _get$($SED -re 's/-/_/g' <<< ${COMPUTE_TYPE})RedirPorts; } || _defaultComputeNodeRedirPorts));

    # process input chain NAT
    for port in ${redirPorts[@]} ; do
        local ruleParams=($($SED 's/:/ /g' <<< ${port}));
        $IPTABLES -t nat -A ${ruleParams[0]} -s ${ruleParams[1]} -d ${ruleParams[2]} -m tcp -p tcp --dport ${ruleParams[3]} -j ${ruleParams[5]} --to-port ${ruleParams[4]};
    done;

    [ "x${COMPUTE_TYPE}" == "xcartridge" ] && {
        [ -f "${CARTRIDGE_HOME}/jelastic/scripts/firewall.sh" ] && source "${CARTRIDGE_HOME}/jelastic/scripts/firewall.sh" >>$JEM_CALLS_LOG 2>&1;
    }

    _loadRes=0
    for j in $(seq 4 2 6) ; do
        eval "_ruleFiles=(\${_rule${j}Files[*]})"
        eval "_processRuleFile=processRule${j}File"
        for i in $(seq 0 $((${#_ruleFiles[*]}-1)) ) ; do
            $_processRuleFile ${_ruleFiles[$i]}
            _res=$?
            [[ "$_res" -ne 0 ]] && echo "Load contents of ${_ruleFiles[$i]} filed" >>$JEM_CALLS_LOG
            _loadRes=$((_loadRes+_res))
        done
    done

    if [ -f /proc/net/if_inet6 ] ; then
        $IP6TABLES -I INPUT -p icmpv6 -j ACCEPT
        $IP6TABLES -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
        $IP6TABLES -I INPUT -i lo -j ACCEPT
    fi

    $IPTABLES -I INPUT -p icmp -j ACCEPT
    $IPTABLES -I INPUT -i lo -j ACCEPT
    $IPTABLES -I INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

    #JE-67929
    $IPTABLES -I INPUT -p icmp -m icmp --icmp-type 13 -j DROP

    #Add reject only if there was no problem while loading rules
    if [[ "$_loadRes" -gt 0 ]] ; then
        echo "Not saving rules due to errors above" >>$JEM_CALLS_LOG
        _setDefaultPolicy "ACCEPT"
        return 2
    fi

    checkRule "INPUT" "-j REJECT --reject-with icmp-host-prohibited" || $IPTABLES -A INPUT -j REJECT --reject-with icmp-host-prohibited
}

function netfilterEnable() {
    [ -d "${JELASTIC_RULES_DIR}" ] || mkdir -p "${JELASTIC_RULES_DIR}"
    [ ! -e "${CUSTOM_6RULES_FILE}" ] && touch ${CUSTOM_6RULES_FILE}
    [ -n "$DATA_OWNER" ] && chown "$DATA_OWNER" "${CUSTOM_6RULES_FILE}" &>>"$JEM_CALLS_LOG"

    # patch iptables-persistent plugins
    # VZ containers has no modprobe support
    if [ "x$OS" == "xubuntu" -o "x$OS" == "xdebian" ] ; then
        [ -d "/usr/share/netfilter-persistent/plugins.d/" ] && {
            sed -i -re '/\/sbin\/modprobe/s/^/#/g' /usr/share/netfilter-persistent/plugins.d/* >>$ACTIONS_LOG 2>&1
            systemctl enable netfilter-persistent >>$ACTIONS_LOG 2>&1
        }
    else
        chkconfig --level 345 iptables on 2>&1 | $GRAB_OUTPUT
        chkconfig --level 345 ip6tables on 2>&1 | $GRAB_OUTPUT
    fi
}

function saveCurrentRules() {
    savefile="${1:-""}"
    if [ ! -z "$savefile" ] ; then
        [ ! -d "/etc/jelastic" ] && mkdir -p "/etc/jelastic" 2>>$JEM_CALLS_LOG
        $IPTABLES_SAVE > $BACKUP_IPV4 2>>$JEM_CALLS_LOG
        $IP6TABLES_SAVE > $BACKUP_IPV6 2>>$JEM_CALLS_LOG
        return 0
    fi

    if [ "x$OS" == "xubuntu" -o "x$OS" == "xdebian" ] ; then
        [ -x /etc/init.d/netfilter-persistent ] && /etc/init.d/netfilter-persistent save &>> $JEM_CALLS_LOG
    else
        /sbin/service iptables save &>> $JEM_CALLS_LOG
        /sbin/service ip6tables save &>> $JEM_CALLS_LOG
    fi
    echo "iptables rules saved" >>$JEM_CALLS_LOG
}


# global variable RULES required see firewall.module
function firewallSet() {
    if [ ${#RULES[@]} -eq 0 ] ; then
        echo "rules are epmty"
        return 1
    fi
    [ -d "$(vzctPath $VZ_CTID_ROOT $JELASTIC_RULES_DIR)" ] || mkdir -p "${VZ_CTID_ROOT}${JELASTIC_RULES_DIR}"
    echo "*filter" > "$(vzctPath $VZ_CTID_ROOT $JELASTIC_4RULES_FILE)"
    echo "*filter" > "$(vzctPath $VZ_CTID_ROOT $JELASTIC_6RULES_FILE)"
    for ruleraw in ${RULES[@]} ; do
        rulevars=$(awk -F, '{print "proto="$1";ptype="$2";dport="$3";srchost="$4";dsthost="$5";chain="$6";raction="$7";tcptype="$8";cmd="$9";"}' <<< $ruleraw )
        eval $rulevars
        addRule "$cmd" "$proto" "$ptype" "$dport" "$srchost" "$dsthost" "$chain" "$tcptype" "$raction"
        res=$?
        if [ $res -gt 0 ] ; then
            echo "error adding rules"
            return 1
        fi
    done
    echo "COMMIT" >> "$(vzctPath $VZ_CTID_ROOT $JELASTIC_4RULES_FILE)"
    echo "COMMIT" >> "$(vzctPath $VZ_CTID_ROOT $JELASTIC_6RULES_FILE)"
}

function getNumberOfActiveSets() {
    local jel_sets=($(ipset -L -n  2>>$JEM_CALLS_LOG | sed -rne "/^${_JELASTIC_REGEX}\$/p;" | grep "${HOSTER_DOMAIN}" | sed ':a;N;$!ba;s/\n/ /g'))
    echo "${#jel_sets[@]}"
}

function setExists() {
    ipset -L -n 2>>$JEM_CALLS_LOG | grep -q "$1"
}

function netfilterList() {
    echo "IPTABLES rules ($@)"
    echo "--------------------"
    $IPTABLES -L -t $@
    echo
    echo "IP6TABLES rules ($@)"
    echo "--------------------"
    $IP6TABLES -L -t $@
}
