#!/bin/sh
RCDLINKS="2,S41 3,S41 6,K41"
#
#     The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V1.1 3/26/2001
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]	      
#
#     (c) 1999,2000,2001 - Tom Eastep (teastep@evergo.net)
#
#	On most distributions, this file should be called:
#	/etc/rc.d/init.d/shorewall or /etc/init.d/shorewall
#
#	Complete documentation is available at http://shorewall.sourceforge.net
#
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of Version 2 of the GNU General Public License 
#	as published by the Free Software Foundation.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
#
#	If an error occurs while starting or restarting the firewall, the
#	firewall is automatically stopped.
#
#	Commands are:
#
#	   shorewall start			  Starts the firewall 
#	   shorewall restart			  Restarts the firewall
#	   shorewall stop			  Stops the firewall
#	   shorewall status			  Displays firewall status
#	   shorewall reset			  Resets iptabless packet and
#						  byte counts
#	   shorewall clear			  Remove all Shorewall chains 
#						  and rules/policies.
#	   shorewall refresh	.		  Rebuild the common chain
#
# chkconfig: 2345 25 90
# description: Packet filtering firewall
#
# Run all utility programs using the C locale					
#
# Thanks to Vincent Planchenault for this tip #

export LC_ALL=C
################################################################################
# Run iptables and if an error occurs, stop the firewall and quit	       #
################################################################################
run_iptables() {
    if ! iptables `echo $@ | sed 's/!/! /'`; then
        [ -z "$stopping" ] && { stop_firewall; exit 2; }
    fi
}
################################################################################
# Run ip and if an error occurs, stop the firewall and quit		       #
################################################################################
run_ip() {
    if ! ip $@ ; then
	[ -z "$stopping" ] && { stop_firewall; exit 2; }
    fi
}
################################################################################
# Run arp and if an error occurs, stop the firewall and quit		       #
################################################################################
run_arp() {
    if ! arp $@ ; then
	[ -z "$stopping" ] && { stop_firewall; exit 2; }
    fi
}
################################################################################
# Create a filter chain							       #
#									       #
# If the chain isn't one of the common chains then			       #
# add a rule to the chain allowing packets that are part of an		       #
# established connection. Create a variable ${1}_exists and set its value      #
# to Yes to indicate that the chain now exists.				       #
################################################################################
createchain() # $1 = chain name
{
    run_iptables -N $1

    case $1 in
    common|icmpdef|shorewall|rfc1918)
	;;
    *)
	state="ESTABLISHED"
	[ -n "$ALLOWRELATED" ] && state="$state,RELATED"
	run_iptables -A $1 -m state --state $state -j ACCEPT

	;;
    esac

    eval ${1}_exists=Yes
}
################################################################################
# Determine if a chain exists						       #
#									       #
# When we create a chain "chain", we create a variable named chain_exists and  #
# set its value to Yes. This function tests for the "_exists" variable	       #
# corresponding to the passed chain having the value of "Yes".		       #
################################################################################
havechain() # $1 = name of chain
{
    eval test \"\$${1}_exists\" = Yes
}
################################################################################
# Ensure that a chain exists (create it if it doesn't)			       #
################################################################################
ensurechain() # $1 = chain name
{
    havechain $1 || createchain $1
}
################################################################################
# Add a rule to a chain creating the chain if necessary			       #
################################################################################
addrule() # $1 = chain name, remainder of arguments specify the rule
{
    ensurechain $1
    run_iptables -A $@
}
################################################################################
# Delete a chain if it exists						       #
################################################################################
deletechain() # $1 = name of chain
{
    qt iptables -L $1 -n && qt iptables -F $1 && qt iptables -X $1
}
################################################################################
# Set a standard chain's policy and flush its rules			       #
################################################################################
setpolicy() # $1 = name of chain, $2 = policy
{
    run_iptables -P $1 $2
    run_iptables -F $1
}
################################################################################
# Set a standard chain's policy and immediately enable established connections #
################################################################################
setpolicycontinue() # $1 = name of chain, $2 = policy
{
    setpolicy $1 $2
    run_iptables -A $1 -m state --state ESTABLISHED -j ACCEPT
}
################################################################################
# Flush one of the NAT table chains					       #
################################################################################
flushnat() # $1 = name of chain
{
    run_iptables -t nat -F $1
}
################################################################################
# Flush one of the Mangle table chains					       #
################################################################################
flushmangle() # $1 = name of chain
{
    run_iptables -t mangle -F $1
}
################################################################################
# Find interfaces to a given zone					       #
#									       #
# Read /etc/shorewall/interfaces and for each record matching the passed ZONE, #
# echo the contents of the "INTERFACE" column				       #
################################################################################
find_interfaces() # $1 = interface zone
{
    while read z interface subnet options; do
	[ "x$z" = "x$1" ] && echo $interface
    done < /etc/shorewall/interfaces
}
################################################################################
# Find hosts in a given zone						       #
#									       #
# Read /etc/shorewall/hosts and for each record matching the passed ZONE,      #
# echo the contents of the "HOST(S)" column				       #
################################################################################
find_hosts() # $1 = host zone
{
    local hosts

    while read z hosts options; do
	[ "x$z" = "x$1" ] && echo $hosts
    done < /etc/shorewall/hosts
}
################################################################################
# Determine the interfaces on the firewall				       #
#									       #
# For each zone, create a variable called ${zone}_interfaces. This	       #
# variable contains a space-separated list of interfaces to the zone	       #
################################################################################
determine_interfaces() {
    for zone in $zones; do
	interfaces=`find_interfaces $zone`
	interfaces=`echo $interfaces` # Remove extra trash
	eval ${zone}_interfaces="\$interfaces"
    done
}
################################################################################
# Determine the defined hosts in each zone and generate report		       #
################################################################################
determine_hosts() {
    for zone in $zones; do
	hosts=`find_hosts $zone`
	hosts=`echo $hosts` # Remove extra trash

	if [ -z "$hosts" ]; then
	    ####################################################################
	    # If no hosts are defined for a zone then the zone consists of any 
	    # host that can send us messages via the interfaces to the zone
	    #
	    eval interfaces=\$${zone}_interfaces

	    for interface in $interfaces; do
		if [ -z "$hosts" ]; then
		    hosts=$interface:0.0.0.0/0
		else
		    hosts="$hosts $interface:0.0.0.0/0"
		fi
	    done
	fi

	eval ${zone}_hosts="\$hosts"
	
	[ -n "$hosts" ] && { 
	    eval display=\$${zone}_display 
	    display_list "$display Zone:" $hosts 
	}
    done
}
################################################################################
# Ensure that the passed zone is defined in the zones file or is "fw"	       #
################################################################################
validate_zone() # $1 = zone
{
    local zone
    for zone in $zones fw; do
	[ "$zone" = "$1" ] && return 0
    done
    return 1
}
################################################################################
# Find broadcast addresses corresponding to interfaces to a given zone	       #
################################################################################
find_broadcast() # $1 = zone
{
    while read z interface subnet options; do
	if [ "x$z" = "x$1" -a -n "$subnet" ]; then
	    if [ "x$subnet" = "xdetect" ]; then
		addr="`ip addr show $interface 2> /dev/null`"
		if [ -n "`echo "$addr" | grep BROADCAST`" ]; then
		    addr="`echo "$addr" | \
			mygrep "inet " | sed 's/   inet.*brd //;s/scope.*//'`"
		    echo $addr | cut -d' ' -f 1
		fi
	    elif [ "x${subnet}" != "x-" ]; then
		echo $subnet
	    fi
	fi
    done < /etc/shorewall/interfaces
}
################################################################################
# Find interfaces that have the passed option specified			       #
################################################################################
find_interfaces_by_option() # $1 = option
{
    #
    # This function gets called in stop_firewall so we may be stopping before we
    # processed the zones file
    #    
    if [ -n "$zonepattern" ]; then
	mygrep "$zonepattern" /etc/shorewall/interfaces | \
	while read ignore interface subnet options; do
	    for option in `separate_list $options`; do
	        [ "$option" = "$1" ] && echo $interface && break 1
            done

	done
    fi
}
################################################################################
# Find hosts with the passed option					       #
################################################################################
find_hosts_by_option() # $1 = option
{
    #
    # This funciton gets called in stop_firewall so we may be stopping before we
    # processed the zones file
    #
    if [ -n "$zonepattern" ]; then
	mygrep "$zonepattern" /etc/shorewall/hosts | \
	while read ignore host options; do
	    for option in `separate_list $options`; do
	        [ "$option" = "$1" ] && echo $host
	    done
	done
        
	mygrep "$zonepattern" /etc/shorewall/interfaces | \
	    while read ignore interface subnet options; do
	    	for option in `separate_list $options`; do
	            [ "$option" = "$1" ] && \
		        echo $interface:0.0.0.0/0 &&
		        break 1
            	done
	    done
    fi
}
################################################################################
# Determine if there are interfaces of the given zone and option	       #
#									       #
# Returns zero if any such interfaces are found and returns one otherwise.     #
################################################################################
have_interfaces_in_zone_with_option() # $1 = zone, $2 = option
{
    while read z interface broadcast options; do
	[ "x$z" = "x$1" ] && \
	    for option in `separate_list $options`; do
	    	[ "$option" = "$2" ] && return 0
	    done
    done < /etc/shorewall/interfaces
    return 1
}
################################################################################
# Flush and delete all user-defined chains				       #
################################################################################
deleteallchains() {
    chains="`iptables -L -n | mygrep ^Chain | cut -d' ' -f2`"

    for chain in $chains; do
	case $chain in
	INPUT|OUTPUT|FORWARD)
	    ;;
	*)
	    iptables -F $chain
	    ;;
	esac
    done

    for chain in $chains; do
	case $chain in
	INPUT|OUTPUT|FORWARD)
	    ;;
	*)
	    iptables -X $chain
	    ;;
	esac
    done
}
################################################################################
# Stop the Firewall -							       #
################################################################################
stop_firewall() {
    stopping="Yes"

    rm -f /tmp/shorewallpolicy-$$

    deletechain shorewall

    [ -f /etc/shorewall/stop ] && . /etc/shorewall/stop

    [ -n "$MANGLE_ENABLED" ] && flushmangle OUTPUT && flushmangle PREROUTING

    [ -n "$NAT_ENABLED" ] && delete_nat
    delete_proxy_arp

    setpolicy INPUT DROP
    setpolicy OUTPUT DROP
    setpolicy FORWARD DROP

    deleteallchains

    hosts="`find_hosts_by_option routestopped`"

    for host in $hosts; do
	interface=${host%:*}
	subnet=${host#*:}
	iptables -A INPUT  -i $interface -s $subnet -j ACCEPT
	iptables -A OUTPUT -o $interface -d $subnet -j ACCEPT

	for host1 in $hosts; do
	    [ "$host" != "$host1" ] && \
		iptables -A FORWARD -i $interface -s $subnet \
		    -o ${host1%:*} -d ${host1#*:} -j ACCEPT
	done
    done

    iptables -A INPUT  -i lo -j ACCEPT
    iptables -A OUTPUT -o lo -j ACCEPT


    [ -n "$zonepattern" ] && \
    mygrep "$zonepattern" /etc/shorewall/interfaces | \
    while read ignore interface subnet options; do
    	    for option in `separate_list $options`; do
		if [ "$option" = "dhcp" ]; then
	    	    iptables -A INPUT  -p udp -i $interface --dport 67:68 -j ACCEPT
	 	    iptables -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT
       		    break 1
		fi
  	    done
    done

    case "$IP_FORWARDING" in
    [Oo]n)
    	echo 1 > /proc/sys/net/ipv4/ip_forward
	;;
    [Oo]ff)
        echo 0 > /proc/sys/net/ipv4/ip_forward
        ;;
    esac

    logger "Shorewall Stopped"

    case $command in
    stop|clear)
	;;
    *)
	#
	# The firewall is being stopped when we were trying to do something
	# else. Kill the shell in case we're in a subshell
	#
        kill $$
        ;;
    esac
}
################################################################################
# Remove all rules and remove all user-defined chains			       #
################################################################################
clear_firewall() {
    stop_firewall

    setpolicy INPUT ACCEPT
    setpolicy FORWARD ACCEPT
    setpolicy OUTPUT ACCEPT

    [ -f /etc/shorewall/clear ] && . /etc/shorewall/clear

    logger "Shorewall Cleared"
}
################################################################################
# Set up ipsec tunnels							       #
################################################################################
setup_tunnels() {
    local inchain
    local outchain

    setup_one_ipsec() # $1 = zone, $2 = gateway $3 = gateway zone
    {
	if ! validate_zone $1; then
	    echo "Invalid gateway zone ($3)" \
		" -- Tunnel \"$tunnel\" Ignored" >&2
	    return 1
	fi

	options="-mstate --state NEW -j ACCEPT"
	inchain=${1}2fw
	outchain=fw2${1}
	addrule $inchain          -p 50  -s $2 $options
	addrule $outchain         -p 50  -d $2 $options
	run_iptables -A $inchain  -p 51  -s $2 $options
	run_iptables -A $outchain -p 51  -d $2 $options
	run_iptables -A $inchain  -p udp -s $2 --sport 500 --dport 500 $options
	run_iptables -A $outchain -p udp -d $2 --dport 500 --sport 500 $options

	if [ -n "$3" ]; then
	    if validate_zone $3; then
		addrule fw2${3} -p udp --sport 500 --dport 500 $options
	    else
		echo "Warning: Invalid gateway zone ($3)" \
		" -- Tunnel \"$tunnel\" may encounter keying problems" >&2
	    fi
	fi

	return 0
    }

    setup_one_ipip() # $1 = zone, $2 = gateway $3 = gateway zone
    {
	if ! validate_zone $1; then
	    echo "Invalid gateway zone ($3)" \
		" -- Tunnel \"$tunnel\" Ignored" >&2
	return 1
	fi

	options="-mstate --state NEW -j ACCEPT"
	inchain=${1}2fw
	outchain=fw2${1}
	addrule $inchain    -p 4         -s $2 $options
	addrule $outchain   -p 4         -d $2 $options

	return 0
    }

    while read kind z gateway z1; do
	tunnel="`echo $kind $z $gateway $z1`"
	case $kind in
	\#*)
		;;
	    ipsec)
		setup_one_ipsec $z $gateway $z1 && \
		echo "   IPSEC tunnel to $gateway defined."
		;;
	    ipip)
		setup_one_ipip $z $gateway && \
		echo "   IPIP tunnel to $gateway defined."
		;;
	    *)
		if [ -n "$kind" ]; then
		    echo "Tunnels of type $kind are not supported:" \
			"Tunnel \"$tunnel\" Ignored" >&2
		fi
	;;
	esac
    done < /etc/shorewall/tunnels
}
################################################################################
# Setup Proxy ARP							       #
################################################################################
setup_proxy_arp() {
    setup_one_proxy_arp() {
	run_ip route add $address dev $interface

	run_arp -Ds $address $external pub

	echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp
	echo 0 > /proc/sys/net/ipv4/conf/$external/proxy_arp

	echo $address $interface $external >> ${STATEDIR}/proxyarp

	echo "   Host $address connected to $interface added to ARP on $external"
    }

    > ${STATEDIR}/proxyarp

    [ -f /etc/shorewall/proxyarp ] && while read address interface external; do
	[ -n "$address" ] && case "$address" in
	    \#*)
		;;
	    *)
		[ -n "$address" ] && setup_one_proxy_arp
		;;
	esac
    done < /etc/shorewall/proxyarp
}
################################################################################
# Delete existing Proxy ARP						       #
################################################################################
delete_proxy_arp() {
    if [ -f ${STATEDIR}/proxyarp ]; then
        while read address interface external; do
	    qt arp -i $external -d $address pub
	    qt ip route del $address dev $interface

	    echo 0 > /proc/sys/net/ipv4/conf/$external/proxy_arp
	    echo 0 > /proc/sys/net/ipv4/conf/$interface/proxy_arp
	done < ${STATEDIR}/proxyarp

	rm -f ${STATEDIR}/proxyarp
    fi

    [ -d ${STATEDIR} ] && touch ${STATEDIR}/proxyarp
}           
################################################################################
# Setup Static Network Address Translation (NAT)			       #
################################################################################
setup_nat() {
    local allints
    #
    # At this point, we're just interested in the network translation
    #
    > ${STATEDIR}/nat

    while read external interface internal allints localnat; do
	[ -n "$external" ] && case "$external" in
	    \#*)
		;;
	    *)
		qt ip addr del $external dev $interface
		if [ -z "$allints" -o "$allints" = "Yes" \
		    -o "$allints" = "yes" ]
		then
		    run_iptables -t nat -A PREROUTING -d $external \
			-j DNAT --to-destination $internal
		    run_iptables -t nat -A POSTROUTING -s $internal \
			-j SNAT --to-source $external
		    if [ "$localnat" = "Yes" -o "$localnat" = "yes" ]; then
			run_iptables -t nat -A OUTPUT -d $external \
			-j DNAT --to-destination $internal
		    fi
		else
		    run_iptables -t nat -A PREROUTING -i $interface \
			-d $external -j DNAT --to-destination $internal
		    run_iptables -t nat -A POSTROUTING -o $interface \
			-s $internal -j SNAT --to-source $external
		fi
		run_ip addr add $external dev $interface
		echo "$external $interface" >> ${STATEDIR}/nat
		echo "   Host $internal NAT $external on $interface"
		;;
	esac
    done < /etc/shorewall/nat
}
################################################################################
# Delete existing Static NAT and Port Forwarding			       #
################################################################################
delete_nat() {
    run_iptables -t nat -F PREROUTING
    run_iptables -t nat -F POSTROUTING
    run_iptables -t nat -F OUTPUT

    [ -f ${STATEDIR}/nat ] && while read external interface; do
	qt ip addr del $external dev $interface
    done < ${STATEDIR}/nat
}
################################################################################
# Process a record from the rules file					       #
#									       #
# The caller has loaded the column contents from the record into the following #
# variables:								       #
#									       #
#   target clients servers protocol ports cports address		       #
#									       #
# and has loaded a space-separated list of their values in "rule".	       #
################################################################################
process_rule() {
    ############################################################################
    # Add one rule
    #
    add_a_rule() {
        ########################################################################
	# Determine the format of the client
	#
	[ -n "$client" ] && case "$client" in
	    -)
	    	client=
		;;
	    [0-9]*|![0-9]*)
		#
		# IP Address or subnet
		#
		client="-s $client"
		;;
	    *)
		#
		# Assume that this is a device name
		#
		client="-i $client"
		;;
        esac

    	dest_interface=

	[ -n "$server" ] && case "$server" in
	    -)
	    	server=
		;;
	    [0-9]*|![0-9]*)
		;;
	    *)
		dest_interface="-o $server"
		server=
		;;
	esac
	################################################################
 	# Setup PROTOCOL, PORT and STATE variables
  	#
   	sports=""
	dports=""
     	state="-m state --state NEW"
      	proto=$protocol
       	addr=$address
        serv=$server
        servport=$serverport

        case $proto in
	    tcp|udp)
	    	[ -n "$port" ] && [ "x${port}" != "x-" ] && \
		    dports="--dport $port"
		[ -n "$cport" ] && [ "x${cport}" != "x-" ] && \
		    sports="--sport $cport"
		;;
	    icmp)
		[ -n "$port" ] && dports="--icmp-type $port"
		state=""
		;;
	    all)
		proto=
		;;
	    related)
		proto=
		state="-m state --state RELATED"
		;;
	    *)
		;;
	esac

	proto="${proto:+-p $proto}"

	if [ -n "${serv}${servport}" ]; then
	    ##################################################################
	    # Destination is a Specific Server or we're redirecting a port
	    #
	    if [ -n "$addr" -a "$addr" != "$serv" ]; then
		##############################################################
 		# Must use Prerouting DNAT
		#
		if [ -z "$NAT_ENABLED" ]; then
	    	    echo "Warning - Rule requires NAT; rule \"$rule\" ignored" >&2
	    	    return
		fi

	    if [ "$target" != "ACCEPT" ]; then
	    	echo "Warning - Negative rule may not specify port mapping;" \
	    	"rule \"$rule\" ignored" >&2
	    	return
	    fi

	    if [ "$addr" != "${addr%:*}" ]; then
		snat="${addr#*:}"
		addr="${addr%:*}"
	    else
		snat=""
	    fi

	    [ "$addr" = "all" ] && addr= || addr="-d $addr"

	    if [ -n "$serv" ]; then
		servport="${servport:+:$servport}"
		target1="DNAT --to-destination ${serv}${servport}"
	    else
		target1="REDIRECT --to-port $servport"
	    fi

	    if [ -n "$client" ]; then
		run_iptables -t nat -A PREROUTING $proto $client $sports \
		    $addr $dports -j $target1
	    else
		for source_host in $source_hosts; do
		    run_iptables -t nat -A PREROUTING -i ${source_host%:*} \
			-s ${source_host#*:} $proto $sports \
			$addr $dports -j $target1
		done
	    fi

	    [ -n "$servport" ] && dports="--dport ${servport#*:}"

	    if [ -n "$snat" ]; then
	        if [ -n "$client" ]; then
		   run_iptables -t nat -A POSTROUTING $proto $client \
			$sports -d $serv $dports -j SNAT --to-source $snat
		else
		    for source_host in $source_hosts; do
			run_iptables -t nat -A POSTROUTING \
			    -s ${source_host#*:} $proto $sports \
			    -d $serv $dports -j SNAT --to-source $snat
		    done
		fi
	    fi
	fi

	serv="${serv:+-d $serv}"

	[ -n "$loglevel" ] && run_iptables -A $chain $proto $state $client \
	    $sports $serv $dports -j LOG $LOGPARMS \
	    --log-prefix "Shorewall:$chain:$target:" --log-level $loglevel
	run_iptables -A $chain $proto $state $client $sports \
	    $serv $dports -j $target
	else
 	    ####################################################################
	    # Destination is just a location or an interface
	    #
	    [ -n "$loglevel" ] && run_iptables -A $chain $proto \
	        $dest_interface $state $client $sports $dports -j LOG \
		$LOGPARMS --log-prefix "Shorewall:$chain:$target:" \
		--log-level $loglevel
	    run_iptables -A $chain $proto $dest_interface $state \
	        $client $sports $dports -j $target
	fi
    }
    ############################################################################
    # P r o c e s s _ R u l e    S t a r t s    H e r e
    ############################################################################
    # Parse the Target and Clients columns
    #
    if [ "$target" = "${target%:*}" ]; then
	loglevel=
    else
	loglevel="${target#*:}"
	target="${target%:*}"
    fi

    if [ "$clients" = "${clients%:*}" ]; then
	clientzone="$clients"
	clients=
    else
        clientzone="${clients%:*}"
        clients="${clients#*:}"
    fi
    ############################################################################
    # Validate the Source Zone

    if validate_zone $clientzone; then
	source=$clientzone
    else
	echo "Warning: Undefined Client Zone - rule \"$rule\" ignored" >&2
	return
    fi

    [ $source = fw ] && source_hosts= || eval source_hosts=\"\$${source}_hosts\"

    ############################################################################
    # Parse the servers column
    #
    if [ "$servers" = "${servers%:*}" ] ; then
	serverzone="$servers"
	servers=
	serverport=
    else
	serverzone="${servers%%:*}"
	servers="${servers#*:}"
	if [ "$servers" != "${servers%:*}" ] ; then
	    serverport="${servers#*:}"
	    servers="${servers%:*}"
	else
	    serverport=
	fi
    fi
    ############################################################################
    # Validate the destination zone
    #
    if validate_zone $serverzone; then
	dest=$serverzone
    else
	echo "Warning: Undefined Server Zone - rule \"$rule\" ignored" >&2
	return
    fi

    chain=${source}2${dest}
    ############################################################################
    # Create the canonlcal chain if it doesn't exist
    #
    ensurechain $chain

    for client in `separate_list ${clients:=-}`; do
	for server in `separate_list ${servers:=-}`; do
	    for port in `separate_list ${ports:=-}`; do
		for cport in `separate_list ${cports:=-}`; do
		    add_a_rule
		done
	    done
	done
    done

    echo "   Rule \"$rule\" added."
}
################################################################################
# Process the rules file						       #
################################################################################
process_rules()
{
    while read target clients servers protocol ports cports address; do
	[ -n "$target" ] && case "$target" in
	\#*)
	    ;;
	ACCEPT*|DROP*|REJECT*)
	    rule="`echo $target $clients $servers $protocol $ports $cports $address`"
	    process_rule
	    ;;
	*)
	    rule="`echo $target $clients $servers $protocol $ports $cports $address`"
	    echo "Warning: Invalid Target - rule \"$rule\" ignored" >&2
	    ;;
	esac
    done < /etc/shorewall/rules
}
################################################################################
# Process a record from the tos file					       #
#									       #
# The caller has loaded the column contents from the record into the following #
# variables:								       #
#									       #
#    src dst protocol sport dport tos					       #
#									       #
# and has loaded a space-separated list of their values in "rule".	       #
################################################################################
process_tos_rule() {
    ############################################################################
    # Parse the contents of the 'src' variable
    #
    if [ "$src" = "${src%:*}" ]; then
	srczone="$src"
	src=
    else
	srczone="${src%:*}"
	src="${src#*:}"
    fi
    
    source=
    #
    # Validate the source zone
    #
    if validate_zone $srczone; then
	source=$srczone
    elif [ "$srczone" = "all" ]; then
	source="all"
    else
	echo "Warning: Undefined Source Zone - rule \"$rule\" ignored" >&2
	return
    fi

    [ -n "$src" ] && case "$src" in
	[0-9]*|![0-9]*)
	    #
	    # IP Address or subnet
	    #
	    src="-s $src"
	    ;;
	*)
	    #
	    # Assume that this is a device name
	    #
	    src="-i $src"
	    ;;
    esac

    ############################################################################
    # Parse the contents of the 'dst' variable
    #
    if [ "$dst" = "${dst%:*}" ]; then
	dstzone="$dst"
	dst=
    else
	dstzone="${dst%:*}"
	dst="${dst#*:}"
fi

    dest=
    #
    # Validate the destination zone
    #
    if validate_zone $dstzone; then
	dest=$dstzone
    elif [ "$dstzone" = "all" ]; then
	dest="all"
    else
	echo "Warning: Undefined Destination Zone - rule \"$rule\" ignored" >&2
	return
    fi

    [ -n "$dst" ] && case "$dst" in
	[0-9]*|![0-9]*)
	    #
	    # IP Address or subnet
	    #
	    ;;
	*)
	    #
	    # Assume that this is a device name
	    #
	    echo "Warning: Invalid Destination - rule \"$rule\" ignored" >&2
	    return
	    ;;
    esac

    ############################################################################
    # Setup PROTOCOL and PORT variables
    #
    sports=""
    dports=""

    case $protocol in
	tcp|udp|6|17)
	    [ -n "$sport" ] && [ "x${sport}" != "x-" ] && \
		sports="--sport $sport"
	    [ -n "$dport" ] && [ "x${dport}" != "x-" ] && \
		dports="--dport $dport"
	    ;;
	icmp)
	    [ -n "$dport" ] && [ "x${dport}" != "x-" ] && \
		dports="--icmp-type $dport"
	    ;;
	all)
	    protocol=
	    ;;
	*)
	    ;;
    esac
    
    protocol="${protocol:+-p $protocol}"

    tos="-j TOS --set-tos $tos"

    case "$dstzone" in
    all)
	dst=0.0.0.0/0
	;;
    *)
	[ -z "$dst" ] && eval dst=\$${dstzone}_hosts
	;;
    esac

    for dest in $dst; do
	dest="-d $dest"

	case $srczone in
	fw)
	    run_iptables -t mangle -A OUTPUT \
		$protocol $dest $dports $sports $tos
	    ;;
	all)
	    run_iptables -t mangle -A OUTPUT \
		$protocol $dest $dports $sports $tos
	    run_iptables -t mangle -A PREROUTING \
		$protocol $dest $dports $sports $tos
	    ;;
	*)
	    if [ -n "$src" ]; then
		run_iptables -t mangle -A PREROUTING $src \
		    $protocol $dest $dports $sports $tos
	    else
		eval interfaces=\$${srczone}_interfaces

		for interface in $interfaces; do
		    run_iptables -t mangle -A PREROUTING -i $interface \
			$protocol $dest $dports $sports $tos
		done
	    fi
	    ;;
	esac
    done

    echo "   Rule \"$rule\" added."
}
################################################################################
# Process the tos file							       #
################################################################################
process_tos() {
    echo "Processing /etc/shorewall/tos..."

    mygrep "$zonepattern\|^all\|^fw" /etc/shorewall/tos | \
    while read src dst protocol sport dport tos; do
	rule="`echo $src $dst $protocol $sport $dport $tos`"
	process_tos_rule
    done
}
################################################################################
# Load a Kernel Module							       #
################################################################################
loadmodule() # $1 = module name, $2 - * arguments
{
    local modulename=$1
    local modulefile
    local tmpmodulefile

    if [ -z "`lsmod | grep $modulename`" ]; then
	shift
	modulefile=$MODULESDIR/${modulename}.o

	if [ -f $modulefile ]; then
	    insmod $modulefile $*
	    return
	fi
	#
	# If the modules directory contains compressed modules then we'll
	# assume that insmod can load them
	#
	modulefile=${modulefile}.gz

	if [ -f $modulefile ]; then
	    insmod $modulefile $*
	fi
    fi
}
################################################################################
# Display elements of a list with leading white space			       #
################################################################################
display_list() # $1 = List Title, rest of $* = list to display
{
    [ $# -gt 1 ] && echo "   $*"
}
################################################################################
# Add rules to the "common" chain to silently drop packets addressed to any of #
# the passed addresses							       #
################################################################################
drop_broadcasts() # $* = broadcast addresses
{
    while [ $# -gt 0 ]; do
	run_iptables -A common -d $1 -j DROP
	shift
    done
}
################################################################################
# Add policy rule ( and possibly logging rule) to the passed chain	       #
################################################################################
policy_rules() # $1 = chain to add rules to, $2 = policy, $3 = loglevel
{
    [ "$policy" != "ACCEPT" ] && run_iptables -A $1 -j common
    [ $# -eq 3 ] && run_iptables -A $1 -j LOG $LOGPARMS \
	--log-prefix "Shorewall:$chain:$policy:" --log-level $3
    run_iptables -A $1 -j $2
}
################################################################################
# Generate default policy & log level rules for the passed client & server     #
# zones									       #
#------------------------------------------------------------------------------#
# This function is only called when the canonical chain for this client/server #
# pair is known to exist. If the default policy for this pair specifies the    #
# same chain then we add the policy (and logging) rule to the canonical chain; #
# otherwise add a rule to the canonical chain to jump to the appropriate       #
# policy chain.								       #
################################################################################
default_policy() # $1 = client $2 = server
{
    local chain="${1}2${2}"
    local policy=
    local loglevel=
    
    apply_default()
    {
	########################################################################
	# Construct policy chain name
	#
	chain1=${client}2${server}

	if [ "$chain" = "$chain1" ]; then
	    ####################################################################
	    # The policy chain is the canonical chain; add policy rule to it
	    #
	    policy_rules $chain $policy $loglevel
	else
	    ####################################################################
	    # Policy chain is different; add a rule to jump from the canonical
	    # chain to the policy chain
	    #
	    run_iptables -A $chain -j $chain1
	fi

	echo "   Policy $policy for $1 to $2."
    }

    while read client server policy loglevel ; do
	case "$client" in
	all)
	    [ "$server" = "$2" -o "$server" = "all" ] && {
		apply_default $1 $2
		return
	    }
	    ;;
	*)

	    if [ "$client" = "all" -o "$client" = "$1" ] && \
		[ "$server" = "all" -o "$server" = "$2" ]
	    then
		apply_default $1 $2
		return
	    fi
	    ;;
	esac
    done < /tmp/shorewallpolicy-$$

    echo "Error: No default policy for zone $1 to zone $2" >&2
    stop_firewall
    exit 2
}
################################################################################
# Find the appropriate chain to pass packets from a source zone to a	       #
# destination zone							       #
#									       #
# If the canonical chain for this zone pair exists, echo it's name; otherwise  #
# locate and echo the name of the appropriate policy chain		       #
################################################################################
rules_chain() # $1 = source zone, $2 = destination zone
{
    local chain=${1}2${2}

    havechain $chain && { echo $chain; return; }

    while read client server policy loglevel ; do
	case "$client" in
	all)
	    [ "$server" = "$2" -o "$server" = "all" ] && {
		echo ${client}2${server}
		return;
	    }
	    ;;
	*)
	if [ "$client" = "all" -o "$client" = "$1" ] && \
	    [ "$server" = "all" -o "$server" = "$2" ]; then
		echo ${client}2${server}
		return
	    fi
	    ;;
	esac
    done < /tmp/shorewallpolicy-$$

    echo "Error: No appropriate chain for zone $1 to zone $2" >&2
    stop_firewall
    exit 2
}
################################################################################
# Set up Dynamic NAT (Masquerading)					       #
################################################################################
setup_masq() {
    setup_one() {

	if [ "$interface" = "${interface%:*}" ]; then
	    destnet="0.0.0.0/0"
	else
	    destnet="${interface#*:}"
	    interface="${interface%:*}"
	fi

	case $subnet in
	[0-9]*|![0-9]*)
	    ;;
	*)
	    ipaddr="`run_ip addr show $subnet | mygrep 'inet '`"

	    if [ -z "$ipaddr" ]; then
		echo "Interface $subnet must be up before Shorewall starts" >&2
		stop_firewall
		exit 1
	    fi

	    subnet="`echo $ipaddr | sed s/"    "// | cut -d' ' -f2`"
	    [ -z "`echo "$subnet" | grep '/'`" ] && subnet="${subnet}/32"
	    ;;
	esac

	run_iptables -t nat -A POSTROUTING -s $subnet -d $destnet -o $interface \
	    -j MASQUERADE
	echo "   To $destnet from $subnet through interface $interface"
    }

    [ -n "$NAT_ENABLED" ] && echo "Masqueraded Subnets and Hosts:"

    while read interface subnet; do
	[ -n "$interface" ] && case "$interface" in
	    \#*)
		;;
	    *)
		[ -n "$NAT_ENABLED" ] && setup_one || \
		    echo "Warning: NAT disabled; masq rule ignored" >&2
		;;
	esac
    done < /etc/shorewall/masq
}
################################################################################
# Setup Intrazone chain if appropriate					       #
################################################################################
setup_intrazone() # $1 = zone
{
    eval hosts=\$${1}_hosts

    if [ "$hosts" != "${hosts% *}" ] || \
	have_interfaces_in_zone_with_option $1 multi
    then
	ensurechain ${1}2${1}
    fi
}
################################################################################
# Start/Restart the Firewall						       #
################################################################################
define_firewall() # $1 = Command (Start or Restart)
{
    echo "${1}ing Shorewall..."

    osversion=`uname -r`
    
    case $osversion in
    2.4.*|2.5.*)
        ;;
    *)
	echo "Shorewall version $version does not work with kernel version $osversion"
	exit 2
	;;
    esac

    [ -z "$MODULESDIR" ] &&
    MODULESDIR=/lib/modules/$osversion/kernel/net/ipv4/netfilter

    if [ -f /etc/shorewall/modules -a -d $MODULESDIR ]; then
	echo "Loading Modules..."
	. /etc/shorewall/modules
    fi

    echo "Initializing..."

    deletechain shorewall
        
    qt mkdir -p ${STATEDIR}

    [ -n "$NAT_ENABLED" ] && delete_nat
    
    delete_proxy_arp

    [ -n "$MANGLE_ENABLED" ] && flushmangle OUTPUT && flushmangle PREROUTING

    setpolicycontinue INPUT   DROP
    setpolicycontinue OUTPUT  DROP
    setpolicycontinue FORWARD DROP

    [ -f /etc/shorewall/init ] && . /etc/shorewall/init

    echo "Determining Zones..."

    determine_zones

    [ -z "$zones" ] && echo "ERROR: No Zones Defined" >&2 && exit 2

    display_list "Zones:" $zones

    echo "Determining Hosts in Zones..."

    determine_interfaces
    determine_hosts

    echo "Deleting user chains..."

    deleteallchains

    createchain icmpdef
    createchain common
    
    echo "Configuring Proxy ARP and NAT"

    setup_proxy_arp
    setup_nat

    case "$IP_FORWARDING" in
    [Oo]n)
    	echo 1 > /proc/sys/net/ipv4/ip_forward
	echo "IP Forwarding Enabled"
	;;
    [Oo]ff)
        echo 0 > /proc/sys/net/ipv4/ip_forward
	echo "IP Forwarding Disabled!"
        ;;
    esac

    echo "Adding Common Rules"
    ############################################################################
    # Common ICMP rules
    #
    if [ -f /etc/shorewall/icmpdef ]; then
	. /etc/shorewall/icmpdef
    else
	. /etc/shorewall/icmp.def
    fi
    ############################################################################
    # Common rules in each chain
    #
    if [ -f /etc/shorewall/common ]; then
	. /etc/shorewall/common
    else
	. /etc/shorewall/common.def
    fi
    ###########################################################################
    # BROADCASTS
    #
    for zone in $zones; do
	eval interfaces=\$${zone}_interfaces

	[ -n "$interfaces" ] && drop_broadcasts `find_broadcast $zone`
	setup_intrazone $zone
    done

    norfc1918_interfaces="`find_interfaces_by_option norfc1918`"

    if [ -n "$norfc1918_interfaces" ]; then
	echo "Enabling RFC1918 Filtering"

	createchain rfc1918
	########################################################################
	# Since Linux DHCP client's often use the source address of another
	# interface when they DISCOVER, we don't get alarmed over limited
	# broadcasts for DHCP here.

	run_iptables -A rfc1918 -p udp --dport 67 -d 255.255.255.255 -j RETURN

	########################################################################
	# 240.0.0.0/4 isn't mentioned in RFC 1918 but since it is reserved, we
	# include it here
	#
	disp="LOG --log-prefix "Shorewall:rfc1918:DROP:" --log-level info"

	for subnet in '10.0.0.0/8' '192.168.0.0/16' \
		'172.16.0.0/12' '240.0.0.0/4'; do
	    run_iptables -A rfc1918 -s $subnet -j $disp $LOGPARMS
	    run_iptables -A rfc1918 -s $subnet -j DROP
	done
	########################################################################
	# Also drop Microsoft's auto-configuration class B but don't squak
	# about it
	#
	run_iptables -A rfc1918 -s 169.254.0.0/16 -j DROP

	for interface in $norfc1918_interfaces; do
	    run_iptables -A INPUT   -i $interface -j rfc1918
	    run_iptables -A FORWARD -i $interface -j rfc1918
	done
    fi
    ############################################################################
    # Enable the Loopback interface
    #
    run_iptables -A INPUT   -i lo -j ACCEPT
    run_iptables -A OUTPUT  -o lo -j ACCEPT
    ############################################################################
    # Enable icmp output
    #
    run_iptables -A OUTPUT -p icmp -j ACCEPT

    echo "Setting up ICMP Echo handling..."
    
    noping_interfaces="`find_interfaces_by_option noping`"

    for zone in $zones; do
	eval interfaces=\$${zone}_interfaces

	for interface in $interfaces; do
	    [ -n "`echo $noping_interfaces | grep $interface`" ] && \
		target=DROP || target=ACCEPT
	    addrule ${zone}2fw -i $interface \
		-p icmp --icmp-type echo-request -j $target
	done
    done

    for f in /proc/sys/net/ipv4/conf/*/rp_filter; do
	echo 0 > $f
    done

    interfaces="`find_interfaces_by_option routefilter`"

    if [ -n "$interfaces" ]; then
	echo "Setting up Kernel Route Filtering..."

	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/rp_filter
	    if [ -f $file ]; then
		echo 1 > $file
	    else
		echo "Warning: Cannot set route filtering on $interface" >&2
	    fi
	done
    fi

    [ -f /etc/shorewall/tunnels ] && \
	echo "Processing /etc/shorewall/tunnels..." && setup_tunnels

    chains="`run_iptables -L -n | mygrep ^Chain | cut -d' ' -f2`"
    chains=`echo $chains`

    echo "Processing /etc/shorewall/rules..."
    
    process_rules

    echo "Adding rules for DHCP"

    while read zone interface subnet options; do
	[ -n "$zone" ] && case "$zone" in
	    \#*)
		;;
	    *)
		if validate_zone "$zone"; then
		    for option in `separate_list $options`; do
			if [ "$option" = "dhcp" ]; then
			    run_iptables -A INPUT  -p udp -i $interface --dport 67:68 -j ACCEPT
			    run_iptables -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT
			    break 1
		    	fi
		    done
		fi
		;;
	esac
    done < /etc/shorewall/interfaces
   
    echo "Processing /etc/shorewall/policy..."

    mygrep "${zonepattern}\|^fw\|^all" /etc/shorewall/policy > \
        /tmp/shorewallpolicy-$$

    while read client server policy loglevel ; do
	chain=${client}2${server}
	if ! havechain $chain; then
	    createchain $chain
	    [ "$client" = "all" -o "$server" = "all" ] && \
		policy_rules $chain $policy $loglevel
	fi
    done < /tmp/shorewallpolicy-$$

    for zone in fw $zones; do
	for zone1 in fw $zones; do
	    chain=${zone}2${zone1}
	    if havechain $chain; then
		[ -f /etc/shorewall/$chain ] && . /etc/shorewall/$chain
		default_policy $zone $zone1
	    fi
	done
    done

    [ -f /etc/shorewall/masq ] && setup_masq

    [ -f /etc/shorewall/tos ] && [ -n "$MANGLE_ENABLED" ] && process_tos

    echo "Activating Rules..."

    for zone in $zones; do
	eval source_hosts=\$${zone}_hosts

	for host in $source_hosts; do
	    interface=${host%:*}
	    subnet=${host#*:}
	    run_iptables -A INPUT -i $interface -s $subnet \
		-j `rules_chain $zone fw`
	    run_iptables -A OUTPUT -o $interface -d $subnet \
		-j `rules_chain fw $zone`
	    done

	for zone1 in $zones; do
	    eval dest_hosts=\$${zone1}_hosts

	    chain="`rules_chain $zone $zone1`"

	    for host in $source_hosts; do
		interface=${host%:*}
		subnet=${host#*:}

		for host1 in $dest_hosts; do
		    interface1=${host1%:*}
		    subnet1=${host1#*:}

		    [ $interface = $interface1 -a "x$subnet" = "x$subnet1" ] ||\
			run_iptables -A FORWARD -i $interface -s $subnet \
			    -o $interface1 -d $subnet1 -j $chain
		done
	    done
	done
    done

    rm -f /tmp/shorewallpolicy-$$

    while read zone interface broadcast options; do
	for z in $zones; do
	    [ "x$z" = "x$zone" ] && \
	    	for option in `separate_list $options`; do
	    	    [ "$option" = "multi" ] && \
			run_iptables -A FORWARD -i $interface \
		    		-o $interface -j ${zone}2${zone} && \
			    break 1
        	done
	done
    done < /etc/shorewall/interfaces

    for chain in INPUT OUTPUT FORWARD; do
	[ -f /etc/shorewall/$chain ] && . /etc/shorewall/$chain
	run_iptables -A $chain -j common
	run_iptables -A $chain -j LOG $LOGPARMS \
	    --log-prefix "Shorewall:$chain:DROP:" --log-level info
    done

    run_iptables -D INPUT 1
    run_iptables -D OUTPUT 1
    run_iptables -D FORWARD 1

    [ -f /etc/shorewall/start ] && . /etc/shorewall/start

    createchain shorewall
    
    echo "Shorewall ${1}ed"

    logger "Shorewall ${1}ed"
}
################################################################################
# Rebuild the common chain                                                     #
################################################################################
refresh_firewall()
{
    echo "Refreshing Shorewall..."

    echo "Determining Zones and Interfaces..."

    determine_zones

    [ -z "$zones" ] && echo "ERROR: No Zones Defined" >&2 && exit 2

    determine_interfaces

    run_iptables -F common

    echo "Adding Common Rules"
    ############################################################################
    # Common rules in each chain
    #
    if [ -f /etc/shorewall/common ]; then
	. /etc/shorewall/common
    else
	. /etc/shorewall/common.def
    fi
    ###########################################################################
    # BROADCASTS
    #
    for zone in $zones; do
	eval interfaces=\"\$${zone}_interfaces\"

	[ -n "$interfaces" ] && drop_broadcasts `find_broadcast $zone`
    done

    echo "Shorewall Refreshed"

    logger "Shorewall Refreshed"
}
################################################################################
# Determine the value for a parameter that defaults to Yes		       #
################################################################################
added_param_value() # $1 = Parameter Name, $2 = Parameter value
{
    local val="$2"

    if [ -z "$val" ]; then
	echo "Yes"
    else case $val in
        Yes|yes)
	    echo "Yes"
	    ;;
	No|no)
	    echo ""
	    ;;
	*)
	    echo "Invalid value ($val) for $1" >&2
	    exit 2
	    ;;
	esac
    fi
}
################################################################################
# Give Usage Information						       #
################################################################################
usage() {
    echo "Usage: $0 {start|stop|reset|restart|status|refresh|clear]}"
    exit 1
}
################################################################################
# E X E C U T I O N    B E G I N S   H E R E				       #
################################################################################
[ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; }
[ $# -ne 1 ] && usage

PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
################################################################################
# Clear all configuration variables
#
version=
LOCKFILE=
STATEDIR=
ALLOWRELATED=
LOGRATE=
LOGBURST=
LOGPARMS=
NAT_ENABLED=
MANGLE_ENABLED=
stopping=
parentpid=$$

[ -f /etc/shorewall/version ] && version=`cat /etc/shorewall/version`

[ -f /etc/shorewall/shorewall.conf ] && . /etc/shorewall/shorewall.conf

[ -f /etc/shorewall/functions ] && . /etc/shorewall/functions || {
    echo "/etc/shorewall/functions does not exist!" >&2
    exit 2
}

[ -z "${STATEDIR}" ] && STATEDIR=/var/state/shorewall

ALLOWRELATED="`added_param_value ALLOWRELATED $ALLOWRELATED`"
NAT_ENABLED="`added_param_value NAT_ENABLED $NAT_ENABLED`"
MANGLE_ENABLED="`added_param_value MANGLE_ENABLED $MANGLE_ENABLED`"

if [ -n "${LOGRATE}${LOGBURST}" ]; then
    LOGPARMS="--match limit"
    [ -n "$LOGRATE" ]  && LOGPARMS="$LOGPARMS --limit $LOGRATE"
    [ -n "$LOGBURST" ] && LOGPARMS="$LOGPARMS --limit-burst $LOGBURST"
fi

if [ -n "$IP_FORWARDING" ]; then
    case "$IP_FORWARDING" in
        [Oo]n|[Oo]ff|[Kk]eep)
            ;;
        *)
	    echo "Invalid value ($IP_FORWARDING) for IP_FORWARDING" >&2
	    exit 2
    esac
else
    IP_FORWARDING=On
fi

command="$1"

case "$command" in
    stop)
	echo -n "Stopping Shorewall..."
	determine_zones
	stop_firewall
	[ -n "$LOCKFILE" ] && rm -f $LOCKFILE
	echo "done."
	;;
    start)
	if qt iptables -L shorewall -n ; then
	    [ -n "$LOCKFILE" ] && touch $LOCKFILE
	    echo "Shorewall Already Started"
	    exit 0;
	fi

	define_firewall "Start" && [ -n "$LOCKFILE" ] && touch $LOCKFILE
	;;
    restart)
	if qt iptables -L shorewall -n ; then
	    define_firewall "Restart"
	else
	    echo "Shorewall Not Currently Running"
	    define_firewall "Start"
	fi

	[ $? -eq 0 ] && [ -n "$LOCKFILE" ] && touch $LOCKFILE
	;;
    status)
	echo -e "Shorewall-$version Status at $HOSTNAME - `date`\\n"
	iptables -L -n -v
	;;
    reset)
	iptables -L -n -Z -v
	echo "Shorewall Counters Reset"
	logger "Shorewall Counters Reset"
	;;
    refresh)
	if ! qt iptables -L shorewall -n ; then
	    echo "Shorewall Not Started"
	    exit 2;
        fi
	refresh_firewall;
	;;
    clear)
	echo -n "Clearing Shorewall..."
	determine_zones
	clear_firewall
	[ -n "$LOCKFILE" ] && rm -f $LOCKFILE
	echo "done."
	;;
    *)
	usage
	;;
esac
