#!/bin/sh
# Sreen Tallam 08/07/08

if [ -f /mnt/logs/var/ip6fw_disable ]
then
    echo "Firewall disabled " > /var/run/firewall_status
    exit 0;
else
    echo "Firewall enabled " > /var/run/firewall_status
fi

IPTABLES="/sbin/iptables"
log="/var/log/messages"
interfaces="/mnt/logs/etc/network/interfaces"
breakout=0
date=$(date | sed "s/[A-Z]\{3\} [0-9]\{4\}//")
loginfo="$date (none) firewall[$pid]:"
brdrev=$(cat /var/run/boardrev)
razorl_rev_p2i=3
MAIN_FIREWALL_LOCKDIR="/tmp/main_firewall_lockdir"

############################################################################
#  Following is the order of execution
#
#  /etc/init.d/firewall start
#  --------------------------
#
#  1. create_v4_rules   (creates the ipv4 related rules)
#  2.   update_v4_chain (creates rules for INPUT, OUTPUT & POSTROUTING chains)
#  3. create_v6_rules   (creates the ipv6 related rules)
#  4.   update_v6_chain (creates rules for INPUT & OUTPUT chains)
#
#
#  /etc/init.d/firewall restart
#  -----------------------------
#
#  1. modify_v4_rules   (flushes the rules and invokes update_v4_chain)
#  2. update_v4_chain
#  3. modify_v6_rules   (flushes the rules and invokes update_v6_chain)
#  4. update_v6_chain
#
#
#  /etc/init.d/firewall open_telnet_port
#  -------------------------------------
#
#  - Open the TELNET port for GDB connection
#
############################################################################
dir_unlock() {
	rm -rf "$1"
	/usr/bin/logger -t $0 "released directory lock $1"
}

dir_lock() {
	not_locked=true;
	while $not_locked
	do
		if mkdir "$1"
		then
			trap "dir_unlock $1" SIGINT SIGTERM EXIT
			not_locked=false
		else
			/usr/bin/logger -t $0 "waiting to obtain directory lock $1"
			sleep 1
		fi

	done

	/usr/bin/logger -t $0  "obtained directory lock $1"
}
#
# create loopback rules for the IPv4 adapter
# See burt 421193

default_loopback() {

    $IPTABLES -A OUTPUT -p ALL -o lo -j output_pkts
    $IPTABLES -A INPUT -p ALL -i lo -j input_pkts
}

update_v4_chain() {

    # for vlan, we always update the rules for the 16 & 64 adapter
    if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
        $IPTABLES -A INPUT -i eth0.16 -j input_eth_16
        $IPTABLES -A INPUT -i eth0.64 -j input_eth_64
        $IPTABLES -A OUTPUT -o eth0.16 -j output_eth_16
        $IPTABLES -A OUTPUT -o eth0.64 -j output_eth_64
    fi


    ### create INPUT and OUTPUT chain rules only if v4 is enabled ###
    if [ "$ipv4_disable_flag" -eq 0 ]
    then

        ###### INPUT chain ######
	$IPTABLES -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP

        $IPTABLES -A INPUT -p ALL -j input_pkts

        $IPTABLES -A INPUT -p ALL -i $LO_IFACE -s $INET_IP -j ACCEPT

        $IPTABLES -A INPUT -p ALL -i $INET_IFACE -d $INET_IP -m state \
            --state ESTABLISHED,RELATED -j ACCEPT

        $IPTABLES -A INPUT -m limit --limit 1/hour \
             --limit-burst 2 -j LOG --log-level DEBUG \
             --log-prefix "ipv4: INPUT died"

        # Set up the rule to forward the spcs packets to spcs chain
        $IPTABLES -A INPUT -p tcp -s 0/0 --dport 49152:65535 -j spcs

        # Now execute the spcs firewall script to see if we need to perform
        # any additional actions
        /usr/local/bin/set_spcs_firewall_rules.sh

        ###### OUTPUT chain ######

        $IPTABLES -A OUTPUT -p ALL -j output_pkts

        $IPTABLES -A OUTPUT -p ALL -o $INET_IFACE -s $INET_IP -j ACCEPT

        $IPTABLES -A OUTPUT -m limit --limit 1/hour --limit-burst 2 \
            -j LOG --log-level DEBUG --log-prefix "ipv4: OUTPUT died"

        ## forwarding (not used for the primary eth adatper) ##

        if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
            $IPTABLES -A FORWARD -i eth0.16 -j forward_eth_16
            $IPTABLES -A FORWARD -i eth0.64 -j forward_eth_64
        fi

        ###### POSTROUTING chain ######

        # Enable simple IP Forwarding and Network Address Translation
        $IPTABLES -t nat -A POSTROUTING -o $INET_IFACE \
             -j SNAT --to-source $INET_IP
    fi

}

update_v6_chain() {

    if [ "$ipv6_disable_flag" -eq 1 ]
    then
        $IP6TABLES -A INPUT -p ipv6-icmp -j icmp_packets
        $IP6TABLES -A OUTPUT -p ipv6-icmp -j icmp_packets
    fi

    ### create INPUT and OUTPUT chain rules only if v6 is enabled ###
    if [ "$ipv6_disable_flag" -eq 0 ]
    then

        ######################### INPUT  #########################
	$IP6TABLES -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP

        $IP6TABLES -A INPUT -p ALL -j input_pkts

        $IP6TABLES -A INPUT -p udp \
                 -m limit --limit 20/second --limit-burst 50 -j ACCEPT

        ### Add rate limiting rule for protocol 254 (Use for experimentation
        ### and testing).  This is needed for USGv6 compliance testing.
        $IP6TABLES -A INPUT -p 254  -s 0/0 -m limit --limit 3/second \
             --limit-burst 5 -j ACCEPT

        $IP6TABLES -A INPUT -m limit --limit 1/hour --limit-burst 2 \
             -j LOG --log-level DEBUG --log-prefix "ipv6: INPUT died"

        # Set up the rule to forward the spcs packets to spcs chain
        $IP6TABLES -A INPUT -p tcp -s 0/0 --dport 49152:65535 -j spcs

        # Now execute the spcs firewall script to see if we need to perform
        # any additional actions
        /usr/local/bin/set_spcs_firewall_rules.sh

        ######################### OUTPUT  #########################

        $IP6TABLES -A OUTPUT -p ipv6-icmp -j icmp_packets

        # Bad TCP packets we don't want.
        $IP6TABLES -A OUTPUT -p tcp ! --syn -m state --state NEW \
             -j LOG -m limit --limit 1/hour --limit-burst 2 \
             --log-prefix "ipv6: OUTPUT new not syn"
        $IP6TABLES -A OUTPUT -p tcp ! --syn -m state --state NEW \
             -j DROP

        $IP6TABLES -A OUTPUT -p ALL -j ACCEPT

        # Log weird packets that don't match the above.
        $IP6TABLES -A OUTPUT -m limit --limit 1/hour \
             --limit-burst 2 -j LOG --log-level DEBUG \
             --log-prefix "ipv6: OUTPUT died"

    fi

}

create_v4_rules() {

    INET_IP=$IP
    LO_IP="127.0.0.1"

    echo "0" > /proc/sys/net/ipv4/ip_forward

    $IPTABLES -F

    # Drop all incoming traffic
    $IPTABLES -P INPUT DROP
    $IPTABLES -P OUTPUT DROP
    $IPTABLES -P FORWARD DROP

    # Clear out nat table
    # if this is not flushed, RLM keeps using the oldest ip address
    # that is present in RLM for the outgoing packets
    $IPTABLES -t nat --flush

    $IPTABLES -N input_pkts
    $IPTABLES -F input_pkts

    $IPTABLES -N ssh_pkts
    $IPTABLES -F ssh_pkts

    $IPTABLES -N output_pkts
    $IPTABLES -F output_pkts

    #Create spcs chain for ipv4
    $IPTABLES -N spcs
    $IPTABLES -F spcs

    $IPTABLES -N blocked_ip
    $IPTABLES -F blocked_ip

    if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
        for i in 16 64 ; do
            for r in input output forward ; do
                $IPTABLES -N ${r}_eth_${i}
                $IPTABLES -F ${r}_eth_${i}
                ###### input_pkts chain ######
                # create the rules for vlan, the default rules will be accept
                $IPTABLES -A ${r}_eth_${i} -j ACCEPT
            done
        done
    fi

    $IPTABLES -A input_pkts -p tcp ! --syn -m state --state NEW \
         -j LOG -m limit --limit 1/hour --limit-burst 2 \
         --log-prefix "ipv4: INPUT new not syn"

    if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
        $IPTABLES -A input_pkts -p ALL -i $LO_IFACE -j ACCEPT
    else
        $IPTABLES -A input_pkts -p ALL -i $LO_IFACE -s $LO_IP -j ACCEPT
    fi

    $IPTABLES -A input_pkts -p ALL -j ssh_pkts

    $IPTABLES -A input_pkts -p ALL -i $INET_IFACE -j blocked_ip

    $IPTABLES -A input_pkts -p ICMP -i $INET_IFACE -s 0/0 \
        --icmp-type 8 -m limit --limit 3/second --limit-burst 5 \
        -j ACCEPT

    $IPTABLES -A input_pkts -p ICMP -i $INET_IFACE -s 0/0 \
        --icmp-type 11 -m limit --limit 3/second --limit-burst 5 \
        -j ACCEPT

    ###### ssh_pkts chain ######

    #Following 2 rules create a 'recent' list of recently seen IP addresses.
    #If new SSH connection requests are made from the same IP in a short
    #interval, it is likely a DOS attack. The following rule allows 10 SSH
    #requests from the same IP within a minute and then drops new requests.

    $IPTABLES -A ssh_pkts -p TCP -i $INET_IFACE --dport 22 -m state \
        --state NEW -m recent --set

    $IPTABLES -A ssh_pkts -p TCP -i $INET_IFACE --dport 22 -m state \
        --state NEW -m recent --update --seconds 60 --hitcount 10 -j DROP

     #Following rule rate-limits new incoming ssh connection requests to
     #an average of 20 per minute, with an initial burst of 20 connections

    $IPTABLES -A ssh_pkts -p TCP -i $INET_IFACE -s 0/0 \
        --dport 22 --syn -m state --state NEW -m limit --limit 20/minute \
        --limit-burst 20 -j ACCEPT

    ###### output_pkts chain ######

    $IPTABLES -A output_pkts -p tcp ! --syn -m state --state NEW -j LOG \
         -m limit --limit 1/hour --limit-burst 2 \
         --log-prefix "ipv4: OUTPUT new not syn"

    $IPTABLES -A output_pkts -p tcp ! --syn -m state --state NEW -j DROP

    if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
#        $IPTABLES -t mangle -A PREROUTING -p tcp --sport 5141 -j MARK --set-mark 16
#        $IPTABLES -t mangle -A PREROUTING -p tcp --sport 5131 -j MARK --set-mark 16
        $IPTABLES -A output_pkts -p ALL -o $LO_IFACE -j ACCEPT
    else
        $IPTABLES -A output_pkts -p ALL -s $LO_IP -j ACCEPT
    fi

    # Default spcs chain rule: DROP everything coming to the spcs chain
    # Only if spcs is enabled and listening would this rule will change
    $IPTABLES -A spcs -p tcp -s 0/0 -j DROP

    ###### INPUT, OUTPUT AND POSTROUTING chains ######
    update_v4_chain
}


##############################################
#  Initial ipv6 rules creation
##############################################

create_v6_rules() {

    LO_IP6="::1"
    #LO_IP6="::1/128"

    # Clear out any old rules
    $IP6TABLES -F INPUT
    $IP6TABLES -F OUTPUT
    $IP6TABLES -F FORWARD

    # 4.1.1 Set policies ( Drop all incoming traffic)
    $IP6TABLES -P INPUT DROP
    $IP6TABLES -P OUTPUT DROP
    $IP6TABLES -P FORWARD DROP

    $IP6TABLES -N icmp_packets
    $IP6TABLES -F icmp_packets

    $IP6TABLES -N input_pkts
    $IP6TABLES -F input_pkts

    $IP6TABLES -N ssh_pkts
    $IP6TABLES -F ssh_pkts

    #Create spcs chain for ipv6
    $IP6TABLES -N spcs
    $IP6TABLES -F spcs

    $IP6TABLES -N blocked_ip
    $IP6TABLES -F blocked_ip

    ######################### icmp_packets rules ########################

    # Rate limit all ipv6-icmp packet types.  This is needed for USGv6
    # compliance testing.
    $IP6TABLES -A icmp_packets -p ipv6-icmp -s 0/0 \
         -m limit --limit 10/second --limit-burst 30 -j ACCEPT

    ###### input_pkts chain ######

    $IP6TABLES -A input_pkts -p ALL -i $INET_IFACE -j blocked_ip

    # allowed chain
    # Only Accept packets with the RST,ACK set and SYN cleared
    # i.e. only connections that are coming back from the internet with
    # ACK,RST flags set and SYN cleared. This is used for all the
    # HTTP connections intiated from RLM. WGET is one such example
    $IP6TABLES -A input_pkts -p tcp ! --syn -j ACCEPT

    $IP6TABLES -A input_pkts -p ipv6-icmp -j icmp_packets

    # handle ssh packets
    $IP6TABLES -A input_pkts -p ALL -j ssh_pkts

    #$IP6TABLES -A input_pkts -p ICMP -i $INET_IFACE -j icmp_packets

    # Rules for special networks not part of the Internet
    $IP6TABLES -A input_pkts -p ALL -i $LO_IFACE -s $LO_IP6 -j ACCEPT

    ###### ssh_pkts chain ######
    $IP6TABLES -A ssh_pkts -p tcp -i $INET_IFACE -s 0/0 --dport 22 \
         -j ACCEPT


    # Default spcs chain rule: DROP everything coming to the spcs chain
    # Only if spcs is enabled and listening would this rule will change

    $IP6TABLES -A spcs -p tcp -s 0/0 -j DROP

    ###### INPUT AND OUTPUT chains ######
    update_v6_chain

}


####################################################
#  Ipv4 rules modification for the new ipv4 address
####################################################

modify_v4_rules() {

    INET_IP=$IP
    LO_IP="127.0.0.1"

    echo "0" > /proc/sys/net/ipv4/ip_forward

    # Clear out nat table
    # if this is not flushed, RLM keeps using the oldest ip address
    # that is present in RLM for the outgoing packets
    $IPTABLES -t nat --flush
    $IPTABLES -F INPUT
    $IPTABLES -F OUTPUT

    ###### INPUT, OUTPUT AND POSTROUTING chains ######
    update_v4_chain

}

####################################################
#  Ipv6 rules modification for the new ipv6 address
####################################################

modify_v6_rules() {

    LO_IP6="::1"
    #LO_IP6="::1/128"

    # Clear out any old rules
    $IP6TABLES -F INPUT
    $IP6TABLES -F OUTPUT

    ###### INPUT AND OUTPUT chains ######
    update_v6_chain
}

#set -x

case "$1" in
  stop)
    echo "."
    ;;

  start|reload|force-reload|restart)

    # do not setup the firewall if in a test fixture.
    if [ -f /var/run/InTestFixture ]; then
      exit 0;
    fi

    IP6TABLES="/sbin/ip6tables"
    if [ ! -f "/var/run/RazorL" ] || [ "$brdrev" -ge "$razorl_rev_p2i" ] ; then
        INET_IFACE="eth0.32"
        IFCONFIG="/sbin/ifconfig eth0.32"
    else
        INET_IFACE="eth0"
        IFCONFIG="/sbin/ifconfig eth0"
    fi
    LO_IFACE="lo"

    tmp_ifconfig_eth0="/tmp/ifconfig_file_eth0"
    tmp_ip_6_addr="/tmp/ip_6_addr"
    IP6ADDR="/sbin/ip -6 addr"
    tuples_filer="/mnt/logs/etc/rlm_config_from_filer"
    IPv4="/sbin/ip"
    got_router_assigned_addr=0

    ##################################################################
    #  Get IPv4 and IPv6 disable/enable status
    ##################################################################
    if [ -s "$tuples_filer" ]
    then
        ipv6_disable_flag=$(awk '/^ipv6_disable/ {print $2}' $tuples_filer)
        ipv4_disable_flag=$(awk '/^ipv4_disable/ {print $2}' $tuples_filer)
        ip_filer=$(awk '/^ip / {print $2}' $tuples_filer)
        is_ipv6_ra_disabled=$(awk '/^ra_disable/ {print $2}' $tuples_filer)
    else
        ipv4_disable_flag=0
        ipv6_disable_flag=1
    fi

    if [ -z "$ipv4_disable_flag" ]
    then
        ipv4_disable_flag=0
    fi

    if [ -z "$ipv6_disable_flag" ]
    then
        ipv6_disable_flag=1
    fi

    if [ -z "$is_ipv6_ra_disabled" ]
    then
        is_ipv6_ra_disabled=0
    fi

    ##################################################################
    #  Get router assigned address
    #  The execution of the v6 firewall happens much faster than
    #  the time taken for the Router Advertised v6 address to reach
    #  RLM. So the script needs to wait until the RA v6 address
    #  is assigned to the RLM.
    ##################################################################

    if [ "$ipv6_disable_flag" -eq 0 ]
    then
        LIMIT=20
        counter=1

        while [ "$counter" -ne "$LIMIT" ]
        do
            $IP6ADDR >$tmp_ip_6_addr
            counter=$(expr $counter + 1)
            ip6_router_assigned_addr=$(/usr/local/bin/get_ip6_router_assigned_addr.sh $tmp_ip_6_addr)
            if [ -n "$ip6_router_assigned_addr" ]
            then
                got_router_assigned_addr=1
                break
            fi

            /bin/usleep 250000
        done

        $IP6ADDR >$tmp_ip_6_addr
        ip6_global_addr="$(/usr/local/bin/get_ip6_global_addr.sh \
            $tmp_ip_6_addr)"

        # If there is no Ipv6 address to assing, then, please DO NOT
        # run the IPv6 firewall .. Save time!!
        if [ -z "$ip6_router_assigned_addr" -a -z "$ip6_global_addr" -a "$is_ipv6_ra_disabled" -eq "0" ]
        then
            echo "$loginfo No Global IPv6 or Router Assigned Address's were found by RLM " 1>>$log
        fi
    fi

    ##################################################################
    #  Get IPv4 address.  There are two cases:
    #  1. if [ IPv4 flag == " " ] ; Older OnTap -> get v4 address
    #  2. if [ IPv4 flag == "1"/"0" ] ; Newer Ontap
    #   a . if [ IPv4 flag == "1" ] ; disable v4 address
    #   b . if [ IPv4 flag == "0" ] ; enable v4 address
    ##################################################################

    if [ "$ipv4_disable_flag" -eq 1 -o $ip_filer == 0.0.0.0 ]
    then
        IP=0
    else
        IP=$(/usr/local/bin/ipaddr.sh)
        count=0
        while [ -z $IP ] && [ "$count" -lt 30 ]
        do
            if [ "$count" -gt 0 ]
            then
                sleep 1
            fi
            count=$((count+1))
            IP=$(/usr/local/bin/ipaddr.sh)
        done

	# considering if IPv6 address is available, it confirms interface file credibility
	# and DHCP server is unable to allocated IPv4 address
        if [ "$ipv6_disable_flag" -eq 0 ]
	then
            ip6addr=$(awk '/^ip6_addr/ {print $2}' $tuples_filer)
	    if [ -n "$ip6addr" ] && [ -n "$ip6_global_addr" ] ; then
                if [ "$ip6addr" == "$ip6_global_addr" ] ; then
		    dont_rollback=1
		else
		    dont_rollback=0
		fi
	    else
		dont_rollback=0
	    fi
	fi

        # If weve run out of retries, we try to roll back the interfaces file
        if [ "$count" -ge 30 ] && [ "$dont_rollback" -eq 0 ] ; then
            IP=0
            num_prev_interfaces=$(ls -la $interfaces.*[0-9] | wc -l)

            # Use default interfaces file if none are available otherwise use
            # the last known healthy config (see rlm_conf_sp.sh)
            if [ "$num_prev_interfaces" -le 0 ]
            then
                if [ -f "/var/run/RazorL" ] && [ $brdrev -lt $razorl_rev_p2i ] ; then
                    def_interfaces="/etc/defconfig/etc/network/interfaces.RazorNIC"
                else
                    def_interfaces="/etc/defconfig/etc/network/interfaces.RazorSW"
                fi

                # If default config is the same as what is loaded
                # breakout to prevent an infinite loop
                if diff $interfaces $def_interfaces &> /dev/null ; then
                    breakout=1
                else
                    logger "Networking configuration may be corrupt. Copying to logger (below) and rolling back to default."
                    logger < $interfaces
		    cp $def_interfaces $interfaces
                fi
            else
                rollback_file=$(ls -c $interfaces.*[0-9] | head -n 1)
                logger "Networking configuration may be corrupt. Copying to logger (below) and rolling back to previous config."
                logger < $interfaces
		cp $rollback_file $interfaces
                rm -f $rollback_file
            fi

            if [ "$breakout" -ne 1 ]
            then
                #restart networking and then restart self (firewall)
                /etc/init.d/networking restart
                sleep 10
                exec $(readlink -f $0) restart
                logger "exec to restart the firewall failed...exiting..."
                exit 1
            fi
        fi
    fi

    #############################################
    #  create/reconfigure v4 and v6 firewall rules
    #############################################

	#Lock firewall - unlocking is handled as a trap
	dir_lock $MAIN_FIREWALL_LOCKDIR

    if [ "$1" == "start" ]
    then
        create_v4_rules
        create_v6_rules
    fi

    if [ "$1" == "restart" ]
    then
        modify_v4_rules
        modify_v6_rules
    fi


    if [ "$1" == "start" ]
    then
        # set SSH access control
        echo "Setting SSH access control rules " >> /var/log/messages
        /usr/local/bin/set_ssh_firewall_rules.sh
    fi

    # Bring the interface down if both these flags are set to TRUE
    # This will help bring the interface next time around when there
    # is some actual IP configuration.
    # Burt reference 361543
    if [ "$ipv4_disable_flag" -eq 1  -a  "$ipv6_disable_flag" -eq 1 ];  then
        $IFCONFIG down
        /sbin/ifdown $INET_IFACE
        # allow packets to cross lo adapter
        default_loopback
    else
        #############################################
        #  delete v4 address if v4 is disabled
        #  delete v6 address(s) if v6 is disabled
        #############################################

        $IFCONFIG >$tmp_ifconfig_eth0

        if [ "$ipv4_disable_flag" -eq 1  ]
        then
            IP=$(cat $tmp_ifconfig_eth0 | grep "inet addr:" | \
                sed 's/.*inet addr:\([^ ]*\).*/\1/')
            if [ -n "$IP" ]
            then
                $IPv4 addr del $IP dev $INET_IFACE
            fi

            # allow packets to cross lo adapter
            default_loopback
        fi

        if [ "$ipv6_disable_flag" -eq 1  ]
        then
            IP6=$(cat $tmp_ifconfig_eth0 | awk '/inet6 addr:/ {print $3}')
            for address_to_del in $IP6
            do
                $($IFCONFIG del "$address_to_del")
            done
        elif [ "$is_ipv6_ra_disabled" -eq 1 ]
        then
            ip6_router_assigned_addr="$($IP6ADDR | awk '/scope global dynamic $/ {print $2}')"
            [ -n "${ip6_router_assigned_addr}" ] && $IFCONFIG del $ip6_router_assigned_addr
        fi
    fi

    ;;

    open_telnet_port)
        # Enable the TELNET port of RLM when GDB Telnet mode is turned ON.
        gdb_console_telnet_port="6466"

        $IPTABLES -A INPUT -p TCP -s 0/0 --dport $gdb_console_telnet_port -j ACCEPT
        echo "$loginfo firewall set to accept telnet connection for GDB" 1>>$log
        echo "."
    ;;


  *)
    echo "Usage: /etc/init.d/ipchains {start|stop|reload|force-reload|restart}"
    exit 1
esac

exit 0

