#!/bin/sh
#
#     The Shoreline Firewall (Shorewall) Packet Filtering Firewall Compiler - V3.2
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
#     (c) 1999,2000,2001,2002,2003,2004,2005,2006 - Tom Eastep (teastep@shorewall.net)
#
#     tcstart from tc4shorewall       Version 0.5
#     (c) 2005 Arne Bernin <arne@ucbering.de>
#     Modified by Tom Eastep for integration into the Shorewall distribution
#     published under GPL Version 2#
#
#	Complete documentation is available at http://shorewall.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:
#
#          compile check                        Verify the configuration files.
#	   compile compile <path name>          Compile into <path name>
#
# Mutual exclusion -- These functions are jackets for the mutual exclusion
#		      routines in $FUNCTIONS. They invoke
#		      the corresponding function in that file if the user did
#		      not specify "nolock" on the runline.
#

#
# Fatal error -- stops the firewall after issuing the error message
#
fatal_error() # $* = Error Message
{    echo "   ERROR: $@" >&2
    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE
    exit 2
}

#
# Fatal error during startup -- generate an error message and abend without
#				altering the state of the firewall
#
startup_error() # $* = Error Message
{
    echo "   ERROR: $@" >&2
    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE
    kill $$
    exit 2
}

#
# Write the passed args to $RESTOREBASE -- We need all of these varieties to support
#                                          extension scripts.
#
save_command()
{
    echo "${INDENT}${@}" >> $RESTOREBASE
}

run_and_save_command()
{
    echo "${INDENT}${@}" >> $RESTOREBASE
}

ensure_and_save_command()
{
    echo "${INDENT}${@}" >> $RESTOREBASE
}

save_command_unindented()
{
    echo "${@}" >> $RESTOREBASE
}

#
# Write a progress_message command to $RESTOREBASE
#
save_progress_message()
{
    echo                                    >> $RESTOREBASE
    echo "${INDENT}progress_message \"$@\"" >> $RESTOREBASE
    echo                                    >> $RESTOREBASE
}

save_progress_message_short()
{
    echo "${INDENT}progress_message \"$@\"" >> $RESTOREBASE
}

#
# Append a file in /var/lib/shorewall to $RESTOREBASE
#
append_file() # $1 = File Name
{
    save_command "cat > /var/lib/shorewall/$1 << __EOF__"
    cat $STATEDIR/$1 >> $RESTOREBASE
    save_command_unindented __EOF__
}

#
# Generate a command to run iptables
#
do_iptables() {
    save_command \$IPTABLES $@
}

#
# Generate an IPTABLES command. Include hacks to work around iptables limitations
#
run_iptables() {
    #
    # Purge the temporary files that we use to prevent duplicate '-m' specifications
    #
    [ -n "$BRIDGING" ]      && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
    [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange

    save_command "$IPTABLES_COMMAND $@"

}

#
# Version of 'run_iptables' that inserts white space after "!" in the arg list
#
run_iptables2() {
    save_command run_iptables $(fix_bang $@)
}

#
# Generate command to quietly run iptables
#
qt_iptables() {
    #
    # Purge the temporary files that we use to prevent duplicate '-m' specifications
    #
    [ -n "$BRIDGING" ]      && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
    [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange

    save_command qt \$IPTABLES $@
}

#
# Generate a command to run tc
#
run_tc() {
    save_command run_tc $@
}

#
# Add the implicit ACCEPT rules at the end of a rules file section
#
finish_chain_section() # $1 = canonical chain $2 = state list
{
    local policy policychain

    [ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state $2 -j ACCEPT

    if list_search RELATED $(separate_list $2) ; then
	if is_policy_chain $1 ; then
	    if eval test -n \"\$${1}_synparams\" ; then
		if [ $SECTION = DONE ]; then
		    eval policy=\$${1}_policy

		    case $policy in
			ACCEPT|CONTINUE|QUEUE)
			    run_iptables -A $1 -p tcp --syn -j @$1
			    ;;
		    esac
		else
		    run_iptables -A $1 -p tcp --syn -j @$1
		fi
	    fi
	else
	    eval policychain=\$${1}_policychain

	    if eval test -n \"\$${policychain}_synparams\" ; then
		run_iptables -A $1 -p tcp --syn -j @$policychain
	    fi
	fi
    fi
}

finish_section() # $1 = Section(s)
{
    local zone zone1 chain

    for zone in $ZONES $FW; do
	for zone1 in $ZONES $FW; do
	    chain=${zone}2${zone1}
	    if havechain $chain; then
		finish_chain_section $chain $1
	    fi
	done
    done
}

#
# 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 exists_${1} and set its value to Yes to indicate that the chain now
# exists.
#
createchain() # $1 = chain name, $2 = If "yes", do section-end processing
{
    local c=$(chain_base $1)

    run_iptables -N $1

    if [ $2 = yes ]; then
	case $SECTION in
	    NEW|DONE)
		finish_chain_section $1 ESTABLISHED,RELATED
		;;
	    RELATED)
		finish_chain_section $1 ESTABLISHED
		;;
	esac
    fi

    eval exists_${c}=Yes
}

createchain2() # $1 = chain name, $2 = If "yes", create default rules
{
    local c=$(chain_base $1)

    ensurechain $1

    if [ $2 = yes ]; then
	case $SECTION in
	    NEW|DONE)
		finish_chain_section $1 ESTABLISHED,RELATED
		;;
	    RELATED)
		finish_chain_section $1 ESTABLISHED
		;;
	esac

    fi

    eval exists_${c}=Yes
}

#
# Determine if a chain exists
#
# When we create a chain "x", we create a variable named exists_x 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
{
    local c=$(chain_base $1)

    eval test \"\$exists_${c}\" = Yes
}

#
# Ensure that a chain exists (create it if it doesn't)
#
ensurechain() # $1 = chain name
{
    havechain $1 || createchain $1 yes
}

ensurechain1() # $1 = chain name
{
    havechain $1 || createchain $1 no
}

#
# 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 $@
}

addrule2() # $1 = chain name, remainder of arguments specify the rule
{
    ensurechain $1
    run_iptables2 -A $@
}

#
# Query NetFilter about the existence of a filter chain
#
chain_exists() # $1 = chain name
{
    qt $IPTABLES -L $1 -n
}

#
# Create a mangle chain
#
# Create a variable exists_mangle_${1} and set its value to Yes to indicate that
# the chain now exists.
#
createmanglechain() # $1 = chain name
{
    run_iptables -t mangle -N $1

    eval exists_mangle_${1}=Yes
}

#
# Determine if a mangle chain exists
#
# When we create a chain "chain", we create a variable named exists_nat_chain
# and set its value to Yes. This function tests for the "exists_" variable
# corresponding to the passed chain having the value of "Yes".
#
havemanglechain() # $1 = name of chain
{
    eval test \"\$exists_mangle_${1}\" = Yes
}

#
# Ensure that a mangle chain exists (create it if it doesn't)
#
ensuremanglechain() # $1 = chain name
{
    havemanglechain $1 || createmanglechain $1
}

#
# Create a nat chain
#
# Create a variable exists_nat_${1} and set its value to Yes to indicate that
# the chain now exists.
#
createnatchain() # $1 = chain name
{
    run_iptables -t nat -N $1

    eval exists_nat_${1}=Yes
}

#
# Determine if a nat chain exists
#
# When we create a chain "chain", we create a variable named exists_nat_chain
# and set its value to Yes. This function tests for the "exists_" variable
# corresponding to the passed chain having the value of "Yes".
#
havenatchain() # $1 = name of chain
{
    eval test \"\$exists_nat_${1}\" = Yes
}

#
# Ensure that a nat chain exists (create it if it doesn't)
#
ensurenatchain() # $1 = chain name
{
    havenatchain $1 || createnatchain $1
}

#
# Add a rule to a nat chain creating the chain if necessary
#
addnatrule() # $1 = chain name, remainder of arguments specify the rule
{
    ensurenatchain $1
    run_iptables2 -t nat -A $@
}

#
# Create a rule to delete a chain if it exists
#
deletechain() # $1 = name of chain
{
    save_command "qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1"
}

#
# Determine if a chain is a policy chain
#
is_policy_chain() # $1 = name of chain
{
    eval test \"\$${1}_is_policy\" = Yes
}

#
# Set a standard chain's policy
#
setpolicy() # $1 = name of chain, $2 = policy
{
    run_iptables -P $1 $2
}

#
# Set a standard chain to enable established and related connections
#
setcontinue() # $1 = name of chain
{
    run_iptables -A $1 -m state --state ESTABLISHED,RELATED -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
}

#
# This function assumes that the TMP_DIR variable is set and that
# its value named an existing directory.
#
determine_zones()
{
    local zone parent parents rest new_zone_file= r

    merge_zone()
    {
	local z zones="$ZONES" merged=

	if [ -n "$parents" ]; then
	    ZONES=
	    for z in $zones; do
		if [ -z "$merged" ] && list_search $z $parents; then
		    ZONES="$ZONES $zone"
		    merged=Yes
		fi
		ZONES="$ZONES $z"
	    done
	else
	    ZONES="$ZONES $zone"
	fi
    }

    strip_file zones

    ZONES=
    IPV4_ZONES=
    IPSEC_ZONES=

    [ "$IPSECFILE" = zones ] && new_zone_file=Yes || test -n "${FW:=fw}"

    while read zone type rest; do
	expandv zone type

	case $zone in
	    *:*)
		parents=${zone#*:}
		zone=${zone%:*}
		[ -n "$zone" ] || startup_error "Invalid nested zone syntax: :$parents"
		parents=$(separate_list $parents)
		;;
	    *)
		parents=
		;;
	esac

	for parent in $parents; do
	    [ "$parent" = "$FW" ] && startup_error "Sub-zones of the firewall zone are not allowed"
	    list_search $parent $ZONES || startup_error "Parent zone not defined: $parent"
	done

	[ ${#zone} -gt 5 ] && startup_error "Zone name longer than 5 characters: $zone"

	case "$zone" in
	    [0-9*])
                startup_error "Illegal zone name \"$zone\" in zones file"
		;;
            all|none)
	        startup_error "Reserved zone name \"$zone\" in zones file"
		;;
        esac

	if [ -n "$new_zone_file" ]; then
	    case ${type:=ipv4} in
		ipv4|IPv4|IPV4|plain|-)
		    list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once"
		    merge_zone
		    IPV4_ZONES="$IPV4_ZONES $zone"
		    ;;
		ipsec|IPSEC|ipsec4|IPSEC4)
		    list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once"
		    [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match"
		    eval ${zone}_is_ipsec=Yes
		    eval ${zone}_is_complex=Yes
		    merge_zone
		    IPSEC_ZONES="$IPSEC_ZONES $zone"
		    ;;
		firewall)
		    [ -n "$FW" ] && startup_error "Only one firewall zone may be defined"
		    list_search $zone $ZONES && startup_error "Zone $zone is defined more than once"
		    [ -n "$parents" ] && startup_error "The firewall zone may not be nested"
		    for r in $rest; do
			[ "x$r" = x- ] || startup_error "OPTIONS not allowed on the firewall zone"
		    done
		    FW=$zone
		    ;;
		*)
		    startup_error "Invalid Zone Type: $type"
		    ;;
	    esac

	    eval ${zone}_type=$type
	else
	    list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once"
	    ZONES="$ZONES $zone"
	    IPV4_ZONES="$IPV4_ZONES $zone"
	    eval ${zone}_type=ipv4
	fi
    done < $TMP_DIR/zones

    [ -z "$ZONES" ] && startup_error "No ipv4 or ipsec Zones Defined"

    [ -z "$FW" ] && startup_error "No Firewall Zone Defined"
}

#
# Find interfaces to a given zone
#
# Search the variables representing the contents of the interfaces file and
# for each record matching the passed ZONE, echo the expanded contents of
# the "INTERFACE" column
#
find_interfaces() # $1 = interface zone
{
    local zne=$1
    local z
    local interface

    for interface in $ALL_INTERFACES; do
	eval z=\$$(chain_base $interface)_zone
	[ "x${z}" = x${zne} ] && echo $interface
    done
}

#
# Forward Chain for an interface
#
forward_chain() # $1 = interface
{
   echo $(chain_base $1)_fwd
}

#
# Input Chain for an interface
#
input_chain() # $1 = interface
{
   echo $(chain_base $1)_in
}

#
# Output Chain for an interface
#
output_chain() # $1 = interface
{
   echo $(chain_base $1)_out
}

#
# Masquerade Chain for an interface
#
masq_chain() # $1 = interface
{
   echo $(chain_base $1)_masq
}

#
# MAC Verification Chain for an interface
#
mac_chain() # $1 = interface
{
   echo $(chain_base $1)_mac
}

macrecent_target() # $1 - interface
{
    [ -n "$MACLIST_TTL" ] && echo $(chain_base $1)_rec || echo RETURN
}

#
# Functions for creating dynamic zone rules
#
dynamic_fwd() # $1 = interface
{
   echo $(chain_base $1)_dynf
}

dynamic_in() # $1 = interface
{
   echo $(chain_base $1)_dyni
}

dynamic_out() # $1 = interface
{
   echo $(chain_base $1)_dyno
}

dynamic_chains() #$1 = interface
{
   local c=$(chain_base $1)

   echo ${c}_dyni ${c}_dynf ${c}_dyno
}

#
# DNAT Chain from a zone
#
dnat_chain() # $1 = zone
{
   echo ${1}_dnat
}

#
# SNAT Chain to an interface
#
snat_chain() # $1 = interface
{
   echo $(chain_base $1)_snat
}

#
# ECN Chain to an interface
#
ecn_chain() # $1 = interface
{
   echo $(chain_base $1)_ecn
}

#
# First chains for an interface
#
first_chains() #$1 = interface
{
   local c=$(chain_base $1)

   echo ${c}_fwd ${c}_in
}

#
# Horrible hack to work around an iptables limitation
#
iprange_echo()
{
    if [ -f $TMP_DIR/iprange ]; then
	echo $@
    else
	echo "-m iprange $@"
	> $TMP_DIR/iprange
    fi
}

#
# Get set flags (ipsets).
#
get_set_flags() # $1 = set name and optional [levels], $2 = src or dst
{
    local temp setname=$1 options=$2

    [ -n "$IPSET_MATCH" ] || fatal_error "Your kernel and/or iptables does not include ipset match: $1"

    case $1 in
	*\[[1-6]\])
            temp=${1#*\[}
	    temp=${temp%\]}
	    setname=${1%\[*}
	    while [ $temp -gt 1 ]; do
	       options="$options,$2"
	       temp=$(($temp - 1))
	    done
	    ;;
	*\[*\])
	    options=${1#*\[}
	    options=${options%\]}
	    setname=${1%\[*}
	    ;;
	*)
	    ;;
    esac

    echo "--set ${setname#+} $options"
}

#
# Horrible hack to work around an iptables limitation
#
physdev_echo()
{
    if [ -f $TMP_DIR/physdev ]; then
	echo $@
    else
	echo -m physdev $@
	> $TMP_DIR/physdev
    fi
}

#
# We allow hosts to be specified by IP address or by physdev. These two functions
# are used to produce the proper match in a netfilter rule.
#
match_source_hosts()
{
    if [ -n "$BRIDGING" ]; then
	case $1 in
	    *:*)
		physdev_echo "--physdev-in ${1%:*} $(source_ip_range ${1#*:})"
		;;
	    *.*.*.*|+*|!+*)
		echo $(source_ip_range $1)
		;;
	    *)
		physdev_echo "--physdev-in $1"
		;;
	esac
    else
	echo $(source_ip_range $1)
    fi
}

match_dest_hosts()
{
    if [ -n "$BRIDGING" ]; then
	case $1 in
	    *:*)
		physdev_echo "--physdev-out ${1%:*} $(dest_ip_range ${1#*:})"
		;;
	    *.*.*.*|+*|!+*)
		echo $(dest_ip_range $1)
		;;
	    *)
		physdev_echo "--physdev-out $1"
		;;
	esac
    else
	echo $(dest_ip_range $1)
    fi
}

#
# Similarly, the source or destination in a rule can be qualified by a device name. If
# the device is defined in /etc/shorewall/interfaces then a normal interface match is
# generated (-i or -o); otherwise, a physdev match is generated.
#-------------------------------------------------------------------------------------
#
# loosely match the passed interface with those in /etc/shorewall/interfaces.
#
known_interface() # $1 = interface name
{
    local iface

    for iface in $ALL_INTERFACES ; do
	if if_match $iface $1 ; then
	    return 0
	fi
    done

    return 1
}

known_port() # $1 = port name
{
    local port

    for port in $ALL_PORTS ; do
	if if_match $port $1 ; then
	    return 0
	fi
    done

    return 1
}

match_source_dev()
{
    if [ -n "$BRIDGING" ]; then
	known_port $1 && physdev_echo "--physdev-in $1" || echo -i $1
    else
	echo -i $1
    fi
}

match_dest_dev()
{
    if [ -n "$BRIDGING" ]; then
	known_port $1 && physdev_echo "--physdev-out $1" || echo -o $1
    else
	echo -o $1
    fi
}

verify_interface()
{
    known_interface $1 || { [ -n "$BRIDGING" ] && known_port $1 ; }
}

#
# Determine if communication to/from a host is encrypted using IPSEC
#
is_ipsec_host() # $1 = zone, $2 = host
{
    eval local is_ipsec=\$${1}_is_ipsec
    eval local hosts=\"\$${1}_ipsec_hosts\"

    test -n "$is_ipsec" || list_search $2 $hosts
}

#
# Generate a match for decrypted packets
#
match_ipsec_in() # $1 = zone, $2 = host
{
    if is_ipsec_host $1 $2 ; then
	eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_in_options\"
	echo "-m policy --pol ipsec --dir in $options"
    elif [ -n "$POLICY_MATCH" ]; then
	echo "-m policy --pol none --dir in"
    fi
}

#
# Generate a match for packets that will be encrypted
#
match_ipsec_out() # $1 = zone, $2 = host
{
    if is_ipsec_host $1 $2 ; then
	eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_out_options\"
	echo "-m policy --pol ipsec --dir out $options"
    elif [ -n "$POLICY_MATCH" ]; then
	echo "-m policy --pol none --dir out"
    fi
}

#
# Jacket for ip_range() that takes care of iprange match
#

firewall_ip_range() # $1 = IP address or range
{
    [ -n "$IPRANGE_MATCH" ] && echo $1 || ip_range $1
}

#
#
# Find hosts in a given zone
#
# Read hosts file and for each record matching the passed ZONE,
# echo the expanded contents of the "HOST(S)" column
#
find_hosts() # $1 = host zone
{
    local hosts interface address addresses

    while read z hosts options; do
	if [ "x$(expand $z)" = "x$1" ]; then
	    expandv hosts
	    interface=${hosts%%:*}
	    addresses=${hosts#*:}
	    for address in $(separate_list $addresses); do
		echo $interface:$address
	    done
	fi
    done < $TMP_DIR/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 if an interface has a given option
#
interface_has_option() # $1 = interface, #2 = option
{
    local options

    eval options=\$$(chain_base $1)_options

    list_search $2 $options
}

#
# 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

	eval interfaces=\$${zone}_interfaces

	for interface in $interfaces; do
	    if interface_has_option $interface detectnets; then
		networks=$(get_routed_networks $interface)
	    else
		networks=0.0.0.0/0
	    fi

	    for network in $networks; do
		if [ -z "$hosts" ]; then
		    hosts=$interface:$network
		else
		    hosts="$hosts $interface:$network"
		fi

		if interface_has_option $interface routeback; then
		    eval ${zone}_routeback=\"$interface:$network \$${zone}_routeback\"
		fi
	    done
	done

	interfaces=

	for host in $hosts; do
	    interface=${host%:*}
	    if list_search $interface $interfaces; then
		list_search $interface:0.0.0.0/0 $hosts && \
		    startup_error "Invalid zone definition for zone $zone"
		list_search $interface:0/0 $hosts && \
		    startup_error "Invalid zone definition for zone $zone"
		eval ${zone}_is_complex=Yes
	    else
		if [ -z "$interfaces" ]; then
		    interfaces=$interface
		else
		    interfaces="$interfaces $interface"
		fi
	    fi
	done

	eval ${zone}_interfaces="\$interfaces"
	eval ${zone}_hosts="\$hosts"

	if [ -n "$hosts" ]; then
	    [ $VERBOSE -ge 1 ] && display_list "$zone Zone:" $hosts
	else
	    error_message "WARNING: Zone $zone is empty"
	fi
    done
}

#
# Ensure that the passed zone is defined in the zones file or is the firewall
#
validate_zone() # $1 = zone
{
    list_search $1 $ZONES $FW
}
#
# Ensure that the passed zone is defined in the zones file.
#
validate_zone1() # $1 = zone
{
    list_search $1 $ZONES
}

#
# Validate the zone names and options in the interfaces file
#
validate_interfaces_file() {
    local wildcard
    local found_obsolete_option=
    local z interface networks options r iface option

    while read z interface networks options; do
	expandv z interface networks options
	r="$z $interface $networks $options"

	[ "x$z" = "x-" ] && z=

	if [ -n "$z" ]; then
	    validate_zone $z || startup_error "Invalid zone ($z) in record \"$r\""
	fi

	list_search $interface $ALL_INTERFACES && \
	    startup_error "Duplicate Interface $interface"

	wildcard=

	case $interface in
	    *:*|+)
		startup_error "Invalid Interface Name: $interface"
		;;
	    *+)
		wildcard=Yes
		;;
	esac

	ALL_INTERFACES="$ALL_INTERFACES $interface"
	options=$(separate_list $options)
	iface=$(chain_base $interface)

	eval ${iface}_broadcast="$networks"
	eval ${iface}_zone="$z"
	eval ${iface}_options=\"$options\"

	for option in $options; do
	    case $option in
		-)
		    ;;
		dhcp|tcpflags|arp_filter|routefilter|maclist|logmartians|sourceroute|blacklist|proxyarp|nosmurfs|upnp|-)
		    ;;
		norfc1918)
		    cat >> $RESTOREBASE << __EOF__

${INDENT}addr=\$(ip -f inet addr show $interface 2> /dev/null | grep inet | head -n1)
${INDENT}if [ -n "\$addr" ]; then
${INDENT}    addr=\$(echo \$addr | sed 's/inet //;s/\/.*//;s/ peer.*//')
${INDENT}    for network in 10.0.0.0/8 176.16.0.0/12 192.168.0.0/16; do
${INDENT}        if in_network \$addr \$network; then
${INDENT}            fatal_error "The 'norfc1918' option has been specified on an interface with an RFC 1918 address. Interface:$interface"
${INDENT}        fi
${INDENT}    done
${INDENT}fi

__EOF__
		    ;;
		arp_ignore=*)
		    eval ${iface}_arp_ignore=${option#*=}
		    ;;
		arp_ignore)
		    eval ${iface}_arp_ignore=1
		    ;;
		detectnets)
		    [ -n "$wildcard" ] && \
			startup_error "The \"detectnets\" option may not be used with a wild-card interface"
		    [ -n $EXPORT ] && \
			startup_error "'detectnets' not permitted with the -e run-line option"
		    ;;
		routeback)
		    [ -n "$z" ] || startup_error "The routeback option may not be specified on a multi-zone interface"
		    ;;
		*)
		    error_message "WARNING: Invalid option ($option) in record \"$r\""
		    ;;
	    esac
	done
    done < $TMP_DIR/interfaces

    [ -z "$ALL_INTERFACES" ] && startup_error "No Interfaces Defined"
}

#
# Check that a mark value or mask is less that 256
#
verify_mark() # $1 = value to test
{
    verify_mark1()
    {
	[ $1 -lt 256 ]
    }

    verify_mark2()
    {
	verify_mark1 $1 2> /dev/null
    }

    verify_mark2 $1 || fatal_error "Invalid Mark or Mask value: $1"
}

#
# Process the providers file
#
setup_providers()
{
    local table number mark duplicate interface gateway options provider address copy route loose addresses rulenum pref echobin=$(mywhich echo) balance

    copy_table() {
	    cat >> $RESTOREBASE << __EOF__
${INDENT}    ip route show table $duplicate | while read net route; do
${INDENT}        case \$net in
${INDENT}            default|nexthop)
${INDENT}                ;;
${INDENT}            *)
${INDENT}                run_ip route add table $number \$net \$route"
${INDENT}                ;;
${INDENT}        esac
${INDENT}    done
__EOF__
    }

    copy_and_edit_table() {
	cat >> $RESTOREBASE << __EOF__
${INDENT}    ip route show table $duplicate | while read net route; do
${INDENT}        case \$net in
${INDENT}            default|nexthop)
${INDENT}                ;;
${INDENT}            *)
${INDENT}                case \$(find_device \$route) in
${INDENT}                    `echo $copy\) | sed 's/ /|/g'`
${INDENT}                         run_ip route add table $number \$net \$route
${INDENT}                         ;;
${INDENT}                esac
${INDENT}                ;;
${INDENT}        esac
${INDENT}    done

__EOF__
    }

    add_a_provider() {
	local t n iface option

	[ -n "$MANGLE_ENABLED" ] || fatal_error "Providers require mangle support in your kernel and iptables"

	for t in $PROVIDERS; do
	    if [ "$t" = "$table" ]; then
		fatal_error "Duplicate Provider: $table, provider: \"$provider\""
	    fi

	    eval n=\$${t}_number

	    if [ $n -eq $number ]; then
		fatal_error "Duplicate Provider number: $number, provider: \"$provider\""
	    fi
	done

	eval ${table}_number=$number

	save_command "    qt ip route flush table $number"

	if [ "x${duplicate:=-}" != x- ]; then
	    if [ "x${copy:=-}" != "x-" ]; then
		if [ "x${copy}" = xnone ]; then
		    copy=$interface
		else
		    copy="$interface $(separate_list $copy)"
		fi
		copy_and_edit_table
	    else
		copy_table
	    fi
	fi

	if [ "x$gateway" = xdetect ] ; then
	    cat >> $RESTOREBASE << __EOF__
${INDENT}    gateway=\$(detect_gateway $interface)

${INDENT}    if [ -n "\$gateway" ]; then
${INDENT}        run_ip route replace \$gateway src \$(find_first_interface_address $interface) dev $interface table $number
${INDENT}        run_ip route add default via \$gateway dev $interface table $number
${INDENT}    else
${INDENT}        fatal_error "Unable to detect the gateway through interface $interface"
${INDENT}    fi

__EOF__
	fi

	if [ x${mark} != x- ]; then
	    verify_mark $mark

	    eval ${table}_mark=$mark

	    save_command "    qt ip rule del fwmark $mark"
	    save_command "    run_ip rule add fwmark $mark pref $((10000 + $mark)) table $number"
	fi

	loose=

	for option in $(separate_list $options); do
	    case $option in
		-)
		    ;;
		track)
		    list_search $interface $ROUTEMARK_INTERFACES && \
			fatal_error "Interface $interface is tracked through an earlier provider"
		    iface=$(chain_base $interface)
		    [ x${mark} = x- ] && fatal_error "The 'track' option requires a numeric value in the MARK column - Provider \"$provider\""
		    eval ${iface}_routemark=$mark
		    ROUTEMARK_INTERFACES="$ROUTEMARK_INTERFACES $interface"
		    ;;
		balance=*)
		    balance=yes
		    save_command "    DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop via \$gateway dev $interface weight ${option#*=}\""
		    ;;
		balance)
		    balance=yes
		    save_command "    DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop via \$gateway dev $interface weight 1\""
		    ;;
		loose)
		    loose=Yes
		    ;;
		*)
		    error_message "   WARNING: Invalid option ($option) ignored in provider \"$provider\""
		    ;;
	    esac
	done

	rulenum=0

	if [ -z "$loose" ]; then
	    cat >> $RESTOREBASE << __EOF__

${INDENT}    rulenum=0

${INDENT}    find_interface_addresses $interface | while read address; do
${INDENT}        qt ip rule del from \$address
${INDENT}        pref=\$((20000 + \$rulenum * 1000 + $number ))
${INDENT}        rulenum=\$((\$rulenum + 1))
${INDENT}        run_ip rule add from \$address pref \$pref table $number
${INDENT}    done

__EOF__
	else
	    cat >> $RESTOREBASE << __EOF__

${INDENT}    find_interface_addresses $interface | while read address; do
${INDENT}        qt ip rule del from \$address
${INDENT}    done

__EOF__
	fi
    }

    strip_file providers $1

    if [ -s $TMP_DIR/providers ]; then
	DEFAULT_ROUTE=
	balance=

	progress_message2 "$DOING $1..."
	save_progress_message "Adding Providers..."
	save_command "if [ -z \"\$NOROUTES\" ]; then"
	save_command "    DEFAULT_ROUTE="

	while read table number mark duplicate interface gateway options copy; do
	    expandv table number mark duplicate interface gateway options copy
	    provider="$table $number $mark $duplicate $interface $gateway $options $copy"
	    add_a_provider
	    PROVIDERS="$PROVIDERS $table"
	    progress_message "   Provider $provider $DONE"
	done < $TMP_DIR/providers

	if [ -n "$PROVIDERS" ]; then
	    if [ -n "$balance" ]; then
		save_command "    run_ip route replace default scope global \$DEFAULT_ROUTE"
		save_command "    progress_message Default route \$DEFAULT_ROUTE Added"
	    fi

	    cat >> $RESTOREBASE << __EOF__
${INDENT}    		cat > /etc/iproute2/rt_tables <<EOF
#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
EOF
__EOF__
	    for table in $PROVIDERS; do
		eval number=\$${table}_number
		cat >> $RESTOREBASE << __EOF__
${INDENT}    \${echobin:-echo} -e "$number\t$table" >>  /etc/iproute2/rt_tables
__EOF__
	    done
	fi

	save_command "    run_ip route flush cache"
	save_command "fi"
	save_command ""
    fi
}

#
# Validate the zone names and options in the hosts file
#
validate_hosts_file() {
   local z hosts options r interface host option port ports

   check_bridge_port()
   {
       list_search $1 $ports || ports="$ports $1"
       list_search ${interface}:${1} $zports || zports="$zports ${interface}:${1}"
       list_search $1 $ALL_PORTS || ALL_PORTS="$ALL_PORTS $1"
   }

   while read z hosts options; do
       expandv z hosts options
       r="$z $hosts $options"
       validate_zone1 $z || startup_error "Invalid zone ($z) in record \"$r\""

       case $hosts in
	   *:*)

	       interface=${hosts%%:*}
	       iface=$(chain_base $interface)

	       list_search  $interface $ALL_INTERFACES || \
		   startup_error "Unknown interface ($interface) in record \"$r\""

	       hosts=${hosts#*:}
	       ;;
	   *)
	       startup_error "Invalid HOST(S) column contents: $hosts"
	       ;;
       esac

       ports=\$${iface}
       eval zports=\$${z}_ports

       for host in $(separate_list $hosts); do
	   if [ -n "$BRIDGING" ]; then
	       case $host in
		   *:*)
		       known_interface ${host%:*} && \
			   startup_error "Bridged interfaces may not be defined in /etc/shorewall/interfaces: $host"
		       check_bridge_port ${host%%:*}
		       ;;
		   *.*.*.*)
		       ;;
		   +*)
		       eval ${z}_is_complex=Yes
		       ;;
		   *)
		       known_interface $host && \
			   startup_error "Bridged interfaces may not be defined in /etc/shorewall/interfaces: $host"
		       check_bridge_port $host
		       ;;
	       esac
	   else
	       case $host in
		   +*)
		       eval ${z}_is_complex=Yes
		       ;;
	       esac
	   fi

	   for option in $(separate_list $options) ; do
	       case $option in
		   norfc1918|blacklist|maclist|tcpflags|nosmurfs|-)
		       ;;
		   ipsec)
		       [ -n "$POLICY_MATCH" ] || \
			   startup_error "Your kernel and/or iptables does not support policy match: ipsec"
		       eval ${z}_ipsec_hosts=\"\$${z}_ipsec_hosts $interface:$host\"
		       eval ${z}_is_complex=Yes
		       ;;
		   routeback)
		       [ -z "$ports" ] && \
			   eval ${z}_routeback=\"$interface:$host \$${z}_routeback\"
		       ;;
		   *)
		       error_message "WARNING: Invalid option ($option) in record \"$r\""
		       ;;
	       esac
	   done
       done

       [ -n "$ports" ]  && eval ${z}_ports=\"$zports\"

   done < $TMP_DIR/hosts

   [ -n "$ALL_PORTS" ] && progress_message2 "   Bridge ports are: $ALL_PORTS"
}

#
# Format a match by the passed MAC address
# The passed address begins with "~" and uses "-" as a separator between bytes
# Example: ~01-02-03-04-05-06
#
mac_match() # $1 = MAC address formated as described above
{
    echo "--match mac --mac-source $(echo $1 | sed 's/~//;s/-/:/g')"
}

#
# validate the policy file
#
validate_policy()
{
    local clientwild
    local serverwild
    local zone
    local zone1
    local pc
    local chain
    local policy
    local loglevel
    local synparams

    print_policy() # $1 = source zone, $2 = destination zone
    {
	[ $1 = $2 ]  || \
	[ $1 = all ] || \
	[ $2 = all ] || \
	progress_message "   Policy for $1 to $2 is $policy using chain $chain"
    }

    ALL_POLICY_CHAINS=

    for zone in $ZONES $FW; do
	chain=${zone}2${zone}
	eval ${chain}_is_policy=Yes
	eval ${chain}_is_optional=Yes
	eval ${chain}_policy=ACCEPT
	eval ${chain}_policychain=$chain
	ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain"
    done

    strip_file policy

    while read client server policy loglevel synparams; do
	expandv client server policy loglevel synparams

	clientwild=
	serverwild=

	case "$client" in
	all|ALL)
	    clientwild=Yes
	    ;;
	*)
	    if ! validate_zone $client; then
		startup_error "Undefined zone $client"
	    fi
	esac

	case "$server" in
	all|ALL)
	    serverwild=Yes
	    ;;
	*)
	    if ! validate_zone $server; then
		startup_error "Undefined zone $server"
	    fi
	esac

	case $policy in
	ACCEPT|REJECT|DROP|CONTINUE|QUEUE)
	    ;;
	NONE)
	    [ "$client" = "$FW" -o "$server" = "$FW" ] && \
		startup_error " $client $server $policy $loglevel $synparams: NONE policy not allowed to/from the $FW zone"

	    [ -n "$clientwild" -o -n "$serverwild" ] && \
		startup_error " $client $server $policy $loglevel $synparams: NONE policy not allowed with \"all\""
	    ;;
	*)
	    startup_error "Invalid policy $policy"
	    ;;
	esac

	chain=${client}2${server}

	if is_policy_chain $chain ; then
	    if eval test \$${chain}_is_optional = Yes ; then
		eval ${chain}_is_optional=
	    else
		startup_error "Duplicate policy: $client $server $policy"
	    fi
	fi

	[ "x$loglevel"   = "x-" ] && loglevel=
	[ "x$synparams"  = "x-" ] && synparams=

	[ $policy = NONE ] || ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain"

	eval ${chain}_is_policy=Yes
	eval ${chain}_policy=$policy
	eval ${chain}_loglevel=$loglevel
	eval ${chain}_synparams=$synparams

	if [ -n "${clientwild}" ]; then
	    if [ -n "${serverwild}" ]; then
		for zone in $ZONES $FW all; do
		    for zone1 in $ZONES $FW all; do
			eval pc=\$${zone}2${zone1}_policychain

			if [ -z "$pc" ]; then
			    eval ${zone}2${zone1}_policychain=$chain
			    eval ${zone}2${zone1}_policy=$policy
			    print_policy $zone $zone1
			fi
		    done
		done
	    else
		for zone in $ZONES $FW all; do
		    eval pc=\$${zone}2${server}_policychain

		    if [ -z "$pc" ]; then
			eval ${zone}2${server}_policychain=$chain
			eval ${zone}2${server}_policy=$policy
			print_policy $zone $server
		    fi
		done
	    fi
	elif [ -n "$serverwild" ]; then
	    for zone in $ZONES $FW all; do
		eval pc=\$${client}2${zone}_policychain

		    if [ -z "$pc" ]; then
			eval ${client}2${zone}_policychain=$chain
			eval ${client}2${zone}_policy=$policy
			print_policy $client $zone
		    fi
	    done
	else
	    eval ${chain}_policychain=${chain}
	    print_policy $client $server
	fi

    done < $TMP_DIR/policy
}

#
# Find broadcast addresses -- if we are compiling a script and 'detect' is specified for an interface
#                             the function returns nothing for that interface
#
find_broadcasts() {
    for interface in $ALL_INTERFACES; do
	eval bcast=\$$(chain_base $interface)_broadcast
	if [ "x$bcast" != "xdetect" -a "x${bcast}" != "x-" ]; then
	    echo $(separate_list $bcast)
	fi
    done
}

#
# Find interfaces with BROADCAST=detect -- Only returns information if we are compiling a script
#
find_bcastdetect_interfaces() {
    for interface in $ALL_INTERFACES; do
	eval bcast=\$$(chain_base $interface)_broadcast
	[ "x$bcast" = "xdetect" ] && echo $interface
    done
}

#
# Find interfaces that have the passed option specified
#
find_interfaces_by_option() # $1 = option
{
    for interface in $ALL_INTERFACES; do
	eval options=\$$(chain_base $interface)_options
	list_search $1 $options && echo $interface
    done
}

#
# This slightly slower version is used to find both the option and option followed
# by equal sign ("=") and a value
#
find_interfaces_by_option1() # $1 = option
{
    local options option

    for interface in $ALL_INTERFACES; do
	eval options=\$$(chain_base $interface)_options
	for option in $options; do
	    if [ "${option%=*}" = "$1" ]; then
		echo $interface
		break
	    fi
	done
    done
}

#
# Find hosts with the passed option
#
find_hosts_by_option() # $1 = option
{
    local ignore hosts interface address addresses options ipsec= list

    while read ignore hosts options; do
	expandv options
	list=$(separate_list $options)
	if list_search $1 $list; then
	    list_search ipsec $list && ipsec=ipsec || ipsec=none
	    expandv hosts
	    interface=${hosts%%:*}
	    addresses=${hosts#*:}
	    for address in $(separate_list $addresses); do
		echo ${ipsec}^$interface:$address
	    done
	fi
    done < $TMP_DIR/hosts

    for interface in $ALL_INTERFACES; do
	interface_has_option $interface $1 && \
	    echo none^${interface}:0.0.0.0/0
    done
}

#
# Flush and delete all user-defined chains in the filter table
#
deleteallchains() {
    run_iptables -F
    run_iptables -X
}

#
# Set /proc/sys/net/ipv4/ip_forward based on $IP_FORWARDING
#
setup_forwarding() {

    save_progress_message "Setting up IP Forwarding..."

    case "$IP_FORWARDING" in
    [Oo][Nn])
	save_command "echo 1 > /proc/sys/net/ipv4/ip_forward"
	progress_message2 "IP Forwarding Enabled"
	;;
    [Oo][Ff][Ff])
	save_command "echo 0 > /proc/sys/net/ipv4/ip_forward"
	progress_message2 "IP Forwarding Disabled!"
	;;
    esac
}

#
# Process the routestopped file either adding or deleting rules
#
process_routestopped() # $1 = command
{
    local hosts= interface host host1 options networks source= dest= matched

    while read interface host options; do
	expandv interface host options
	[ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0
	for h in $(separate_list $host); do
	    hosts="$hosts $interface:$h"
	done

	routeback=

	if [ -n "$options" ]; then
	    for option in $(separate_list $options); do
		case $option in
		    routeback)
			if [ -n "$routeback" ]; then
			    error_message "WARNING: Duplicate routestopped option ignored: routeback"
			else
			    routeback=Yes
			    for h in $(separate_list $host); do
				run_iptables $1 FORWARD -i $interface -o $interface $(both_ip_ranges $h $h) -j ACCEPT
			    done
			fi
			;;
		    source)
			for h in $(separate_list $host); do
			    source="$source $interface:$h"
			done
			;;
		    dest)
			for h in $(separate_list $host); do
			    dest="$dest $interface:$h"
			done
			;;
		    critical)
			;;
		    *)
			error_message "WARNING: Unknown routestopped option ignored: $option"
			;;
		esac
	    done
	fi

    done < $TMP_DIR/routestopped


    for host in $hosts; do
	interface=${host%:*}
	networks=${host#*:}
	run_iptables $1 INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	[ -z "$ADMINISABSENTMINDED" ] && \
	    run_iptables $1 OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT

	matched=

	if list_search $host $source ; then
	    run_iptables $1 FORWARD -i $interface $(source_ip_range $networks) -j ACCEPT
	    matched=Yes
	fi

	if list_search $host $dest ; then
	    run_iptables $1 FORWARD -o $interface $(dest_ip_range $networks) -j ACCEPT
	    matched=Yes
	fi

	if [ -z "$matched" ]; then
	    for host1 in $hosts; do
		[ "$host" != "$host1" ] && run_iptables $1 FORWARD -i $interface -o ${host1%:*} $(both_ip_ranges $networks ${host1#*:}) -j ACCEPT
	    done
	fi
    done
}

process_criticalhosts()
{
    local hosts= interface host h options networks criticalhosts=

    [ -f $TMP_DIR/routestopped ] || strip_file routestopped

    while read interface host options; do
	expandv interface host options

	[ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0 || host=$(separate_list $host)

	if [ -n "$options" ]; then
	    for option in $(separate_list $options); do
		case $option in
		    routeback|source|dest)
			;;
		    critical)
			for h in $host; do
			    criticalhosts="$criticalhosts $interface:$h"
			done
			;;
		    *)
			error_message "WARNING: Unknown routestopped option ignored: $option"
			;;
		esac
	    done
	fi
    done < $TMP_DIR/routestopped

    if [ -n "$criticalhosts" ]; then
	CRITICALHOSTS=$criticalhosts
	progress_message "Critical Hosts are:$CRITICALHOSTS"
    fi

}

#
# For each entry in the CRITICALHOSTS global list, add INPUT and OUTPUT rules to
# enable traffic to/from those hosts.
#
enable_critical_hosts()
{
    for host in $CRITICALHOSTS; do
	interface=${host%:*}
	networks=${host#*:}
	do_iptables -A INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
    done
}

#
# For each entry in the CRITICALHOSTS global list, delete the INPUT and OUTPUT rules that
# enable traffic to/from those hosts.
#
disable_critical_hosts()
{
    for host in $CRITICALHOSTS; do
	interface=${host%:*}
	networks=${host#*:}
	do_iptables -D INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	do_iptables -D OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
    done
}

#
# Set up ipsec tunnels
#
setup_tunnels() # $1 = name of tunnels file
{
    local inchain
    local outchain


    setup_one_ipsec() # $1 = gateway $2 = Tunnel Kind $3 = gateway zones
    {
    	local kind=$2 noah=

	case $kind in
	    *:*)
	    	noah=${kind#*:}
		[ $noah = noah -o $noah = NOAH ] || fatal_error "Invalid IPSEC modifier $noah in tunnel \"$tunnel\""
		kind=${kind%:*}
		;;
	esac

	[ $kind = IPSEC ] && kind=ipsec

	options="-m state --state NEW -j ACCEPT"
	addrule2 $inchain	  -p 50	 $(source_ip_range $1) -j ACCEPT
	addrule2 $outchain	  -p 50	 $(dest_ip_range $1)   -j ACCEPT

	if [ -z "$noah" ]; then
	    run_iptables -A $inchain  -p 51 $(source_ip_range $1) -j ACCEPT
	    run_iptables -A $outchain -p 51 $(dest_ip_range $1)   -j ACCEPT
	fi

	run_iptables -A $outchain -p udp $(dest_ip_range $1) --dport 500 $options

	if [ $kind = ipsec ]; then
	    run_iptables -A $inchain  -p udp $(source_ip_range $1) --dport 500 $options
	else
	    run_iptables -A $inchain  -p udp $(source_ip_range $1) --dport 500 $options
	    run_iptables -A $inchain  -p udp $(source_ip_range $1) --dport 4500 $options
	fi

	for z in $(separate_list $3); do
	    if validate_zone $z; then
		addrule ${FW}2${z} -p udp --dport 500 $options
		if [ $kind = ipsec ]; then
		    addrule ${z}2${FW} -p udp --dport 500 $options
		else
		    addrule ${z}2${FW} -p udp --dport 500 $options
		    addrule ${z}2${FW} -p udp --dport 4500 $options
		fi
	    else
		fatal_error "Invalid gateway zone ($z) -- Tunnel \"$tunnel\""
	    fi
	done

	progress_message "   IPSEC tunnel to $gateway defined."
    }

    setup_one_other() # $1 = TYPE, $2 = gateway, $3 = protocol
    {
	addrule2 $inchain  -p $3 $(source_ip_range $2) -j ACCEPT
	addrule2 $outchain -p $3 $(dest_ip_range $2) -j ACCEPT

	progress_message "   $1 tunnel to $2 defined."
    }

    setup_pptp_client() # $1 = gateway
    {
	addrule2 $outchain -p 47               $(dest_ip_range $1)   -j ACCEPT
	addrule2 $inchain  -p 47               $(source_ip_range $1) -j ACCEPT
	addrule2 $outchain -p tcp --dport 1723 $(dest_ip_range $1)   -j ACCEPT

	progress_message "   PPTP tunnel to $1 defined."
    }

    setup_pptp_server() # $1 = gateway
    {
	addrule2 $inchain  -p 47               $(source_ip_range $1) -j ACCEPT
	addrule2 $outchain -p 47               $(dest_ip_range $1)   -j ACCEPT
	addrule2 $inchain  -p tcp --dport 1723 $(source_ip_range $1) -j ACCEPT

	progress_message "   PPTP server defined."
    }

    setup_one_openvpn() # $1 = gateway, $2 = kind[:port]
    {
	local protocol=udp
	local p=1194

	case $2 in
	    *:*:*)
		protocol=${2%:*}
		protocol=${protocol#*:}
		p=${2##*:}
		;;
	    *:tcp|*:udp|*:TCP|*:UDP)
		protocol=${2#*:}
		;;
	    *:*)
		p=${2#*:}
		;;
	esac

	addrule2 $inchain  -p $protocol $(source_ip_range $1) --dport $p -j ACCEPT
	addrule2 $outchain -p $protocol $(dest_ip_range $1)   --dport $p -j ACCEPT

	progress_message "   OPENVPN tunnel to $1:$protocol:$p defined."
    }

    setup_one_openvpn_server() # $1 = gateway, $2 = kind[:port]
    {
	local protocol=udp
	local p=1194

	case $2 in
	    *:*:*)
		protocol=${2%:*}
		protocol=${protocol#*:}
		p=${2##*:}
		;;
	    *:tcp|*:udp|*:TCP|*:UDP)
		protocol=${2#*:}
		;;
	    *:*)
		p=${2#*:}
		;;
	esac

	addrule2 $inchain  -p $protocol $(source_ip_range $1) --dport $p -j ACCEPT
	addrule2 $outchain -p $protocol $(dest_ip_range $1)   --sport $p -j ACCEPT

	progress_message "   OPENVPN server tunnel from $1:$protocol:$p defined."
    }

    setup_one_openvpn_client() # $1 = gateway, $2 = kind[:port]
    {
	local protocol=udp
	local p=1194

	case $2 in
	    *:*:*)
		protocol=${2%:*}
		protocol=${protocol#*:}
		p=${2##*:}
		;;
	    *:tcp|*:udp|*:TCP|*:UDP)
		protocol=${2#*:}
		;;
	    *:*)
		p=${2#*:}
		;;
	esac

	addrule2 $inchain  -p $protocol $(source_ip_range $1) --sport $p -j ACCEPT
	addrule2 $outchain -p $protocol $(dest_ip_range $1)   --dport $p -j ACCEPT

	progress_message "   OPENVPN client tunnel to $1:$protocol:$p defined."
    }

    setup_one_generic() # $1 = gateway, $2 = kind:protocol[:port]
    {
	local protocol
	local p=

	case $2 in
	    *:*:*)
		p=${2##*:}
		protocol=${2%:*}
		protocol=${protocol#*:}
		;;
	    *:*)
		protocol=${2#*:}
		;;
	    *)
		protocol=udp
		p=5000
		;;
	esac

	p=${p:+--dport $p}

	addrule2 $inchain  -p $protocol $(source_ip_range $1) $p -j ACCEPT
	addrule2 $outchain -p $protocol $(dest_ip_range $1)   $p -j ACCEPT

	progress_message "   GENERIC tunnel to $1:$p defined."
    }

    strip_file tunnels $1

    while read kind z gateway z1; do
	expandv kind z gateway z1
	tunnel="$(echo $kind $z $gateway $z1)"
	if validate_zone $z; then
	    inchain=${z}2${FW}
	    outchain=${FW}2${z}
	    gateway=${gateway:-0.0.0.0/0}
	    case $kind in
		ipsec|IPSEC|ipsec:*|IPSEC:*)
		    setup_one_ipsec $gateway $kind $z1
		    ;;
		ipsecnat|IPSECNAT|ipsecnat:*|IPSECNAT:*)
		    setup_one_ipsec $gateway $kind $z1
		    ;;
		ipip|IPIP)
		    setup_one_other IPIP $gateway 4
		    ;;
		gre|GRE)
		    setup_one_other GRE $gateway 47
		    ;;
		6to4|6TO4)
		    setup_one_other 6to4 $gateway 41
		    ;;
		pptpclient|PPTPCLIENT)
		    setup_pptp_client $gateway
		    ;;
		pptpserver|PPTPSERVER)
		    setup_pptp_server $gateway
		    ;;
		openvpn|OPENVPN|openvpn:*|OPENVPN:*)
		    setup_one_openvpn $gateway $kind
		    ;;
		openvpnclient|OPENVPNCLIENT|openvpnclient:*|OPENVPNCLIENT:*)
		    setup_one_openvpn_client $gateway $kind
		    ;;
		openvpnserver|OPENVPNSERVER|openvpnserver:*|OPENVPNSERVER:*)
		    setup_one_openvpn_server $gateway $kind
		    ;;
		generic:*|GENERIC:*)
		    setup_one_generic $gateway $kind
		    ;;
		*)
		    error_message "WARNING: Tunnels of type $kind are not supported:" \
			"Tunnel \"$tunnel\" Ignored"
		    ;;
	    esac
	else
	    error_message "ERROR: Invalid gateway zone ($z)" \
		" -- Tunnel \"$tunnel\" Ignored"
	fi
    done < $TMP_DIR/tunnels
}

#
# Process the ipsec information in the zones file
#
setup_ipsec() {
    local zone using_ipsec=
    #
    # Add a --set-mss rule to the passed chain
    #
    set_mss1() # $1 = chain, $2 = MSS
    {
	eval local policy=\$${1}_policy

	if [ "$policy" != NONE ]; then
	    ensurechain $1
	    run_iptables -I $1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss $2
	fi
    }
    #
    # Set up rules to set MSS to and/or from zone "$zone"
    #
    set_mss() # $1 = MSS value, $2 = _in, _out or ""
    {
	for z in $ZONES; do
	    case $2 in
		_in)
		    set_mss1 ${zone}2${z} $1
		    ;;
		_out)
		    set_mss1 ${z}2${zone} $1
		    ;;
		*)
		    set_mss1 ${z}2${zone} $1
		    set_mss1 ${zone}2${z} $1
		    ;;
	    esac
	done
}

    do_options() # $1 = _in, _out or "" - $2 = option list
    {
	local option opts newoptions= val

	[ x${2} = x- ] && return

	opts=$(separate_list $2)

	for option in $opts; do
	    val=${option#*=}

	    case $option in
		mss=[0-9]*)    set_mss $val $1 ;;
		strict)        newoptions="$newoptions --strict" ;;
		next)          newoptions="$newoptions --next" ;;
		reqid=*)       newoptions="$newoptions --reqid $val" ;;
		spi=*)         newoptions="$newoptions --spi $val" ;;
		proto=*)       newoptions="$newoptions --proto $val" ;;
		mode=*)        newoptions="$newoptions --mode $val" ;;
		tunnel-src=*)  newoptions="$newoptions --tunnel-src $val" ;;
		tunnel-dst=*)  newoptions="$newoptions --tunnel-dst $val" ;;
		reqid!=*)      newoptions="$newoptions ! --reqid $val" ;;
		spi!=*)        newoptions="$newoptions ! --spi $val" ;;
		proto!=*)      newoptions="$newoptions ! --proto $val" ;;
		mode!=*)       newoptions="$newoptions ! --mode $val" ;;
		tunnel-src!=*) newoptions="$newoptions ! --tunnel-src $val" ;;
		tunnel-dst!=*) newoptions="$newoptions ! --tunnel-dst $val" ;;
		*)             fatal_error "Invalid option \"$option\" for zone $zone" ;;
	    esac
	done

	if [ -n "$newoptions" ]; then
	    [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match"
	    eval ${zone}_is_complex=Yes
	    eval ${zone}_ipsec${1}_options=\"${newoptions# }\"
	fi
    }

    case $IPSECFILE in
	zones)
	    f=zones
	    progress_message "Setting up IPSEC..."
	    ;;
	*)
	    f=$IPSECFILE
	    strip_file $f
	    progress_message "$DOING $f..."
	    using_ipsec=Yes
	    ;;
    esac

    while read zone type options in_options out_options mss; do
	expandv zone type options in_options out_options mss

	if [ -n "$using_ipsec" ]; then
	    validate_zone1 $zone || fatal_error "Unknown zone: $zone"
	fi

	if [ -n "$type" ]; then
	    if [ -n "$using_ipsec" ]; then
		case $type in
		    No|no)
			;;
		    Yes|yes)
			[ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match"
			eval ${zone}_is_ipsec=Yes
			eval ${zone}_is_complex=Yes
			eval ${zone}_type=ipsec4
			;;
		    *)
			fatal_error "Invalid IPSEC column contents"
			;;
		esac
	    fi

	    do_options ""     $options
	    do_options "_in"  $in_options
	    do_options "_out" $out_options
	fi

    done < $TMP_DIR/$f
}

##
# Setup Proxy ARP
#
setup_proxy_arp() {

    local setlist= resetlist=

    print_error() {
	error_message "Invalid value for HAVEROUTE - ($haveroute)"
	error_message "Entry \"$address $interface $external $haveroute\" ignored"
    }

    print_error1() {
	error_message "Invalid value for PERSISTENT - ($persistent)"
	error_message "Entry \"$address $interface $external $haveroute $persistent\" ignored"
    }

    print_warning() {
	error_message "PERSISTENT setting ignored - ($persistent)"
	error_message "Entry \"$address $interface $external $haveroute $persistent\""
    }

    setup_one_proxy_arp() {

	case $haveroute in
	[Nn][Oo])
	    haveroute=
	    ;;
	[Yy][Ee][Ss])
	    ;;
	*)
	    if [ -n "$haveroute" ]; then
		print_error
		return
	    fi
	    ;;
	esac

	case $persistent in
	[Nn][Oo])
	    persistent=
	    ;;
	[Yy][Ee][Ss])
	    [ -z "$haveroute" ] || print_warning
	    ;;
	*)
	    if [ -n "$persistent" ]; then
		print_error1
		return
	    fi
	    ;;
	esac

	if [ -z "$haveroute" ]; then
	    save_command "[ -n \"\$NOROUTES\" ] || run_ip route replace $address dev $interface"
	    [ -n "$persistent" ] && haveroute=yes
	fi

	cat >> $RESTOREBASE << __EOF__
${INDENT}if ! arp -i $external -Ds $address $external pub; then
${INDENT}    fatal_error "Command \"arp -i $external -Ds $address $external pub\" failed"
${INDENT}fi

${INDENT}progress_message "   Host $address connected to $interface added to ARP on $external"

__EOF__
	echo $address $interface $external $haveroute >> $STATEDIR/proxyarp

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

    > $STATEDIR/proxyarp

    save_progress_message "Setting up Proxy ARP..."

    while read address interface external haveroute persistent; do
	expandv address interface external haveroute persistent
	list_search $interface $setlist   || setlist="$setlist $interface"
	list_search $external  $resetlist || list_search $external $setlist || resetlist="$resetlist $external"
	setup_one_proxy_arp
    done < $TMP_DIR/proxyarp

    for interface in $resetlist; do
	list_search $interface $setlist || \
	    save_command "echo 0 > /proc/sys/net/ipv4/conf/$interface/proxy_arp"
    done

    for interface in $setlist; do
	save_command "echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp"
    done

    interfaces=$(find_interfaces_by_option proxyarp)

    for interface in $interfaces; do
	if [ -f /proc/sys/net/ipv4/conf/$interface/proxy_arp ] ; then
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp"
	    progress_message "   Enabled proxy ARP on $interface"
	else
	    error_message "WARNING: Unable to enable proxy ARP on $interface"
	fi
    done

}

#
# Set up MAC Verification
#
setup_mac_lists() {
    local interface
    local mac
    local addresses
    local address
    local chain
    local chain1
    local macpart
    local blob
    local hosts
    local ipsec
    local policy=

    create_mac_chain()
    {
	case $MACLIST_TABLE in
	    filter)
		createchain $1 no
		;;
	    *)
		createmanglechain $1
		;;
	esac
    }

    have_mac_chain()
    {
	local result

	case $MACLIST_TABLE in
	    filter)
		havechain $1 && result=0 || result=1
		;;
	    *)
		havemanglechain $1 && result=0 || result=1
		;;
	esac

	return $result
    }
    #
    # Generate the list of interfaces having MAC verification
    #
    maclist_interfaces=

    for hosts in $maclist_hosts; do
	hosts=${hosts#*^}
	interface=${hosts%%:*}
	if ! list_search $interface $maclist_interfaces; then\
	    if [ -z "$maclist_interfaces" ]; then
		maclist_interfaces=$interface
	    else
		maclist_interfaces="$maclist_interfaces $interface"
	    fi
	fi
    done

    progress_message "Setting up MAC Verification on $maclist_interfaces..."
    #
    # Create chains.
    #
    for interface in $maclist_interfaces; do
	chain=$(mac_chain $interface)
	create_mac_chain $chain
	#
	# If we're using the mangle table and the interface is DHCP-enabled then we need to accept DHCP broadcasts from 0.0.0.0
	#
	if [ $MACLIST_TABLE = mangle ] && interface_has_option $interface dhcp; then
	    run_iptables -t mangle -A $chain -s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67:68 -j RETURN
	fi

	if [ -n "$MACLIST_TTL" ]; then
	    chain1=$(macrecent_target $interface)
	    create_mac_chain $chain1
	    run_iptables -A $chain  -t $MACLIST_TABLE -m recent --rcheck --seconds $MACLIST_TTL --name $chain -j RETURN
	    run_iptables -A $chain  -t $MACLIST_TABLE                                                         -j $chain1
	    run_iptables -A $chain  -t $MACLIST_TABLE -m recent --update                        --name $chain -j RETURN
	    run_iptables -A $chain  -t $MACLIST_TABLE -m recent --set                           --name $chain
	fi
    done

    #
    # Process the maclist file producing the verification rules
    #
    while read disposition interface mac addresses; do
	expandv disposition interface mac addresses

	level=

	case $disposition in
	    ACCEPT:*)
		level=${disposition#*:}
		disposition=ACCEPT
		target=RETURN
		;;
	    ACCEPT)
		target=RETURN
		;;
	    REJECT:*)
		[ $MACLIST_TABLE = mangle ] && fatal_error "DISPOSITION = REJECT is incompatible with MACLIST_TABLE=mangle"
		target=reject
		disposition=REJECT
		;;
	    REJECT)
		[ $MACLIST_TABLE = mangle ] && fatal_error "DISPOSITION = REJECT is incompatible with MACLIST_TABLE=mangle"
		target=reject
		;;
	    DROP:*)
		level=${disposition#*:}
		disposition=DROP
		target=DROP
		;;
	    DROP)
		target=DROP
		;;
	    *)
		addresses="$mac"
		mac="$interface"
		interface="$disposition"
		disposition=ACCEPT
		target=RETURN
		;;
	esac

	physdev_part=

	if [ -n "$BRIDGING" ]; then
	    case $interface in
		*:*)
		    physdev_part="-m physdev --physdev-in ${interface#*:}"
		    interface=${interface%:*}
		    ;;
	    esac
	fi

	[ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface)

	if ! have_mac_chain $chain ; then
	    fatal_error "No hosts on $interface have the maclist option specified"
	fi

	if [ x${mac:=-} = x- ]; then
	    if [ -z "$addresses" ]; then
		fatal_error "You must specify a MAC address or an IP address"
	    else
		macpart=
	    fi
	else
	    macpart=$(mac_match $mac)
	fi

	if [ -z "$addresses" ]; then
	    [ -n "$level" ] && \
		log_rule_limit $level $chain $(mac_chain $interface) $disposition "$LOGLIMIT" "" -A -t $MACLIST_TABLE $macpart $physdev_part
	    run_iptables -A $chain -t $MACLIST_TABLE $macpart $physdev_part -j $target
	else
	    for address in $(separate_list $addresses) ; do
		[ -n "$level" ] && \
		    log_rule_limit $level $chain $(mac_chain $interface) $disposition "$LOGLIMIT" "" -A -t $MACLIST_TABLE $macpart -s $address $physdev_part
		run_iptables2 -A $chain -t $MACLIST_TABLE $macpart -s $address $physdev_part -j $target
	    done
	fi
    done < $TMP_DIR/maclist
    #
    # Must take care of our own broadcasts and multicasts then terminate the verification
    # chains
    #
    for interface in $maclist_interfaces; do

	[ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface)

	cat >> $RESTOREBASE << __EOF__

${INDENT}blob=\$(ip link show $interface 2> /dev/null)

${INDENT}[ -z "\$blob" ] && \
${INDENT}    fatal_error "Interface $interface must be up before Shorewall can start"

${INDENT}ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet //; s/brd //; s/scope.*//;' | while read address broadcast; do
${INDENT}    address=\${address%/*}
${INDENT}    if [ -n "\$broadcast" ]; then
${INDENT}       run_iptables -t $MACLIST_TABLE -A $chain -s \$address -d \$broadcast -j RETURN
${INDENT}    fi
${INDENT}done

__EOF__

	if [ -n "$MACLIST_LOG_LEVEL" ]; then
	    log_rule_limit $MACLIST_LOG_LEVEL $chain $(mac_chain $interface) $MACLIST_DISPOSITION "$LOGLIMIT" "" -A -t $MACLIST_TABLE
	fi

	run_iptables -A $chain -t $MACLIST_TABLE -j $maclist_target
    done
    #
    # Generate jumps from the input and forward chains
    #
    for hosts in $maclist_hosts; do
	ipsec=${hosts%^*}
	hosts=${hosts#*^}
	[ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	interface=${hosts%%:*}
	hosts=${hosts#*:}
	case $MACLIST_TABLE in
	    filter)
		for chain in $(first_chains $interface) ; do
		    run_iptables -A $chain $(match_source_hosts $hosts) -m state --state NEW \
			$policy -j $(mac_chain $interface)
		done
		;;
	    *)
		run_iptables -t mangle -A PREROUTING -i $interface $(match_source_hosts $hosts) -m state --state NEW \
			$policy -j $(mac_chain $interface)
		;;
	esac
    done
}

#
# Set up SYN flood protection
#
setup_syn_flood_chain ()
	# $1 = policy chain
	# $2 = synparams
        # $3 = loglevel
{
    local chain=@$1
    local limit=$2
    local limit_burst=

    case $limit in
	*:*)
	    limit_burst="--limit-burst ${limit#*:}"
	    limit=${limit%:*}
	    ;;
    esac

    run_iptables -N $chain
    run_iptables -A $chain -m limit --limit $limit $limit_burst -j RETURN
    [ -n "$3" ] && \
	log_rule_limit $3 $chain $chain DROP "-m limit --limit 5/min --limit-burst 5" "" ""
    run_iptables -A $chain -j DROP
}

setup_syn_flood_chains()
{
    for chain in $ALL_POLICY_CHAINS; do
	eval loglevel=\$${chain}_loglevel
	eval synparams=\$${chain}_synparams

	[ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams $loglevel
    done
}

#
# Delete existing Proxy ARP
#
delete_proxy_arp() {
    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f /var/lib/shorewall/proxyarp ]; then
${INDENT}    while read address interface external haveroute; do
${INDENT}        qt arp -i \$external -d \$address pub
${INDENT}        [ -z "\$haveroute" -a -z "\$NOROUTE" ] && qt ip route del \$address dev \$interface
${INDENT}    done < /var/lib/shorewall/proxyarp

${INDENT}    rm -f {/var/lib/shorewall}/nat
${INDENT}fi
__EOF__

    [ -d $STATEDIR ] && touch $STATEDIR/proxyarp

    cat >> $RESTOREBASE << __EOF__

${INDENT}for f in /proc/sys/net/ipv4/conf/*; do
${INDENT}    [ -f \$f/proxy_arp ] && echo 0 > \$f/proxy_arp
${INDENT}done
${INDENT}
__EOF__
}

#
# Setup Static Network Address Translation (NAT)
#
setup_nat() {
    local external= interface= internal= allints= localnat= policyin= policyout=

    validate_one() #1 = Variable Name, $2 = Column name, $3 = value
    {
	case $3 in
	    Yes|yes)
                ;;
	    No|no)
		eval ${1}=
		;;
	    *)
		[ -n "$3" ] && \
		    fatal_error "Invalid value ($3) for $2 in entry \"$external $interface $internal $allints $localnat\""
		;;
	esac
    }

    do_one_nat() {
	local add_ip_aliases=$ADD_IP_ALIASES iface=${interface%:*}

	if [ -n "$add_ip_aliases" ]; then
	    case $interface in
		*:)
		    interface=${interface%:}
		    add_ip_aliases=
		    ;;
		*)
		    [ -n "$RETAIN_ALIASES" ] || save_command qt ip addr del $external dev $iface
		    ;;
	    esac
	else
	    interface=${interface%:}
	fi

	validate_one allints  "ALL INTERFACES" $allints
	validate_one localnat "LOCAL"          $localnat

	if [ -n "$allints" ]; then
	    addnatrule nat_in  -d $external $policyin  -j DNAT --to-destination $internal
	    addnatrule nat_out -s $internal $policyout -j SNAT --to-source $external
	else
	    addnatrule $(input_chain $iface)  -d $external $policyin  -j DNAT --to-destination $internal
		addnatrule $(output_chain $iface) -s $internal $policyout -j SNAT --to-source $external
	fi

	[ -n "$localnat" ] && \
	    run_iptables2 -t nat -A OUTPUT -d $external $policyout -j DNAT --to-destination $internal

	if [ -n "$add_ip_aliases" ]; then
	    list_search $external $ALIASES_TO_ADD || \
		ALIASES_TO_ADD="$ALIASES_TO_ADD $external $interface"
	fi
    }
    #
    # At this point, we're just interested in the network translation
    #
    > $STATEDIR/nat

    if [ -n "$POLICY_MATCH" ]; then
	policyin="-m policy --pol none --dir in"
	policyout="-m policy --pol none --dir out"
    fi

    [ -n "$RETAIN_ALIASES" ] || save_progress_message "Setting up one-to-one NAT..."

    while read external interface internal allints localnat; do
	expandv external interface internal allints localnat

	do_one_nat

	progress_message "   Host $internal NAT $external on $interface"
    done < $TMP_DIR/nat
}

#
# Delete existing Static NAT
#
delete_nat() {
    run_iptables -t nat -F
    run_iptables -t nat -X

    [ -d $STATEDIR ] && touch $STATEDIR/nat

    cat >> $RESTOREBASE << __EOF__

${INDENT}if [ -f /var/lib/shorewall/nat ]; then
${INDENT}   while read external interface; do
${INDENT}       qt ip addr del \$external dev \$interface
${INDENT}    done < /var/lib/shorewall/nat
${INDENT}
${INDENT}    rm -f {/var/lib/shorewall}/nat
${INDENT}fi

__EOF__
}

#
# Setup Network Mapping (NETMAP)
#
setup_netmap() {

    while read type net1 interface net2 ; do
	expandv type net1 interface net2

	list_search $interface $ALL_INTERFACES || \
	    fatal_error "Unknown interface $interface in entry \"$type $net1 $interface $net2\""

	case $type in
	    DNAT)
		addnatrule $(input_chain $interface)  -d $net1 -j NETMAP --to $net2
		;;
	    SNAT)
		addnatrule $(output_chain $interface) -s $net1 -j NETMAP --to $net2
		;;
	    *)
		fatal_error "Invalid type $type in entry \"$type $net1 $interface $net2\""
		;;
	esac

	progress_message "   Network $net1 on $interface mapped to $net2 ($type)"

    done < $TMP_DIR/netmap
}

#
# Setup ECN disabling rules
#
setup_ecn() # $1 = file name
{
    local interfaces=""
    local hosts=
    local h

    strip_file ecn $1

    progress_message2 "$DOING $1..."

    while read interface host; do
	expandv interface host
	list_search  $interface $ALL_INTERFACES || \
	    startup_error "Unknown interface $interface"
	list_search $interface $interfaces || \
	    interfaces="$interfaces $interface"
	[ "x$host" = "x-" ] && host=
	for h in $(separate_list ${host:-0.0.0.0/0}); do
	    hosts="$hosts $interface:$h"
	done
    done < $TMP_DIR/ecn

    if [ -n "$interfaces" ]; then
	progress_message "Setting up ECN control on${interfaces}..."

	for interface in $interfaces; do
	    chain=$(ecn_chain $interface)
	    if havemanglechain $chain; then
		flushmangle $chain
	    else
		createmanglechain $chain
		run_iptables -t mangle -A POSTROUTING -p tcp -o $interface -j $chain
		run_iptables -t mangle -A OUTPUT      -p tcp -o $interface -j $chain
	    fi
	done

	for host in $hosts; do
	    interface=${host%:*}
	    h=${host#*:}
	    run_iptables -t mangle -A $(ecn_chain $interface) -p tcp $(dest_ip_range $h) -j ECN --ecn-tcp-remove
	    progress_message "   ECN Disabled to $h through $interface"
	done
    fi
}

#
# Set up an exclusion chain
#
build_exclusion_chain() # $1 = variable to store chain name into $2 = table, $3 = SOURCE exclusion list, $4 = DESTINATION exclusion list
{
    local c=excl_${EXCLUSION_SEQ} net

    EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 ))

    run_iptables -t $2 -N $c

    for net in $(separate_list $3); do
	run_iptables -t $2 -A $c $(source_ip_range $net) -j RETURN
    done

    for net in $(separate_list $4); do
	run_iptables -t $2 -A $c $(dest_ip_range $net) -j RETURN
    done

    case $2 in
	filter)
	    eval exists_${c}=Yes
	    ;;
	nat)
	    eval exists_nat_${c}=Yes
	    ;;
    esac

    eval $1=$c
}

#
# Arne Bernin's 'tc4shorewall'
#
setup_traffic_shaping()
{
    local mtu r2q tc_all_devices device mark rate ceil prio options devfile=$(find_file tcdevices) classfile=$(find_file tcclasses) devnum=1
    mtu=1500
    r2q=10

    ensure_and_save_tc() {
	run_tc $@
    }

    rate_to_kbit() {
	local rateunit rate
	rate=$1
	rateunit=$( echo $rate | sed -e 's/[0-9]*//')
	rate=$( echo $rate | sed -e 's/[a-z]*//g')

	case $rateunit in
	    kbit)
		rate=$rate
		;;
	    mbit)
		rate=$(expr $rate \* 1024)
		;;
	    mbps)
		rate=$(expr $rate \* 8192)
		;;
	    kbps)
		rate=$(expr $rate \* 8)
		;;
	    *)
		rate=$(expr $rate / 128)
		;;
	esac
	echo $rate
    }

    calculate_quantum() {
	local rate
	rate=$1
	rate=$(rate_to_kbit $rate)
	rate=$(expr $rate \* 128 / $r2q )
	if [ $rate -lt $mtu ] ; then
	    echo $mtu
	else
	    echo $rate
	fi
    }

    # get given outbandwidth for device
    get_outband_for_dev() {
	local device inband outband
	while read device inband outband; do
	    expandv device inband outband
	    tcdev="$device $inband $outband"
	    if [ "$1" = "$device" ] ; then
		echo $outband
		return
	    fi
	done < $TMP_DIR/tcdevices
    }

    check_tcclasses_options() {
	while [ $# -gt 1 ]; do
	    shift
	    case $1 in
		default|tcp-ack|tos-minimize-delay|tos-maximize-throughput|tos-maximize-reliability|tos-minimize-cost|tos-normal-service)
		    ;;
		*)
		    echo $1
		    return 1
		    ;;
	    esac
	done
	return 0
    }

    get_defmark_for_dev() {
	local searchdev searchmark device ceil prio options
	searchdev=$1

	while read device mark rate ceil prio options; do
	    expandv device mark rate ceil prio options
	    options=$(separate_list $options | tr '[A-Z]' '[a-z]')
	    tcdev="$device $mark $rate $ceil $prio $options"
	    if [ "$searchdev" = "$device" ] ; then
		list_search "default" $options && echo $mark &&return 0
	    fi
	done < $TMP_DIR/tcclasses

	return 1
    }

    check_defmark_for_dev() {
	get_defmark_for_dev $1 >/dev/null
    }

    validate_tcdevices_file() {
	progress_message2 "Validating $devfile..."
	local device local device inband outband
	while read device inband outband; do
	    expandv device inband outband
	    tcdev="$device $inband $outband"
	    check_defmark_for_dev $device || fatal_error "Option default is not defined for any class in tcclasses for interface $device"
	    case $interface in
		*:*|+)
		    fatal_error "Invalid Interface Name: $interface"
		    ;;
	    esac
	    list_search $device $devices && fatal_error "Interface $device is defined more than once in tcdevices"
	    tc_all_devices="$tc_all_devices $device"
	done < $TMP_DIR/tcdevices
    }

    validate_tcclasses_file() {
	progress_message2 "Validating $classfile..."
	local classlist device mark rate ceil prio bandw wrongopt allopts opt
	allopts=""
	while read device mark rate ceil prio options; do
	    expandv device mark rate ceil prio options
	    tcdev="$device $mark $rate $ceil $prio $options"
	    ratew=$(get_outband_for_dev $device)
	    options=$(separate_list $options | tr '[A-Z]' '[a-z]')
	    for opt in $options; do
		list_search "$device-$opt" $allopts && fatal_error "option $opt already defined in a chain for interface $device in tcclasses"
		allopts="$allopts $device-$opt"
	    done
	    wrongopt=$(check_tcclasses_options $options) || fatal_error "unknown option $wrongopt for class iface $device mark $mark in tcclasses file"
	    if [ -z "$ratew" ] ; then
		fatal_error "device $device seems not to be configured in tcdevices"
	    fi
	    list_search "$device-$mark" $classlist && fatal_error "Mark $mark for interface $device defined more than once in tcclasses"
	    classlist="$classlist $device-$mark"
	done < $TMP_DIR/tcclasses
    }

    add_root_tc() {
	local defmark
	defmark=$(get_defmark_for_dev $device)
	save_command qt tc qdisc del dev $device root
	save_command qt tc qdisc del dev $device ingress
	ensure_and_save_tc qdisc add dev $device root handle $devnum: htb default 1$defmark
	ensure_and_save_tc class add dev $device parent $devnum: classid $devnum:1 htb rate $outband
	ensure_and_save_tc qdisc add dev $device handle ffff: ingress
	ensure_and_save_tc filter add dev $device parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${inband} burst 10k drop flowid :1
	eval $(chain_base $device)_devnum=$devnum
	devnum=$(($devnum + 1))
    }

    add_tc_class() {
	local full classid
	full=$(get_outband_for_dev $device)
	full=$(rate_to_kbit $full)

	if [ -z "$prio" ] ; then
	    prio=1
	fi

	case $rate in
	    *full*)
		rate=$(echo $rate | sed -e "s/full/$full/")
		rate="$(($rate))kbit"
		;;
	esac

	case $ceil in
	    *full*)
		ceil=$(echo $ceil | sed -e "s/full/$full/")
		ceil="$(($ceil))kbit"
		;;
	esac

	eval devnum=\$$(chain_base $device)_devnum
	classid=$devnum:1$mark

	[ -n "$devnum" ] || fatal_error "Device $device not defined in $devfile"

	ensure_and_save_tc class add dev $device parent $devnum:1 classid $classid htb rate $rate ceil $ceil prio $prio quantum $(calculate_quantum $rate)
	ensure_and_save_tc qdisc add dev $device parent $classid handle 1$mark: sfq perturb 10
	# add filters
	if [ -n "$CLASSIFY_TARGET" ]; then
	    run_iptables -t mangle -A tcpost -o $device -m mark --mark $mark -j CLASSIFY --set-class $classid
	else
	    ensure_and_save_tc filter add dev $device protocol ip parent $devnum:0 prio 1 handle $mark fw classid $classid
	fi
	#options
	list_search "tcp-ack" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip protocol 6 0xff match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33 flowid $classid
	list_search "tos-minimize-delay" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos 0x10 0xff flowid $classid
	list_search "tos-minimize-cost" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos 0x02 0xff flowid $classid
	list_search "tos-maximize-troughput" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos 0x08 0xff flowid $classid
	list_search "tos-minimize-reliability" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos 0x04 0xff flowid $classid
	list_search "tos-normal-service" $options && ensure_and_save_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos 0x00 0xff flowid $classid
	# tcp
    }

    strip_file tcdevices $devfile
    strip_file tcclasses $classfile

    validate_tcdevices_file
    validate_tcclasses_file

    if [ -s $TMP_DIR/tcdevices ]; then
	save_progress_message "Setting up Traffic Control..."
	progress_message2 "$DOING $devfile..."

	while read device inband outband defmark ackmark; do
	    expandv device inband outband defmark ackmark
	    tcdev="$device $inband $outband"
	    add_root_tc
	    progress_message "   TC Device $tcdev $DONE."
	done < $TMP_DIR/tcdevices
    fi

    if [ -s $TMP_DIR/tcclasses ]; then
	progress_message2 "$DOING $classfile..."

	while read device mark rate ceil prio options; do
	    expandv device mark rate ceil prio options
	    tcdev="$device $mark $rate $ceil $prio $options"
	    options=$(separate_list $options | tr '[A-Z]' '[a-z]')
	    add_tc_class
	    progress_message "   TC Class $tcdev $DONE."
	done < $TMP_DIR/tcclasses
    fi
}

#
# Process a TC Rule - $MARKING_CHAIN is assumed to contain the name of the
#                     default marking chain
#
process_tc_rule()
{
    chain=$MARKING_CHAIN  target="MARK --set-mark" marktest=

    verify_designator() {
	[ "$chain" = tcout ] && \
	    fatal_error "Chain designator not allowed when source is \$FW; rule \"$rule\""
	chain=$1
	mark="${mark%:*}"
    }

    do_ipp2p()
    {
	[ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. Rule: \"$rule\""
	[ "x$port" = "x-" ] && port="ipp2p"

	case $proto in
	    *:*)
		proto=${proto#*:}
		;;
	    *)
		proto=tcp
		;;
	esac

	r="${r}-p $proto -m ipp2p --${port} "
    }

    add_a_tc_rule() {
	r=

	if [ "x$source" != "x-"	 ]; then
	    case $source in
		$FW:*)
		    [ $chain = tcpost ] || chain=tcout
		    r="$(source_ip_range ${source#*:}) "
		    ;;
		*.*.*|+*|!+*)
		    r="$(source_ip_range $source) "
		    ;;
		~*)
		    r="$(mac_match $source) "
		    ;;
		$FW)
		    [ $chain = tcpost ] || chain=tcout
		    ;;
		*)
		    verify_interface $source || fatal_error "Unknown interface $source in rule \"$rule\""
		    r="$(match_source_dev $source) "
		    ;;
	    esac
	fi

	if [ "x${user:--}" != "x-" ]; then

	    [ "$chain" != tcout ] && \
		fatal_error "Invalid use of a user/group: rule \"$rule\""

	    r="$r-m owner"

	    case "$user" in
		*+*)
		    r="$r --cmd-owner ${user#*+} "
		    user=${user%+*}
		    ;;
	    esac

	    case "$user" in
		*:*)
		    temp="${user%:*}"
		    [ -n "$temp" ] && r="$r --uid-owner $temp "
		    temp="${user#*:}"
		    [ -n "$temp" ] && r="$r --gid-owner $temp "
		    ;;
		*)
		    [ -n "$user" ] && r="$r --uid-owner $user "
		    ;;
	    esac
	fi

	[ -n "$marktest" ] && r="${r}-m ${marktest}--mark $testval "

	if [ "x$dest" != "x-"  ]; then
	    case $dest in
		*.*.*|+*|!+*)
		    r="${r}$(dest_ip_range $dest) "
		    ;;
		*)
		    [ "$chain" = tcpre ] && fatal_error "Destination interface is not allowed in the PREROUTING chain"
		    verify_interface $dest || fatal_error "Unknown interface $dest in rule \"$rule\""
		    r="${r}$(match_dest_dev $dest) "
		    ;;
	    esac
	fi

	if [ "x${length:=-}" != "x-" ]; then
	    [ -n "$LENGTH_MATCH" ] || fatal_error "Your kernel and/or iptables does not have length match support. Rule: \"$rule\""
	    r="${r}-m length --length ${length} "
	fi

	multiport=

	case $proto in
	    ipp2p|IPP2P|ipp2p:*|IPP2P:*)
		do_ipp2p
		;;
	    icmp|ICMP|1)
		r="${r}-p icmp "
		[ "x$port"  = "x-" ] || r="${r}--icmp-type $port"
		;;
	    *)
		[ "x$proto" = "x-"  ] && proto=all
		[ "x$proto" = "x"   ] && proto=all
		[ "$proto"  = "all" ] || r="${r}-p $proto "
		[ "x$port"  = "x-"  ] || r="${r}--dport $port "
		;;
	esac

	[ "x$sport" = "x-"  ] || r="${r}--sport $sport "

	if [ -n "${excludesources}${excludedests}" ]; then
	    build_exclusion_chain chain1 mangle "$excludesources" "$excludedests"

	    run_iptables2 -t mangle -A $chain $r -j $chain1

	    run_iptables -t mangle -A $chain1 -j $target $mark
	else
	    run_iptables2 -t mangle -A $chain $r -j $target $mark
	fi

    }

    if [ "$mark" != "${mark%:*}" ]; then
	case "${mark#*:}" in
	    p|P)
		verify_designator tcpre
		;;
	    cp|CP)
		verify_designator tcpre
		target="CONNMARK --set-mark"
		;;
	    f|F)
		verify_designator tcfor
		;;
	    cf|CF)
		verify_designator tcfor
		target="CONNMARK --set-mark"
		;;
	    c|C)
		target="CONNMARK --set-mark"
		mark=${mark%:*}
		;;
	    *)
		chain=tcpost
		target="CLASSIFY --set-class"
		;;
	esac

    fi

    case $mark in
	SAVE)
	    target="CONNMARK --save-mark --mask 255"
	    mark=
	    ;;
	SAVE/*)
	    target="CONNMARK --save-mark --mask"
	    mark=${mark#*/}
	    verify_mark $mark
	    ;;
	RESTORE)
	    target="CONNMARK --restore-mark --mask 255"
	    mark=
	    ;;
	RESTORE/*)
	    target="CONNMARK --restore-mark --mask"
	    mark=${mark#*/}
	    verify_mark $mark
	    ;;
	CONTINUE)
	    target=RETURN
	    mark=
	    ;;
	*)
	    if [ "$chain" != tcpost ]; then
		verify_mark $mark
	    fi
	    ;;
    esac

    case $testval in
	-)
	    ;;
	!*:C)
	    marktest="connmark ! "
	    testval=${testval%:*}
	    testval=${testval#!}
	    ;;
	*:C)
	    marktest="connmark "
	    testval=${testval%:*}
	    ;;
	!*)
	    marktest="mark ! "
	    testval=${testval#!}
	    ;;
	*)
	    [ -n "$testval" ] && marktest="mark "
	    ;;
    esac

    if [ -n "$marktest" ] ; then
	case $testval in
	    */*)
		verify_mark ${testval%/*}
		verify_mark ${testval#*/}
		;;
	    *)
		verify_mark $testval
		testval=$testval/255
		;;
	esac
    fi

    excludesources=

    case ${sources:=-} in
	*!*!*)
	    fatal_error "Invalid SOURCE in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $sourcess) -gt 1 ]; then
		excludesources=${sources#!}
		sources=-
	    fi
	    ;;
	*!*)
	    excludesources=${sources#*!}
	    sources=${sources%!*}
	    ;;
    esac

    excludedests=

    case ${dests:=-} in
	*!*!*)
	    fatal_error "Invalid DEST in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $dests) -gt 1 ]; then
		excludedests=${dests#*!}
		dests=-
	    fi
	    ;;
	*!*)
	    excludedests=${dests#*!}
	    dests=${dests%!*}
	    ;;
    esac

    for source in $(separate_list $sources); do
	for dest in $(separate_list $dests); do
	    for port in $(separate_list ${ports:=-}); do
		for sport in $(separate_list ${sports:=-}); do
		    add_a_tc_rule
		done
	    done
	done
    done

    progress_message "   TC Rule \"$rule\" $DONE"
}

#
# Setup queuing and classes
#
setup_tc1() {
    #
    # Create the TC mangle chains
    #

    createmanglechain tcpre
    createmanglechain tcfor
    createmanglechain tcout
    createmanglechain tcpost
    #
    # Process the TC Rules File
    #
    strip_file tcrules

    while read mark sources dests proto ports sports user testval length; do
	expandv mark sources dests proto ports sports user testval length
	rule=$(echo "$mark $sources $dests $proto $ports $sports $user $testval $length")
	process_tc_rule
    done < $TMP_DIR/tcrules
    #
    # Link to the TC mangle chains from the main chains
    #

    if [ -n "$ROUTEMARK_INTERFACES" ]; then
	#
	# Route marks are restored in PREROUTING/OUTPUT prior to these rules. We only send
	# packets that are not part of a marked connection to the 'tcpre/tcout' chains
	#
	run_iptables -t mangle -A PREROUTING  -m mark --mark 0 -j tcpre
	run_iptables -t mangle -A OUTPUT      -m mark --mark 0 -j tcout
    else
	run_iptables -t mangle -A PREROUTING -j tcpre
	run_iptables -t mangle -A OUTPUT     -j tcout
    fi
    run_iptables -t mangle -A FORWARD     -j tcfor
    run_iptables -t mangle -A POSTROUTING -j tcpost

    if [ -n "$TC_SCRIPT" ]; then

	run_user_exit $TC_SCRIPT

        save_progress_message "Setting up Traffic Control..."
	save_command . $TC_SCRIPT
    elif [ -n "$TC_ENABLED" ]; then
	setup_traffic_shaping
    fi
}

setup_tc() {

    progress_message2 "Setting up Traffic Control Rules..."

    setup_tc1
}

#
# Clear Traffic Shaping
#
delete_tc()
{
    clear_one_tc() {
	save_command "tc qdisc del dev $1 root 2> /dev/null"
	save_command "tc qdisc del dev $1 ingress 2> /dev/null"

    }

    save_progress_message "Clearing Traffic Control/QOS"

    run_user_exit tcclear

    cat >> $RESTOREBASE << __EOF__
${INDENT}ip link list | while read inx interface details; do
${INDENT}    case \$inx in
${INDENT}       [0-9]*)
${INDENT}           qt tc qdisc del dev \${interface%:} root
${INDENT}           qt tc qdisc del dev \${interface%:} ingress
${INDENT}           ;;
${INDENT}       *)
${INDENT}           ;;
${INDENT}    esac
${INDENT}done
__EOF__
}

#
# Process a record from the accounting file
#
process_accounting_rule() {
    rule=
    rule2=
    jumpchain=
    user1=

    accounting_error() {
	error_message "WARNING: Invalid Accounting rule" $action $chain $source $dest $proto $port $sport $user
    }

    accounting_interface_error() {
	error_message "WARNING: Unknown interface $1 in " $action $chain $source $dest $proto $port $sport $user
    }

    accounting_interface_verify() {
	verify_interface $1 || accounting_interface_error $1
    }

    jump_to_chain() {
	if ! havechain $jumpchain; then
	    if ! createchain2 $jumpchain No; then
		accounting_error
		return 2
	    fi
	fi

	rule="$rule -j $jumpchain"
    }

    do_ipp2p() {
	[ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support"
	case $proto in
	    *:*)
		proto=${proto#*:}
		;;
	    *)
		proto=tcp
		;;
	esac

	rule="$rule -p $proto -m ipp2p --${port:-ipp2p}"
    }

    case $source in
	*:*)
	    accounting_interface_verify ${source%:*}
	    rule="$(source_ip_range ${source#*:}) $(match_source_dev ${source%:*})"
	    ;;
	*.*.*.*|+*|!+*)
	    rule="$(source_ip_range $source)"
	    ;;
	-|all|any)
	    ;;
	*)
	    if [ -n "$source" ]; then
		accounting_interface_verify $source
		rule="$(match_source_dev $source)"
	    fi
	    ;;
    esac

    [ -n "$dest" ] && case $dest in
	*:*)
	    accounting_interface_verify ${dest%:*}
	    rule="$rule $(dest_ip_range ${dest#*:}) $(match_dest_dev ${dest%:*})"
	    ;;
	*.*.*.*|+*|!*)
	    rule="$rule $(dest_ip_range $dest)"
	    ;;
	-|all|any)
	    ;;
	*)
	    accounting_interface_verify $dest
	    rule="$rule $(match_dest_dev $dest)"
	    ;;
    esac

    [ -n "$proto" ] && case $proto in
	-|any|all)
	    ;;
	    ipp2p|IPP2P|ipp2p:*|IPP2P:*)
		do_ipp2p
		;;
	*)
	    rule="$rule -p $proto"
	    ;;
    esac

    multiport=

    [ -n "$port" ] && case $port in
	-|any|all)
	    ;;
	*)
	    if [ -n "$MULTIPORT" ]; then
		rule="$rule -m multiport --dports $port"
		multiport=Yes
	    else
		rule="$rule --dport $port"
	    fi
	    ;;
    esac

    [ -n "$sport" ] && case $sport in
	-|any|all)
	    ;;
	*)
	    if [ -n "$MULTIPORT" ]; then
		[ -n "$multiport" ] && rule="$rule --sports $sport" || rule="$rule -m multiport --sports $sport"
	    else
		rule="$rule --sport $sport"
	    fi
	    ;;
    esac

    [ -n "$user" ] && case $user in
	-|any|all)
	    ;;
	*)
	    [ "$chain" != OUTPUT ] && \
		fatal_error "Invalid use of a user/group: chain is not OUTPUT but $chain"
	    rule="$rule -m owner"
	    user1="$user"

	    case "$user" in
		!*+*)
		    if [ -n "${user#*+}" ]; then
			rule="$rule ! --cmd-owner ${user#*+} "
		    fi
		    user1=${user%+*}
		    ;;
		*+*)
		    if [ -n "${user#*+}" ]; then
			rule="$rule --cmd-owner ${user#*+} "
		    fi
		    user1=${user%+*}
		    ;;
	    esac

	    case "$user1" in
		!*:*)
		    if [ "$user1" != "!:" ]; then
			temp="${user1#!}"
			temp="${temp%:*}"
			[ -n "$temp" ] && rule="$rule ! --uid-owner $temp "
			temp="${user1#*:}"
			[ -n "$temp" ] && rule="$rule ! --gid-owner $temp "
		    fi
		    ;;
		*:*)
		    if [ "$user1" != ":" ]; then
			temp="${user1%:*}"
			[ -n "$temp" ] && rule="$rule --uid-owner $temp "
			temp="${user1#*:}"
			[ -n "$temp" ] && rule="$rule --gid-owner $temp "
		    fi
		    ;;
		!*)
		    [ "$user1" != "!" ] && rule="$rule ! --uid-owner ${user1#!} "
		    ;;
		*)
		    [ -n "$user1" ] && rule="$rule --uid-owner $user1 "
		    ;;
	    esac
	    ;;
    esac

    case $action in
	COUNT)
	    ;;
	DONE)
	    rule="$rule -j RETURN"
	    ;;
	*:COUNT)
	    rule2="$rule"
	    jumpchain=${action%:*}
	    jump_to_chain || return
	    ;;
	JUMP:*)
	    jumpchain=${action#*:}
	    jump_to_chain || return
	    ;;
	*)
	    jumpchain=$action
	    jump_to_chain || return
	    ;;
    esac

    [ "x${chain:=accounting}" = "x-" ] && chain=accounting

    ensurechain1 $chain

    if do_iptables -A $chain $(fix_bang $rule) ; then
	[ -n "$rule2" ] && run_iptables2 -A $jumpchain $rule2
	progress_message "   Accounting rule" $action $chain $source $dest $proto $port $sport $user $DONE
	save_progress_message_short "   Accounting rule $action $chain $source $dest $proto $port $sport $user Added"
    else
	accounting_error
    fi
}

#
# Set up Accounting
#
setup_accounting() # $1 = Name of accounting file
{

    progress_message2 "Setting up Accounting..."

    save_progress_message "Setting up Accounting..."

    strip_file accounting $1

    while read action chain source dest proto port sport user ; do
	expandv action chain source dest proto port sport user
	process_accounting_rule
    done < $TMP_DIR/accounting

    if havechain accounting; then
	for chain in INPUT FORWARD OUTPUT; do
	    run_iptables -I $chain -j accounting
	done
    fi

}

#
# Add one Filter Rule from an action -- Helper function for the action file processor
#
# The caller has established the following variables:
#    COMMAND      = current command.
#    client	  = SOURCE IP or MAC
#    server	  = DESTINATION IP or interface
#    protocol	  = Protocol
#    address	  = Original Destination Address
#    port	  = Destination Port
#    cport	  = Source Port
#    multioption  = String to invoke multiport match if appropriate
#    action	  = The chain for this rule
#    ratelimit    = Optional rate limiting clause
#    userandgroup = owner match clause
#    logtag       = Log tag
#
add_an_action()
{
    local chain1

    do_ports() {
	if [ -n "$port" ]; then
	    dports="--dport"
	    if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then
		multiport="$multioption"
		dports="--dports"
	    fi
	    dports="$dports $port"
	fi

	if [ -n "$cport" ]; then
	    sports="--sport"
	    if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then
		multiport="$multioption"
		sports="--sports"
	    fi
	    sports="$sports $cport"
	fi
    }

    interface_error()
    {
	fatal_error "Unknown interface $1 in rule: \"$rule\""
    }

    action_interface_verify()
    {
	verify_interface $1 || interface_error $1
    }

    handle_exclusion()
    {
	build_exclusion_chain chain1 filter "$excludesource" "$excludedest"

	run_iptables -A $chain $(fix_bang $cli $proto $sports $multiport $dports) $user -j $chain1

	cli=
	proto=
	sports=
	multiport=
	dports=
	user=
    }

    do_ipp2p() {
	[ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. Rule: \"$rule\""

	dports="-m ipp2p --${port:-ipp2p}"

	case $proto in
	    ipp2p|IPP2P)
		proto=tcp
		port=
		do_ports
		;;
	    ipp2p:udpIPP2P:UDP)
		proto=udp
		port=
		do_ports
		;;
	    ipp2p:all|IPP2P:ALL)
		proto=all
		;;
	esac
    }

    # Set source variables. The 'cli' variable will hold the client match predicate(s).

    cli=

    case "$client" in
    -)
	;;
    *:*)
	action_interface_verify ${client%:*}
	cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})"
	;;
    *.*.*|+*|!+*)
	cli="$(source_ip_range $client)"
	;;
    ~*)
	cli=$(mac_match $client)
	;;
    *)
	if [ -n "$client" ]; then
	    action_interface_verify $client
	    cli="$(match_source_dev $client)"
	fi
	;;
    esac

    # Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s).

    dest_interface=
    serv=

    case "$server" in
    -)
	;;
    *.*.*|+*|!+*)
	serv=$server
	;;
    ~*)
	fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address"
	;;
    *)
	if [ -n "$server" ]; then
	    action_interface_verify $server
	    dest_interface="$(match_dest_dev $server)"
	fi
	;;
    esac

    # Setup protocol and port variables

    sports=
    dports=
    proto=$protocol
    servport=$serverport
    multiport=
    chain1=$chain
    user="$userandgroup"

    [ x$port  = x- ] && port=
    [ x$cport = x- ] && cport=

    case $proto in
	tcp|TCP|6)
	    do_ports
	    [ "$target" = QUEUE ] && proto="$proto --syn"
	    ;;
	udp|UDP|17)
	    do_ports
	    ;;
	icmp|ICMP|1)
	    [ -n "$port" ]  && 	dports="--icmp-type $port"
	    ;;
	ipp2p|IPP2P|ipp2p:*|IPP2P:*)
	    do_ipp2p
	    ;;
	*)
	    [ -n "$port" ] && \
		fatal_error "Port number not allowed with protocol \"$proto\"; rule: \"$rule\""
	    ;;
    esac

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

    # Some misc. setup

    case "$logtarget" in
	LOG)
	    [ -z "$loglevel" ] && fatal_error "LOG requires log level"
	    ;;
    esac

    if [ -n "${excludesource}${excludedest}" ]; then
	handle_exclusion
    fi

    if [ -n "${serv}" ]; then
	for serv1 in $(separate_list $serv); do
	    for srv in $(firewall_ip_range $serv1); do
		if [ -n "$loglevel" ]; then
		    log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \
			$(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports)
		fi

		run_iptables2 -A $chain1 $proto $multiport $cli $sports \
		    $(dest_ip_range $srv) $dports $ratelimit $user -j $target
	    done
	done
    else
	if [ -n "$loglevel" ]; then
	    log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \
		$(fix_bang $proto $sports $multiport $cli $dest_interface $dports)
	fi

	run_iptables2 -A $chain1 $proto $multiport $cli $dest_interface $sports \
	    $dports $ratelimit $user -j $target
    fi
}

#
# Process a record from an action file
#
process_action() # $1 = chain    (Chain to add the rules to)
                 # $2 = action   (The action name for logging purposes)
                 # $3 = target   (The (possibly modified) contents of the TARGET column)
                 # $4 = clients
                 # $5 = servers
                 # $6 = protocol
                 # $7 = ports
                 # $8 = cports
                 # $9 = ratelimit
                 # $10 = userspec
{
    local chain="$1"
    local action="$2"
    local target="$3"
    local clients="$4"
    local servers="$5"
    local protocol="$6"
    local ports="$7"
    local cports="$8"
    local ratelimit="$9"
    local userspec="${10}"
    local userandgroup=
    local logtag=

    if [ -n "$ratelimit" ]; then
	case $ratelimit in
	    -)
		ratelimit=
		;;
	    *:*)
		ratelimit="-m limit --limit ${ratelimit%:*} --limit-burst ${ratelimit#*:}"
		;;
	    *)
		ratelimit="-m limit --limit $ratelimit"
		;;
	esac
    fi

    [ "x$userspec" = "x-" ] && userspec=

    if [ -n "$userspec" ]; then
	userandgroup="-m owner"

	case "$userspec" in
	    !*+*)
		if [ -n "${userspec#*+}" ]; then
		    userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}"
		fi
		userspec=${userspec%+*}
		;;
	    *+*)
		if [ -n "${userspec#*+}" ]; then
		    userandgroup="$userandgroup --cmd-owner ${userspec#*+}"
		fi
		userspec=${userspec%+*}
		;;
	esac

	case "$userspec" in
	    !*:*)
		if [ "$userspec" != "!:" ]; then
		    temp="${userspec#!}"
		    temp="${temp%:*}"
		    [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp"
		    temp="${userspec#*:}"
		    [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp"
		fi
		;;
	    *:*)
		if [ "$userspec" != ":" ]; then
		    temp="${userspec%:*}"
		    [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp"
		    temp="${userspec#*:}"
		    [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp"
		fi
		;;
	    !*)
		[ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}"
		;;
	    *)
		[ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec"
		;;
	esac

	[ "$userandgroup" = "-m owner" ] && userandgroup=
    fi

    # Isolate log level

    if [ "$target" = "${target%:*}" ]; then
	loglevel=
    else
	loglevel="${target#*:}"
	target="${target%%:*}"
	expandv loglevel
	if [ "$loglevel" != "${loglevel%:*}" ]; then
	    logtag="${loglevel#*:}"
	    loglevel="${loglevel%:*}"
	    expandv logtag
	fi

	case $loglevel in
	    none*)
		loglevel=
		[ $target = LOG ] && return
		;;
	esac

	loglevel=${loglevel%\!}
    fi

    logtarget="$target"

    case $target in
	REJECT)
	    target=reject
	    ;;
	CONTINUE)
	    target=RETURN
	    ;;
	*)
	    ;;
	esac

    excludesource=

    case ${clients:=-} in
	*!*!*)
	    fatal_error "Invalid SOURCE in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $clients) -gt 1 ]; then
		excludesource=${clients#!}
		clients=
	    fi
	    ;;
	*!*)
	    excludesource=${clients#*!}
	    clients=${clients%!*}
	    ;;
    esac

    excludedest=

    case ${servers:=-} in
	*!*!*)
	    fatal_error "Invalid DEST in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $servers) -gt 1 ]; then
		excludedest=${servers#*!}
		servers=
	    fi
	    ;;
	*!*)
	    excludedest=${servers#*!}
	    servers=${servers%!*}
	    ;;
    esac

    # Generate Netfilter rule(s)

    [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all}

    if [ -n "$XMULTIPORT" ] && \
	! list_search $protocol "icmp" "ICMP" "1" && \
	[ $(( $(list_count $ports)  + $(list_count1 $(split $ports ) ) ))  -le 16 -a \
	  $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ]
	then
	#
	# Extended MULTIPORT is enabled, and less than
	# 16 ports are listed (port ranges count as two ports) - use multiport match.
	#
	multioption="-m multiport"
	for client in $(separate_list $clients); do
	    for server in $(separate_list $servers); do
		#
		# add_an_action() modifies these so we must set their values each time
		#
		port=${ports:=-}
		cport=${cports:=-}
		add_an_action
	    done
	done
    elif [ -n "$MULTIPORT" ] && \
	! list_search $protocol "icmp" "ICMP" "1" && \
	[ "$ports" = "${ports%:*}" -a \
	"$cports" = "${cports%:*}" -a \
	$(list_count $ports) -le 15 -a \
	$(list_count $cports) -le 15 ]
	then
	#
	# MULTIPORT is enabled, there are no port ranges in the rule and less than
	# 16 ports are listed - use multiport match.
	#
	multioption="-m multiport"
	for client in $(separate_list $clients); do
	    for server in $(separate_list $servers); do
		#
		# add_an_action() modifies these so we must set their values each time
		#
		port=${ports:=-}
		cport=${cports:=-}
		add_an_action
	    done
	done
    else
	#
	# MULTIPORT is disabled or the rule isn't compatible with multiport match
	#
	multioption=
	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_an_action
		    done
		done
	    done
	done
    fi
    #
    # Report Result
    #
    progress_message "   Rule \"$rule\" $DONE."
    save_progress_message_short "   Rule \"$rule\" added."
}

#
# Create and record a log action chain -- Log action chains have names
# that are formed from the action name by prepending a "%" and appending
# a 1- or 2-digit sequence number. In the functions that follow,
# the CHAIN, LEVEL and TAG variable serves as arguments to the user's
# exit. We call the exit corresponding to the name of the action but we
# set CHAIN to the name of the iptables chain where rules are to be added.
# Similarly, LEVEL and TAG contain the log level and log tag respectively.
#
# For each <action>, we maintain two variables:
#
#    <action>_actchain - The action chain number.
#    <action>_chains   - List of ( level[:tag] , chainname ) pairs
#
# The maximum length of a chain name is 30 characters -- since the log
# action chain name is 2-3 characters longer than the base chain name,
# this function truncates the original chain name where necessary before
# it adds the leading "%" and trailing sequence number.

createlogactionchain() # $1 = Action Name, $2 = Log Level [: Log Tag ]
{
    local actchain= action=$1 level=$2

    eval actchain=\${${action}_actchain}

    case ${#action} in
	29|30)
	    CHAIN=$(echo $action | truncate 28) # %...n makes 30
	    ;;
	*)
	    CHAIN=${action}
	    ;;
    esac

    while havechain %${CHAIN}${actchain}; do
	actchain=$(($actchain + 1))
	[ $actchain -eq 10 -a ${#CHAIN} -eq 28 ] && CHAIN=$(echo $CHAIN | truncate 27) # %...nn makes 30
    done

    CHAIN=%${CHAIN}${actchain}

    eval ${action}_actchain=$(($actchain + 1))

    createchain $CHAIN No
    LEVEL=${level%:*}
    if [ "$LEVEL" != "$level" ]; then
	TAG=${level#*:}
    else
	TAG=
    fi

    [ none = "${LEVEL%\!}" ] && LEVEL=

    run_user_exit $1

    eval ${action}_chains=\"\$${action}_chains $level $CHAIN\"

}

#
# Create an action chain and run it's associated user exit
#

createactionchain() # $1 = Action, including log level and tag if any
{
    case $1 in
	*::*)
	    fatal_error "Invalid ACTION $1"
	    ;;
	*:*:*)
	    set -- $(split $1)
	    createlogactionchain $1 $2:$3
	    ;;
	*:*)
	    set -- $(split $1)
	    createlogactionchain $1 $2
	    ;;
	*)
	    CHAIN=$1
	    LEVEL=
	    TAG=
	    createchain $CHAIN no
	    run_user_exit $CHAIN
	    ;;
    esac
}

#
# Find the chain that handles the passed action. If the chain cannot be found,
# a fatal error is generated and the function does not return.
#
find_logactionchain() # $1 = Action, including log level and tag if any
{
    local fullaction=$1 action=${1%%:*} level= chains=

    case $fullaction in
	*:*)
	    level=${fullaction#*:}
	    ;;
	*)
	    havechain $action ||  fatal_error "Fatal error in find_logactionchain"
	    echo $action
	    return
	    ;;
    esac

    eval chains="\$${action}_chains"

    set -- $chains

    while [ $# -gt 0 ]; do
	[ "$1" = "$level" ] && { echo $2 ; return ; }
	shift 2
    done

    fatal_error "Fatal error in find_logactionchain"

}

#
# This function determines the logging for a subordinate action or a rule within a subordinate action
#
merge_levels() # $1=level at which superior action is called, $2=level at which the subordinate rule is called
{
    local superior=$1 subordinate=$2

    set -- $(split $1)

    case $superior in
	*:*:*)
	    case $2 in
		'none!')
		    echo ${subordinate%%:*}:'none!':$3
		    return
		    ;;
		*'!')
                    echo ${subordinate%%:*}:$2:$3
		    return
		    ;;
		*)
		    case $subordinate in
			*:*:*)
			    echo $subordinate
			    return
			    ;;
			*:*)
			    echo $subordinate:$3
			    return
			    ;;
			*)
			    echo ${subordinate%%:*}:$2:$3
			    return
			    ;;
		    esac
		    ;;
	    esac
	    ;;
	*:*)
	    case $2 in
		'none!')
		    echo ${subordinate%%:*}:'none!'
		    return
		    ;;
		*'!')
                    echo ${subordinate%%:*}:$2
		    return
		    ;;
		*)
		    case $subordinate in
			*:*)
			    echo $subordinate
			    return
			    ;;
			*)
			    echo ${subordinate%%:*}:$2
			    return
			    ;;
		    esac
		    ;;
	    esac
	    ;;
	*)
	    echo $subordinate
	    ;;
    esac
}

# This function substitutes the second argument for the first part of the first argument up to the first colon (":")
#
# Example:
#
#         substitute_action DNAT PARAM:info:FTP
#
#         produces "DNAT:info:FTP"
#
substitute_action() # $1 = parameter, $2 = action
{
    local logpart=${2#*:}

    case $2 in
	*:*)
	    echo $1:${logpart%/}
	    ;;
	*)
	    echo $1
	    ;;
    esac
}

#
# This function maps old action names into their new macro equivalents
#
map_old_action() # $1 = Potential Old Action
{
    local macro= aktion

    if [ -n "$MAPOLDACTIONS" ]; then
	case $1 in
	    */*)
		echo $1
		return
		;;
	    *)
		if [ -f $(find_file $1) ]; then
		    echo $1
		    return
		fi

		case $1 in
		    Allow*)
			macro=${1#*w}
			aktion=ACCEPT
			;;
		    Drop*)
			macro=${1#*p}
			aktion=DROP
			;;
		    Reject*)
			macro=${1#*t}
			aktion=REJECT
			;;
		    *)
			echo $1
			return
			;;
		esac
	esac

	if [ -f $(find_file macro.$macro) ]; then
	    echo $macro/$aktion
	    return
	fi
    fi

    echo $1
}

#
# Combine a source/dest from the macro body with one from the macro invocation
#
merge_macro_source_dest() # $1 = source/dest from macro body, $2 = source/dest from invocation
{
    case $2 in
	-)
	    echo ${1}
	    ;;
	*.*.*|+*|~*)
	    #
	    # Value in the invocation is an address -- put it behind the value from the macro
	    #
	    echo ${1}:${2}
	    ;;
	*)
	    echo ${2}:${1}
	    ;;
    esac
}

#
# The next three functions implement the three phases of action processing.
#
# The first phase (process_actions1) occurs before the rules file is processed. /usr/share/shorewall/actions.std
# and /etc/shorewall/actions are scanned (in that order) and for each action:
#
#      a) The related action definition file is located and scanned.
#      b) Forward and unresolved action references are trapped as errors.
#      c) A dependency graph is created. For each <action>, the variable 'requiredby_<action>' lists the
#         action[:level[:tag]] of each action invoked by <action>.
#      d) All actions are listed in the global variable ACTIONS.
#      e) Common actions are recorded (in variables of the name <policy>_common) and are added to the global
#         USEDACTIONS
#
# As the rules file is scanned, each action[:level[:tag]] is merged onto the USEDACTIONS list. When an <action>
# is merged onto this list, its action chain is created. Where logging is specified, a chain with the name
# %<action>n is used where the <action> name is truncated on the right where necessary to ensure that the total
# length of the chain name does not exceed 30 characters.
#
# The second phase (process_actions2) occurs after the rules file is scanned. The transitive closure of
# USEDACTIONS is generated; again, as new actions are merged onto this list, their action chains are created.
#
# The final phase (process_actions3) is to traverse the USEDACTIONS list populating each chain appropriately
# by reading the action definition files and creating rules. Note that a given action definition file is
# processed once for each unique [:level[:tag]] applied to an invocation of the action.
#
process_actions1() {

    ACTIONS="dropBcast allowBcast dropNotSyn rejNotSyn dropInvalid allowInvalid allowinUPnP allowoutUPnP forwardUPnP"

    USEDACTIONS=

    strip_file actions

    strip_file actions.std /usr/share/shorewall/actions.std

    for inputfile in actions.std actions; do
	while read xaction rest; do
	    [ "x$rest" = x ] || fatal_error "Invalid Action: $xaction $rest"

	    case $xaction in
		*:*)
		    temp=${xaction#*:}
		    [ ${#temp} -le 30 ] || fatal_error "Action Name Longer than 30 Characters: $temp"
		    xaction=${xaction%:*}
		    case $temp in
			ACCEPT|REJECT|DROP|QUEUE)
			    eval ${temp}_common=$xaction
			    if [ -n "$xaction" ] && ! list_search $xaction $USEDACTIONS; then
				USEDACTIONS="$USEDACTIONS $xaction"
			    fi
			    ;;
			*)
			    startup_error "Common Actions are only allowed for ACCEPT, DROP, REJECT and QUEUE"
			    ;;
		    esac
	    esac

	    [ -z "$xaction" ] && continue

	    [ "$xaction" = "$(chain_base $xaction)" ] || startup_error "Invalid Action Name: $xaction"

	    if ! list_search $xaction $ACTIONS; then
		f=action.$xaction
		fn=$(find_file $f)

		eval requiredby_${action}=

		if [ -f $fn ]; then
		    progress_message2 "   Pre-processing $fn..."
		    strip_file $f $fn
		    while read xtarget xclients xservers xprotocol xports xcports xratelimit $xuserspec; do
			expandv xtarget
			temp="${xtarget%%:*}"
			case "$temp" in
			    ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE)
				;;
			    *)
				if list_search $temp $ACTIONS; then
				    eval requiredby=\"\$requiredby_${xaction}\"
				    list_search $xtarget $requiredby || eval requiredby_${xaction}=\"$requiredby $xtarget\"
				else
				    temp=$(map_old_action $temp)

				    case $temp in
					*/*)
					    param=${temp#*/}
					    case $param in
						ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE)
						    ;;
						*)
						    rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec"
						    startup_error "Invalid Macro Parameter in rule \"$rule\""
						    ;;
					    esac
					    temp=${temp%%/*}
					    ;;
				    esac

				    f1=macro.${temp}
				    fn=$(find_file $f1)

				    if [ ! -f $TMP_DIR/$f1 ]; then
					#
					# We must only verify macros once to ensure that they don't invoke any non-standard actions
					#
					if [ -f $fn ]; then
					    strip_file $f1 $fn

					    progress_message "   ..Expanding Macro $fn..."

					    while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do
						expandv mtarget
						temp="${mtarget%%:*}"
						case "$temp" in
						    ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE|PARAM)
							;;
						    *)
							rule="$mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec"
							startup_error "Invalid TARGET in rule \"$rule\""
						esac
					    done < $TMP_DIR/$f1

					    progress_message "   ..End Macro"
					else
					    rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec"
					    startup_error "Invalid TARGET in rule \"$rule\""
					fi
				    fi
				fi
				;;

			esac
		    done < $TMP_DIR/$f
		else
		    startup_error "Missing Action File: $f"
		fi

		ACTIONS="$ACTIONS $xaction"
	    fi
	done < $TMP_DIR/$inputfile
    done
}

process_actions2() {

    local interfaces="$(find_interfaces_by_option upnp)"

    if [ -n "$interfaces" ]; then
	if ! list_search forwardUPnP $USEDACTIONS; then
	    error_message "WARNING:Missing forwardUPnP rule (required by 'upnp' interface option on $interfaces)"
	    USEDACTIONS="$USEDACTIONS forwardUPnP"
	fi
    fi

    progress_message "   Generating Transitive Closure of Used-action List..."

    changed=Yes

    while [ -n "$changed" ]; do
	changed=
	for xaction in $USEDACTIONS; do

	    eval required=\"\$requiredby_${xaction%%:*}\"

	    for xaction1 in $required; do
		#
		# Generate the action that will be passed to process_action by merging the
		# logging specified when the action was invoked with the logging in the
		# invocation of the subordinate action (usually no logging)
		#
		xaction2=$(merge_levels $xaction $xaction1)

		if ! list_search $xaction2 $USEDACTIONS; then
		    #
		    # We haven't seen this one before -- create and record a chain to handle it
		    #
		    USEDACTIONS="$USEDACTIONS $xaction2"
		    createactionchain $xaction2
		    changed=Yes
		fi
	    done
	done
    done
}

process_actions3() {

    for xaction in $USEDACTIONS; do
	#
	# Find the chain associated with this action:level:tag
	#
	xchain=$(find_logactionchain $xaction)
	#
	# Split the action:level:tag
	#
        set -- $(split $xaction)

	xaction1=$1
	xlevel=$2
	xtag=$3

	save_progress_message "Creating action chain $xaction1"

	#
	# Handle Builtin actions
	#
	case $xaction1 in
	    dropBcast)
		if [ -n "$USEPKTTYPE" ]; then
		    case $xlevel in
			none'!')
			    ;;
			*)
			    if [ -n "$xlevel" ]; then
				log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type broadcast
				log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type multicast
			    fi
			    ;;
		    esac

		    run_iptables -A dropBcast -m pkttype --pkt-type broadcast -j DROP
		    run_iptables -A dropBcast -m pkttype --pkt-type multicast -j DROP
		else
		    for interface in $(find_bcastdetect_interfaces); do
			cat >> $RESTOREBASE << __EOF__

${INDENT}ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do
__EOF__
			    case $xlevel in
				none*)
				    ;;
				*)
				    [ -n "$xlevel" ] && \
					cat >> $RESTOREBASE << __EOF__
${INDENT}   log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d \$address
__EOF__
				    ;;
			    esac

			    cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -A $xchain -d \$address -j DROP
${INDENT}done

__EOF__
		    done

		    for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do
			case $xlevel in
			    none*)
				;;
			    *)
				[ -n "$xlevel" ] && \
				    log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d $address
				;;
			esac

			run_iptables -A $xchain -d $address -j DROP
		    done
		fi
		;;
	    allowBcast)
		if [ -n "$USEPKTTYPE" ]; then
		    case $xlevel in
			none'!')
			    ;;
			*)
			    if [ -n "$xlevel" ]; then
				log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type broadcast
				log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type multicast
			    fi
			    ;;
		    esac

		    run_iptables -A allowBcast -m pkttype --pkt-type broadcast -j ACCEPT
		    run_iptables -A allowBcast -m pkttype --pkt-type multicast -j ACCEPT
		else
		    for interface in $(find_bcastdetect_interfaces); do
			cat >> $RESTOREBASE << __EOF__

${INDENT}ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do
__EOF__
			    case $xlevel in
				none*)
				    ;;
				*)
				    [ -n "$xlevel" ] && \
					cat >> $RESTOREBASE << __EOF__
${INDENT}   log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -d \$address
__EOF__
				    ;;
			    esac

			    cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -A $xchain -d \$address -j
${INDENT}done

__EOF__
		    done

		    for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do
			case $xlevel in
			    none*)
				;;
			    *)
				[ -n "$xlevel" ] && \
				    log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -d $address
				;;
			esac

			run_iptables -A $xchain -d $address -j ACCEPT
		    done
		fi
		;;
	    dropNotSyn)
		[ -n "$xlevel" ] && \
		    log_rule_limit ${xlevel%\!} $xchain dropNotSyn DROP "" "$xtag" -A -p tcp ! --syn
		run_iptables -A $xchain -p tcp ! --syn -j DROP
		;;
	    rejNotSyn)
		[ -n "$xlevel" ] && \
		    log_rule_limit ${xlevel%\!} $xchain rejNotSyn REJECT "" "$xtag" -A -p tcp ! --syn
		run_iptables -A $xchain -p tcp ! --syn -j REJECT --reject-with tcp-reset
		;;
	    dropInvalid)
		[ -n "$xlevel" ] && \
		    log_rule_limit ${xlevel%\!} $xchain dropInvalid DROP "" "$xtag" -A -m state --state INVALID
		run_iptables -A $xchain -m state --state INVALID -j DROP
		;;
	    allowInvalid)
		[ -n "$xlevel" ] && \
		    log_rule_limit ${xlevel%\!} $xchain allowInvalid ACCEPT "" "$xtag" -A -m state --state INVALID
		run_iptables -A $xchain -m state --state INVALID -j ACCEPT
		;;
	    forwardUPnP)
		;;
	    allowinUPnP)
		if [ -n "$xlevel" ]; then
		    log_rule_limit ${xlevel%\!} $xchain allowinUPnP ACCEPT "" "$xtag" -A -p udp --dport 1900
		    log_rule_limit ${xlevel%\!} $xchain allowinUPnP ACCEPT "" "$xtag" -A -p tcp --dport 49152
		fi

		run_iptables -A $xchain -p udp --dport 1900  -j ACCEPT
		run_iptables -A $xchain -p tcp --dport 49152 -j ACCEPT
		;;
	    allowoutUPnP)
		[ -n "$xlevel" ] && \
		    log_rule_limit ${xlevel%\!} $xchain allowoutUPnP ACCEPT "" "$xtag" -A -m owner --owner-cmd upnpd
		run_iptables -A $xchain -m owner --cmd-owner upnpd -j ACCEPT
		;;
	    *)
		#
		# Not a builtin
		#
		f=action.$xaction1

		progress_message2 "$DOING $(find_file $f) for Chain $xchain..."

		while read xtarget xclients xservers xprotocol xports xcports xratelimit xuserspec; do
		    expandv xtarget
		    #
		    # Generate the target:level:tag to pass to process_action()
		    #
		    xaction2=$(merge_levels $xaction $xtarget)

		    is_macro=
		    param=

		    xtarget1=${xaction2%%:*}

		    case $xtarget1 in
			ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE)
			    #
			    # Builtin target -- Nothing to do
			    #
			    ;;
			*)
			    if list_search $xtarget1 $ACTIONS ; then
			        #
			        # An Action             -- Replace the target from the file
			        #                       -- with the one generated above
				xtarget=$xaction2
			        #
			        # And locate the chain for that action:level:tag
			        #
			        xaction2=$(find_logactionchain $xtarget)
			    else
				is_macro=yes
			    fi
			    ;;
		    esac

		    expandv xclients xservers xprotocol xports xcports xratelimit xuserspec

		    if [ -n "$is_macro" ]; then

			xtarget1=$(map_old_action $xtarget1)

			case $xtarget1 in
			    */*)
				param=${xtarget1#*/}
				xtarget1=${xtarget1%%/*}
				;;
			esac

			progress_message "..Expanding Macro $(find_file macro.$xtarget1)..."
			while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do
			    expandv mtarget mclients mservers mprotocol mports mcports mratelimit muserspec

			    mtarget=$(merge_levels $xaction2 $mtarget)

			    case $mtarget in
				PARAM|PARAM:*)
				    [ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation"
				    ;;
			    esac

			    if [ -n "$mclients" ]; then
				case $mclients in
				    -)
					mclients=${xclients}
					;;
				    *)
					mclients=$(merge_macro_source_dest $mclients $xclients)
					;;
				esac
			    else
				mclients=${xclients}
			    fi

			    if [ -n "$mservers" ]; then
				case $mservers in
				    -)
					mservers=${xservers}
					;;
				    *)
					mservers=$(merge_macro_source_dest $mservers $xservers)
					;;
				esac
			    else
				mservers=${xserverss}
			    fi

			    [ -n "$xprotocol" ]  && [ "x${xprotocol}" != x- ]  && mprotocol=$xprotocol
			    [ -n "$xports" ]     && [ "x${xports}" != x- ]     && mports=$xports
			    [ -n "$xcports" ]    && [ "x${xcports}" != x- ]    && mcports=$xcports
			    [ -n "$xratelimit" ] && [ "x${xratelimit}" != x- ] && mratelimit=$xratelimit
			    [ -n "$xuserspec" ]  && [ "x${xuserspec}" != x- ]  && muserspec=$xuserspec

			    rule="$mtarget ${mclients:=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${mratelimit:-} ${muserspec:=-}"
			    process_action $xchain $xaction1 $mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec
			done < $TMP_DIR/macro.$xtarget1
			progress_message "..End Macro"
		    else
			rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec"
			process_action $xchain $xaction1 $xaction2 $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec
		    fi
		done < $TMP_DIR/$f
		;;
	esac
    done
}

#
# Add a NAT rule - Helper function for the rules file processor
#
# The caller has established the following variables:
#    COMMAND        = The current command
#                     the motions.
#    cli	    = Source IP, interface or MAC Specification
#    serv	    = Destination IP Specification
#    servport	    = Port the server is listening on
#    dest_interface = Destination Interface Specification
#    proto	    = Protocol Specification
#    addr	    = Original Destination Address
#    dports	    = Destination Port Specification. 'dports' may be changed
#		      by this function
#    cport	    = Source Port Specification
#    multiport	    = String to invoke multiport match if appropriate
#    ratelimit      = Optional rate limiting clause
#    userandgroup   = -m owner match to limit the rule to a particular user and/or group
#    logtag         = Log tag
#    excludesource  = Source Exclusion List
#
add_nat_rule() {
    local chain
    local excludedests=

    # Be sure we can NAT

    if [ -z "$NAT_ENABLED" ]; then
	fatal_error "Rule \"$rule\" requires NAT which is disabled"
    fi

    # Parse SNAT address if any

    if [ "$addr" != "${addr%:*}" ]; then
	fatal_error "SNAT may no longer be specified in a DNAT rule; use /etc/shorewall/masq instead"
    fi

    # Set original destination address

    case $addr in
	all)
	    addr=
	    ;;
	detect)
	    eval interfaces=\$${source}_interfaces

	    if [ -n "$DETECT_DNAT_IPADDRS" -a "$source" != "$FW" ]; then

		save_command ""
		if [ $(list_count1 $interfaces) -eq 1 ]; then
		    save_command "addr=\$(find_first_interface_address $interface)"
		else
		    savecomment "addr="
		    for interface in $interfaces; do
			    cat >> $RESTOREBASE << __EOF__
${INDENT}addr="\$addr \$(find_first_interface_address $interface)"
__EOF__
		    done
		fi
	    fi
	    ;;
	!*)
	    if [ $(list_count $addr) -gt 1 ]; then
		excludedests="${addr#\!}"
		addr=
	    fi
	    ;;
    esac

    addr=${addr:-0.0.0.0/0}

    # Select target

    if [ "$logtarget" = SAME ]; then
	[ -n "$servport" ] && fatal_error "Port mapping not allowed in SAME rules"
	serv1=
	for srv in $(separate_list $serv); do
	    serv1="$serv1 --to ${srv}"
	done
	target1="SAME $serv1"
    elif [ -n "$serv" ]; then
	servport="${servport:+:$servport}"
	serv1=
	for srv in $(separate_list $serv); do
	    serv1="$serv1 --to-destination ${srv}${servport}"
	done
	target1="DNAT $serv1"
    else
	target1="REDIRECT --to-port $servport"
    fi

    if [ $source = $FW ]; then
	[ -n "$excludezones" ] && fatal_error "Invalid Source in rule \"$rule\""
    fi

    # Generate nat table rules

    if [ "$source" = "$FW" ]; then
	if [ -n "${excludesource}${excludedests}" ]; then
	    build_exclusion_chain chain nat "$excludesource" $excludedests

	    for adr in $(separate_list $addr); do
		run_iptables2 -t nat -A OUTPUT $cli $proto $userandgroup $multiport $sports $dports $(dest_ip_range $adr) -j $chain
	    done

	    if [ -n "$loglevel" ]; then
		log_rule_limit $loglevel $chain OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat
	    fi

	    addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection
	else
	    for adr in $(separate_list $addr); do
		if [ -n "$loglevel" ]; then
		    log_rule_limit $loglevel OUTPUT OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat \
			$(fix_bang $proto $cli $sports $userandgroup $(dest_ip_range $adr) $multiport $dports)
		fi

		run_iptables2 -t nat -A OUTPUT $ratelimit $proto $sports $userandgroup $(dest_ip_range $adr) $multiport $dports -j $target1
	    done
	fi
    else
	if [ -n "${excludesource}${excludedests}${excludezones}" ]; then
	    build_exclusion_chain chain nat "$excludesource" $excludedests

	    if [ $addr = detect ]; then
		ensurenatchain $(dnat_chain $source)

		cat >> $RESTOREBASE << __EOF__

${INDENT}for adr in \$addr; do
${INDENT}    run_iptables -t nat -A $(fix_bang $(dnat_chain $source) $cli $proto $multiport $sports $dports) -d \$adr -j $chain
__EOF__
	    else
		for adr in $(separate_list $addr); do
		    addnatrule $(dnat_chain $source) $cli $proto $multiport $sports $dports $(dest_ip_range $adr) -j $chain
		done
	    fi

	    for z in $(separate_list $excludezones); do
		eval hosts=\$${z}_hosts
		for host in $hosts; do
		    addnatrule $chain $(match_source_hosts ${host#*:}) -j RETURN
		done
	    done

	    if [ -n "$loglevel" ]; then
		log_rule_limit $loglevel $chain $(dnat_chain $source) $logtarget "$ratelimit" "$logtag" -A -t nat
	    fi

	    addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection
	else
	    chain=$(dnat_chain $source)

	    if [ $addr = detect ]; then
		ensurenatchain $chain

		cat >> $RESTOREBASE << __EOF__

${INDENT}for adr in \$addr; do
__EOF__
		if [ -n "$loglevel" ]; then
		    cat >> $RESTOREBASE << __EOF__
${INDENT}    log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A -t nat $(fix_bang $proto $cli $sports $multiport $dports) -d \$adr
__EOF__
		fi

		cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -t nat -A $chain $(fix_bang $proto $ratelimit $cli $sports $multiport $dports) -d \$adr -j $target1
__EOF__
	    else
		for adr in $(separate_list $addr); do
		    if [ -n "$loglevel" ]; then
			ensurenatchain $chain
			log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A -t nat \
			    $(fix_bang $proto $cli $sports $(dest_ip_range $adr) $multiport $dports)
		    fi

		    addnatrule $chain $proto $ratelimit $cli $sports \
			-d $adr $multiport $dports -j $target1
		done
	    fi
	fi
    fi

    # Replace destination port by the new destination port

    if [ -n "$servport" ]; then
	if [ -z "$multiport" ]; then
	    dports="--dport ${servport#*:}"
	else
	    dports="--dports ${servport#*:}"
	fi
    fi

    [ "x$addr" = "x0.0.0.0/0" ] && addr=
    ratelimit=
}

#
# Process a record from the rules file
#
process_rule() # $1 = target
               # $2 = clients
               # $3 = servers
               # $4 = protocol
               # $5 = ports
               # $6 = cports
               # $7 = address
               # $8 = ratelimit
               # $9 = userspec
{
    local target="$1"
    local clients="$2"
    local servers="$3"
    local protocol="$4"
    local ports="$5"
    local cports="$6"
    local address="$7"
    local ratelimit="$8"
    local userspec="$9"
    local userandgroup=
    local logtag=
    local nonat=
    #
    # Add one Filter Rule
    #
    # The caller has established the following variables:
    #	 COMMAND	= current command.
    #			  which only goes through the motions.
    #	 client		= SOURCE IP or MAC
    #	 server		= DESTINATION IP or interface
    #	 protocol	= Protocol
    #	 address	= Original Destination Address
    #	 port		= Destination Port
    #	 cport		= Source Port
    #	 multioption	= String to invoke multiport match if appropriate
    #	 servport	= Port the server listens on
    #	 chain		= The canonical chain for this rule
    #	 logchain	= The chain that should be mentioned in log messages
    #	 ratelimit	= Optional rate limiting clause
    #	 userandgroup	= -m owner clause
    #	 userspec	= User name
    #	 logtag		= Log tag
    #	 policy		= Applicable Policy
    #
    add_a_rule()
    {
	local natrule=

	do_ports() {
	    if [ -n "$port" ]; then
		dports="--dport"
		if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then
		    multiport="$multioption"
		    dports="--dports"
		fi
		dports="$dports $port"
	    fi

	    if [ -n "$cport" ]; then
		sports="--sport"
		if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then
		    multiport="$multioption"
		    sports="--sports"
		fi
		sports="$sports $cport"
	    fi
	}

	interface_error()
	{
	    fatal_error "Unknown interface $1 in rule: \"$rule\""
	}

	rule_interface_verify()
	{
	    verify_interface $1 || interface_error $1
	}

	handle_exclusion()
	{
	    build_exclusion_chain chain filter "$excludesource" "$excludedest"

	    if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then
		for adr in $(separate_list $addr); do
		    run_iptables -A $logchain $state $(fix_bang $proto $sports $multiport $dports) $user -m conntrack --ctorigdst $adr -j $chain
		done
		addr=
	    else
		run_iptables -A $state $logchain $(fix_bang $cli $proto $sports $multiport $dports) $user -j $chain
	    fi

	    cli=
	    proto=
	    sports=
	    multiport=
	    dports=
	    user=
	    state=
	}

	do_ipp2p() {
	    [ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. Rule: \"$rule\""

	    dports="-m ipp2p --${port:-ipp2p}"

	    case $proto in
		ipp2p|IPP2P|ipp2p:tcp|IPP2P:TCP)
		    port=
		    proto=tcp
		    do_ports
		    ;;
		ipp2p:udp|IPP2P:UDP)
		    port=
		    proto=udp
		    do_ports
		    ;;
		ipp2p:all|IPP2P:ALL)
		    port=
		    proto=all
		    ;;
		*)
		    fatal_error "Invalid IPP2P protocol ${proto#*:}. Rule:  \"$rule\""
		    ;;
	    esac
	}

	# Set source variables. The 'cli' variable will hold the client match predicate(s).

	cli=

	case "$client" in
	    -)
		;;
	    *:*)
		rule_interface_verify ${client%:*}
		cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})"
		;;
	    *.*.*|+*)
		cli="$(source_ip_range $client)"
		;;
	    ~*)
		cli=$(mac_match $client)
		;;
	    *)
		if [ -n "$client" ]; then
		    rule_interface_verify $client
		    cli="$(match_source_dev $client)"
		fi
		;;
	esac

	# Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s).

	dest_interface=
	serv=

	case "$server" in
	    -)
		;;
	    *.*.*|+*)
		serv=$server
		;;
	    ~*)
		fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address"
		;;
	    *)
		if [ -n "$server" ]; then
		    [ -n "$nonat" ] && fatal_error "Destination interface not allowed with $logtarget"
		    rule_interface_verify $server
		    dest_interface="$(match_dest_dev $server)"
		fi
		;;
	esac

	# Setup protocol and port variables

	sports=
	dports=
	proto=$protocol
	addr=$address
	servport=$serverport
	multiport=
	user="$userandgroup"

	# Restore $chain to the canonical chain.

	chain=$logchain

	[ x$port  = x- ] && port=
	[ x$cport = x- ] && cport=

	case $proto in
	    tcp|TCP|6)
		do_ports
		[ "$target" = QUEUE ] && proto="$proto --syn"
		;;
	    udp|UDP|17)
		do_ports
		;;
	    icmp|ICMP|1)
		[ -n "$port" ]  && 	dports="--icmp-type $port"
		;;
	    all|ALL)
		[ -n "$port" ] && \
		    fatal_error "Port number not allowed with protocol \"all\"; rule: \"$rule\""
		proto=
		;;
	    ipp2p|IPP2P|ipp2p:*|IPP2P:*)
		do_ipp2p
		;;
	    *)
		[ -n "$port" ] && \
		    fatal_error "Port number not allowed with protocol \"$proto\"; rule: \"$rule\""
		;;
	esac

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

	# Some misc. setup

	case "$logtarget" in
	    ACCEPT|DROP|REJECT|CONTINUE)
		if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" -a -z "$user" -a -z "$excludesource" -a -z "$excludedest" ] ; then
		    error_message "WARNING -- Rule \"$rule\" is a POLICY"
		    error_message "	   -- and should be moved to the policy file"
		fi
		;;
	    REDIRECT)
		[ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\""

		[ -n "$serv" ] && \
		    fatal_error "REDIRECT rules cannot  specify a server IP; rule: \"$rule\""
		servport=${servport:=$port}
		natrule=Yes
		;;
	    DNAT|SAME)
		[ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\""

		[ -n "$serv" ] || \
		    fatal_error "$logtarget rules require a server address; rule: \"$rule\""
		natrule=Yes
		;;
	    LOG)
		[ -z "$loglevel" ] && \
		    fatal_error "LOG requires log level"
		;;
	esac

	case $SECTION in
	    ESTABLISHED|RELATED)
		state="-m state --state $SECTION"
		;;
	    *)
		state=
		;;
	esac

	if [ -n "${serv}${servport}" ]; then

	    # A specific server or server port given

	    if [ -n "$natrule" ]; then
		add_nat_rule
		[ $policy = ACCEPT ] && return
	    elif [ -n "$servport" -a "$servport" != "$port" ]; then
		fatal_error "Only DNAT, SAME and REDIRECT rules may specify destination port mapping; rule \"$rule\""
	    fi

	    if [ -n "${excludesource}${excludedest}" ]; then
		handle_exclusion
	    fi

	    if [ -z "$dnat_only" ]; then
		if [ -n "$serv" ]; then
		    for serv1 in $(separate_list $serv); do
			for srv in $(firewall_ip_range $serv1); do
			    if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then
				if [ "$addr" = detect ]; then
				    cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -A $chain $state $proto $ratelimit $multiport $cli $sports $(dest_ip_range $srv) $dports -m conntrack --ctorigdst \$adr $user -j $target
${INDENT}done

__EOF__
				else
				    for adr in $(separate_list $addr); do
					if [ -n "$loglevel" -a -z "$natrule" ]; then
					    log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A -m conntrack --ctorigdst $adr \
						$user $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) $state
					fi

					run_iptables2 -A $chain $state $proto $ratelimit $multiport $cli $sports \
					    $(dest_ip_range $srv) $dports -m conntrack --ctorigdst $adr $user -j $target
				    done
				fi
			    else
				if [ -n "$loglevel" -a -z "$natrule" ]; then
				    log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
					    $state $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports)
				fi

				if [ -n "$nonat" ]; then
				    addnatrule $(dnat_chain $source) $proto $multiport \
					$cli $sports $(dest_ip_range $srv) $dports $ratelimit $user -j RETURN
				fi

				if [ "$logtarget" != NONAT ]; then
				    run_iptables2 -A $chain $state $proto $multiport $cli $sports \
					$(dest_ip_range $srv) $dports $ratelimit $user -j $target
				fi
			    fi
			done
		    done
		else
		    if [ -n "$loglevel" -a -z "$natrule" ]; then
			log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
				$state $(fix_bang $proto $sports $multiport $cli $dports)
		    fi

		    [ -n "$nonat" ] && \
			addnatrule $(dnat_chain $source) $proto $multiport \
			$cli $sports $dports $ratelimit $user -j RETURN

			[ "$logtarget" != NONAT ] && \
			    run_iptables2 -A $chain $state $proto $multiport $cli $sports \
			    $dports $ratelimit $user -j $target
		fi
	    fi
	else

	# Destination is a simple zone

	    if [ -n "${excludesource}${excludedest}" ]; then
		handle_exclusion
	    fi

	    if [ -n "$addr" ]; then
		for adr in $(separate_list $addr); do
		    if [ -n "$loglevel" ]; then
			log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
				$state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports -m conntrack --ctorigdst $adr)
		    fi

		    if [ "$logtarget" != LOG ]; then
			if [ -n "$nonat" ]; then
			    addnatrule $(dnat_chain $source) $proto $multiport \
				$cli $sports $dports $ratelimit $user  -m conntrack --ctorigdst $adr -j RETURN
			fi

			if [ "$logtarget" != NONAT ]; then
			    run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \
				$sports $dports $ratelimit $user  -m conntrack --ctorigdst $adr -j $target
			fi
		    fi
		done
	    else
		if [ -n "$loglevel" ]; then
		    log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
			    $state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports)
		fi

		if [ "$logtarget" != LOG ]; then
		    if [ -n "$nonat" ]; then
			addnatrule $(dnat_chain $source) $proto $multiport \
			    $cli $sports $dports $ratelimit $user -j RETURN
		    fi

		    if [ "$logtarget" != NONAT ]; then
			run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \
			    $sports $dports $ratelimit $user -j $target
		    fi
		fi
	    fi
	fi
    }

    # # # # # F u n c t i o n   B o d y # # # # #

    [ "x$ratelimit" = "x-" ] && ratelimit=

    if [ -n "$ratelimit" ]; then
	case $ratelimit in
	    *:*)
		ratelimit="-m limit --limit ${ratelimit%:*} --limit-burst ${ratelimit#*:}"
		;;
	    *)
		ratelimit="-m limit --limit $ratelimit"
		;;
	esac
    fi

    # Isolate log level

    if [ "$target" = "${target%:*}" ]; then
	loglevel=
    else
	loglevel="${target#*:}"
	target="${target%%:*}"
	expandv loglevel
	if [ "$loglevel" != "${loglevel%:*}" ]; then
	    logtag="${loglevel#*:}"
	    loglevel="${loglevel%:*}"
	    expandv logtag
	fi

	case $loglevel in
	    none*)
		loglevel=
		[ $target = LOG ] && return
		;;
	esac

	loglevel=${loglevel%\!}
    fi
    #
    # Save the original target in 'logtarget' for logging rules
    #
    logtarget=${target%-}
    #
    # Targets ending in "-" only apply to the nat table
    #
    [ $target = $logtarget ] && dnat_only= || dnat_only=Yes

    # Tranform the rule:
    #
    # - parse the user specification
    # - set 'target' to the filter table target.
    # - make $FW the destination for REDIRECT
    # - remove '-' suffix from logtargets while setting 'dnat_only'
    # - clear 'address' if it has been set to '-'

    [ "x$userspec" = x- ]   && userspec=
    [ "x$address" = "x-" ] && address=

    if [ -n "$userspec" ]; then

	userandgroup="-m owner"

	case "$userspec" in
	    !*+*)
		if [ -n "${userspec#*+}" ]; then
		    userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}"
		fi
		userspec=${userspec%+*}
		;;
	    *+*)
		if [ -n "${userspec#*+}" ]; then
		    userandgroup="$userandgroup --cmd-owner ${userspec#*+}"
		fi
		userspec=${userspec%+*}
		;;
	esac

	case "$userspec" in
	    !*:*)
		if [ "$userspec" != "!:" ]; then
		    temp="${userspec#!}"
		    temp="${temp%:*}"
		    [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp"
		    temp="${userspec#*:}"
		    [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp"
		fi
		;;
	    *:*)
		if [ "$userspec" != ":" ]; then
		    temp="${userspec%:*}"
		    [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp"
		    temp="${userspec#*:}"
		    [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp"
		fi
		;;
	    !*)
		[ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}"
		;;
	    *)
		[ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec"
		;;
	esac

	[ "$userandgroup" = "-m owner" ] && userandgroup=
    fi

    case $target in
	ACCEPT+|NONAT)
	    [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
	    nonat=Yes
	    target=ACCEPT
	    ;;
	ACCEPT|LOG)
	    ;;
	DROP)
	    [ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with DROP"
	    ;;
	REJECT)
	    target=reject
	    ;;
	CONTINUE)
	    target=RETURN
	    ;;
	DNAT*|SAME*)
	    [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
	    target=ACCEPT
	    address=${address:=detect}
	    ;;
	REDIRECT*)
	    [ $SECTION = NEW ] || fatal_error "REDIRECT rules are not allowed in the $SECTION SECTION"
	    target=ACCEPT
	    address=${address:=all}
	    if [ "x-" = "x$servers" ]; then
		servers=$FW
	    else
		servers="$FW::$servers"
	    fi
	    ;;
	*-)
	    [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
	    ;;
    esac

    # Parse and validate source

    if [ "$clients" = "${clients%:*}" ]; then
	clientzone="$clients"
	clients=
    else
	clientzone="${clients%%:*}"
	clients="${clients#*:}"
	[ -z "$clientzone" -o -z "$clients" ] && \
	   fatal_error "Empty source zone or qualifier: rule \"$rule\""
    fi

    excludesource=

    case $clients in
	*!*!*)
	    fatal_error "Invalid SOURCE in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $clients) -gt 1 ]; then
		excludesource=${clients#!}
		clients=
	    fi
	    ;;
	*!*)
	    excludesource=${clients#*!}
	    clients=${clients%!*}
	    ;;
    esac

    if [ "$clientzone" = "${clientzone%!*}" ]; then
 	excludezones=
    else
	excludezones="${clientzone#*!}"
	clientzone="${clientzone%!*}"

	case $logtarget in
 	    DNAT|REDIRECT|SAME)
 		;;
 	    *)
 		fatal_error "Exclude zone only allowed with DNAT, SAME or REDIRECT"
 		;;
 	esac
    fi

    validate_zone $clientzone || fatal_error "Undefined Client Zone in rule \"$rule\""

    # Parse and validate destination

    source=$clientzone

    if [ $source = $FW ]; then
	source_hosts=
    elif [ -n "$userspec" ]; then
	fatal_error "Invalid use of a user-qualification: rule \"$rule\""
    else
	eval source_hosts=\"\$${source}_hosts\"
    fi

    if [ "$servers" = "${servers%:*}" ] ; then
	serverzone="$servers"
	servers=
	serverport=
    else
	serverzone="${servers%%:*}"
	servers="${servers#*:}"
	if [ "$servers" != "${servers%:*}" ] ; then
	    serverport="${servers#*:}"
	    servers="${servers%:*}"
	    [ -z "$serverzone" -o -z "$serverport" ] && \
	        fatal_error "Empty destination zone or server port: rule \"$rule\""
	    if [ $(list_count $servers) -gt 1 ]; then
		case $servers in
		    !*)
			fatal_error "Exclude lists not supported in the DEST column"
			;;
		esac
	    fi
	else
	    serverport=
	    [ -z "$serverzone" -o -z "$servers" ] && \
	        fatal_error "Empty destination zone or qualifier: rule \"$rule\""
	fi
    fi

    excludedest=

    case $servers in
	*!*!*)
	    fatal_error "Invalid DEST in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $servers) -gt 1 ]; then
		excludedest=${servers#*!}
		servers=
	    fi
	    ;;
	*!*)
	    excludedest=${servers#*!}
	    servers=${servers%!*}
	    ;;
    esac

    if ! validate_zone $serverzone; then
	fatal_error "Undefined Server Zone in rule \"$rule\""
    fi

    dest=$serverzone

    # Ensure that this rule doesn't apply to a NONE policy pair of zones

    chain=${source}2${dest}

    # If we have one or more exclusion lists, we will create a new chain and
    # store it's name in 'chain'. We still want log rules to reflect the
    # canonical chain so we store it's name in $logchain.

    logchain=$chain

    eval policy=\$${chain}_policy

    [ -z "$policy" ] && \
	fatal_error "No policy defined from zone $source to zone $dest"

    [ $policy = NONE ] && \
	fatal_error "Rules may not override a NONE policy: rule \"$rule\""

    [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all}

    ensurechain $chain

    # Generate Netfilter rule(s)

    case $logtarget in
	DNAT*|SAME)

	    if [ -n "$XMULTIPORT" ] && \
	       ! list_search $protocol "icmp" "ICMP" "1" && \
	       [ $(( $(list_count $ports)  + $(list_count1 $(split $ports ) ) ))  -le 16 -a \
		 $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ]
		then
	        #
	        # Extended MULTIPORT is enabled, and less than
	        # 16 ports are listed (port ranges count as two ports) - use multiport match.
	        #
		multioption="-m multiport"
		for client in $(separate_list ${clients:=-}); do
		    #
		    # add_a_rule() modifies these so we must set their values each time
		    #
		    server=${servers:=-}
		    port=${ports:=-}
		    cport=${cports:=-}
		    add_a_rule
		done
	    elif [ -n "$MULTIPORT" ] && \
	       ! list_search $protocol "icmp" "ICMP" "1" && \
	       [ "$ports" = "${ports%:*}" -a \
		"$cports" = "${cports%:*}" -a \
		$(list_count $ports) -le 15 -a \
		$(list_count $cports) -le 15 ]
		then
	        #
	        # MULTIPORT is enabled, there are no port ranges in the rule and less than
	        # 16 ports are listed - use multiport match.
	        #
		multioption="-m multiport"
		for client in $(separate_list ${clients:=-}); do
		    #
		    # add_a_rule() modifies these so we must set their values each time
		    #
		    server=${servers:=-}
		    port=${ports:=-}
		    cport=${cports:=-}
		    add_a_rule
		done
	    else
	        #
	        # MULTIPORT is disabled or the rule isn't compatible with multiport match
	        #
		multioption=
		for client in $(separate_list ${clients:=-}); do
		    for port in $(separate_list ${ports:=-}); do
			for cport in $(separate_list ${cports:=-}); do
			    server=${servers:=-}
			    add_a_rule
			done
		    done
		done
	    fi
	    ;;
	*)

	    if [ -n "$XMULTIPORT" ] && \
	       ! list_search $protocol "icmp" "ICMP" "1" && \
	       [ $(( $(list_count $ports)  + $(list_count1 $(split $ports ) ) ))  -le 16 -a \
		 $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16  ]
		then
	        #
	        # Extended MULTIPORT is enabled, and less than
	        # 16 ports are listed (port ranges count as two ports) - use multiport match.
	        #
		multioption="-m multiport"
		for client in $(separate_list ${clients:=-}); do
		    for server in $(separate_list ${servers:=-}); do
		        #
		        # add_a_rule() modifies these so we must set their values each time
		        #
			port=${ports:=-}
			cport=${cports:=-}
			add_a_rule
		    done
		done
	    elif [ -n "$MULTIPORT" ] && \
	       ! list_search $protocol "icmp" "ICMP" "1" && \
	       [ "$ports" = "${ports%:*}" -a \
		"$cports" = "${cports%:*}" -a \
		$(list_count $ports) -le 15 -a \
		$(list_count $cports) -le 15 ]
		then
	        #
	        # MULTIPORT is enabled, there are no port ranges in the rule and less than
	        # 16 ports are listed - use multiport match.
	        #
		multioption="-m multiport"
		for client in $(separate_list ${clients:=-}); do
		    for server in $(separate_list ${servers:=-}); do
		        #
		        # add_a_rule() modifies these so we must set their values each time
		        #
			port=${ports:=-}
			cport=${cports:=-}
			add_a_rule
		    done
		done
	    else
	        #
	        # MULTIPORT is disabled or the rule isn't compatible with multiport match
	        #
		multioption=
		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
	    fi
	    ;;
    esac
    #
    # Report Result
    #
	progress_message "   Rule \"$rule\" $DONE."
	save_progress_message_short "   Rule \"$rule\" added."
}

#
# Process a macro invocation in the rules file
#

process_macro() # $1 = target
               # $2 = param
               # $2 = clients
               # $3 = servers
               # $4 = protocol
               # $5 = ports
               # $6 = cports
               # $7 = address
               # $8 = ratelimit
               # $9 = userspec
{
    local itarget="$1"
    local param="$2"
    local iclients="$3"
    local iservers="$4"
    local iprotocol="$5"
    local iports="$6"
    local icports="$7"
    local iaddress="$8"
    local iratelimit="$9"
    local iuserspec="${10}"

    progress_message "..Expanding Macro $(find_file macro.${itarget%%:*})..."

    while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do
	expandv mtarget mclients mservers mprotocol mports mcports mratelimit muserspec

	mtarget=$(merge_levels $itarget $mtarget)

	case $mtarget in
	    PARAM|PARAM:*)
		[ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation"
		;;
	esac

	case ${mtarget%%:*} in
	    ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-)
		;;
	    *)
		if list_search ${mtarget%%:*} $ACTIONS; then
		    if ! list_search $mtarget $USEDACTIONS; then
			createactionchain $mtarget
			USEDACTIONS="$USEDACTIONS $mtarget"
		    fi

		    mtarget=$(find_logactionchain $mtarget)
		else
		    fatal_error "Invalid Action in rule \"$mtarget ${mclients:--} ${mservers:--} ${mprotocol:--} ${mports:--} ${mcports:--} ${xaddress:--} ${mratelimit:--} ${muserspec:--}\""
		fi
		;;
	esac

	if [ -n "$mclients" ]; then
	    case $mclients in
		-)
		    mclients=${iclients}
		    ;;
		*)
		    mclients=$(merge_macro_source_dest $mclients $iclients)
		    ;;
	    esac
	else
	    mclients=${iclients}
	fi

	if [ -n "$mservers" ]; then
	    case $mservers in
		-)
		    mservers=${iservers}
		    ;;
		*)
		    mservers=$(merge_macro_source_dest $mservers $iservers)
		    ;;
	    esac
	else
	    mservers=${iservers}
	fi

	[ -n "$iprotocol" ]  && [ "x${iprotocol}" != x- ]  && mprotocol=$iprotocol
	[ -n "$iports" ]     && [ "x${iports}" != x- ]     && mports=$iports
	[ -n "$icports" ]    && [ "x${icports}" != x- ]    && mcports=$icports
	[ -n "$iratelimit" ] && [ "x${iratelimit}" != x- ] && mratelimit=$iratelimit
	[ -n "$iuserspec" ]  && [ "x${iuserspec}" != x- ]  && muserspec=$iuserspec

	rule="$mtarget ${mclients=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${xaddress:=-} ${mratelimit:=-} ${muserspec:=-}"
	process_rule $mtarget $mclients $mservers $mprotocol $mports $mcports ${iaddress:=-} $mratelimit $muserspec

    done < $TMP_DIR/macro.${itarget%%:*}

    progress_message "..End Macro"

}

#
# Process the rules file
#
process_rules()
{
    #
    # Process a rule where the source or destination is "all"
    #
    process_wildcard_rule() # $1 = Yes, if this is a macro, $2 = Yes if we want intrazone traffic
    {
	local yclients yservers ysourcezone ydestzone ypolicy

	for yclients in $xclients; do
	    for yservers in $xservers; do
		ysourcezone=${yclients%%:*}
		ydestzone=${yservers%%:*}
		if [ "${ysourcezone}" != "${ydestzone}" -o "$2" = Yes ] ; then
		    eval ypolicy=\$${ysourcezone}2${ydestzone}_policy
		    if [ "$ypolicy" != NONE ] ; then
			if [ "$1" = Yes ]; then
			    process_macro $xtarget "$xparam" $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
			else
			    rule="$xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
			    process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
			fi
		    fi
		fi
	    done
	done
    }

    do_it() # $1 = "Yes" if the target is a macro.
    {
	expandv xprotocol xports xcports xaddress xratelimit xuserspec intrazone=

	if [ -z "$SECTIONS" ]; then
	    finish_section ESTABLISHED,RELATED
	    SECTIONS="ESTABLISHED RELATED NEW"
	    SECTION=NEW
	fi

	case $xclients in
	    all+)
		xclients=all
		intrazone=Yes
		;;
	esac

	case $xservers in
	    all+)
		xservers=all
		intrazone=Yes
		;;
	esac

	if [ "x$xclients" = xall ]; then
	    xclients="$ZONES $FW"
	    if [ "x$xservers" = xall ]; then
		xservers="$ZONES $FW"
	    fi
	    process_wildcard_rule "$1" $intrazone
	    return
	fi

	if [ "x$xservers" = xall ]; then
	    xservers="$ZONES $FW"
	    process_wildcard_rule "$1" $intrazone
	    return
	fi

	if [ "$1" = Yes ]; then
	    process_macro $xtarget "$xparam" $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
	else
	    rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
	    process_rule $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
	fi
    }

    while read xtarget xclients xservers xprotocol xports xcports xaddress xratelimit xuserspec; do
	expandv xtarget xclients xservers

	if [ "x$xclients" = xnone -o "x$servers" = xnone ]; then
	    rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
	    progress_message "   Rule \"$rule\" ignored."
	    continue
	fi

	case "${xtarget%%:*}" in
	    ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-)
		do_it No
		;;
	    SECTION)
		list_search $xclients $SECTIONS && fatal_error "Duplicate or out of order SECTION $xclients"

		case $xclients in
		    ESTABLISHED)
			SECTIONS=ESTABLISHED
			;;
		    RELATED)
			finish_section ESTABLISHED
			SECTIONS="ESTABLISHED RELATED"
		        ;;
		    NEW)
			[ $SECTION = RELATED ] && finish_section RELATED || finish_section ESTABLISHED,RELATED
			SECTIONS="ESTABLISHED RELATED NEW"
			;;
		    *)
			fatal_error "Invalid SECTION $xclients"
			;;
		esac

		[ -n "$xservers" ] && fatal_error "Invalid SECTION $xclients $xservers"

		SECTION=$xclients
		;;
	    *)
		if list_search ${xtarget%%:*} $ACTIONS; then
		    if ! list_search $xtarget $USEDACTIONS; then
			createactionchain $xtarget
			USEDACTIONS="$USEDACTIONS $xtarget"
		    fi

		    xtarget=$(find_logactionchain $xtarget)
		    do_it No
		else
		    xtarget1=$(map_old_action ${xtarget%%:*})

		    case $xtarget1 in
			*/*)
			    xparam=${xtarget1#*/}
			    xtarget1=${xtarget1%%/*}
			    xtarget=$(substitute_action $xtarget1 $xtarget)
			    ;;
			*)
			    xparam=
			    ;;
		    esac

		    f=macro.$xtarget1

		    if [ -f $TMP_DIR/$f ]; then
			do_it Yes
		    else
			fn=$(find_file $f)

			if [ -f $fn ]; then
			    strip_file $f $fn
			    do_it Yes
			else
			    rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
			    fatal_error "Invalid Action in rule \"$rule\""
			fi
		    fi
		fi
		;;

	esac
    done < $TMP_DIR/rules

    case $SECTION in
	ESTABLISHED)
	    finish_section ESTABLISHED,RELATED
	    ;;
	RELATED)
	    finish_section RELATED
	    ;;
    esac

    SECTION=DONE
}

#
# 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
	error_message "WARNING: Undefined Source Zone - rule \"$rule\" ignored"
	return
    fi

    [ -n "$src" ] && case "$src" in
	*.*.*|+*|!+*)
	    #
	    # IP Address or networks
	    #
	    src="$(source_ip_range $src)"
	    ;;
	~*)
	    src=$(mac_match $src)
	    ;;
	*)
	    #
	    # Assume that this is a device name
	    #
	    if ! verify_interface $src ; then
		error_message "WARNING: Unknown Interface in rule \"$rule\" ignored"
		return
	    fi

	    src="$(match_source_dev $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
	error_message \
	 "WARNING: Undefined Destination Zone - rule \"$rule\" ignored"
	return
    fi

    [ -n "$dst" ] && case "$dst" in
	*.*.*|+*|!+*)
	    #
	    # IP Address or networks
	    #
	    ;;
	*)
	    #
	    # Assume that this is a device name
	    #
	    error_message \
	     "WARNING: Invalid Destination - rule \"$rule\" ignored"
	    return
	    ;;
    esac

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

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

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

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

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

    for dest in $dst; do
	dest="$(dest_ip_range $dest)"

	case $srczone in
	$FW)
	    run_iptables2 -t mangle -A outtos \
		$protocol $dest $dports $sports $tos
	    ;;
	all|ALL)
	    run_iptables2 -t mangle -A outtos \
		$protocol $dest $dports $sports $tos
	    run_iptables2 -t mangle -A pretos \
		$protocol $dest $dports $sports $tos
	    ;;
	*)
	    if [ -n "$src" ]; then
		run_iptables2 -t mangle -A pretos $src \
		    $protocol $dest $dports $sports $tos
	    else
		eval interfaces=\$${srczone}_interfaces

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

    progress_message "   Rule \"$rule\" $DONE."
}

#
# Process the tos file
#
process_tos() # $1 = name of tos file
{
    progress_message2 "$DOING $1..."

    strip_file tos $1

    if [ -s $TMP_DIR/tos ] ; then
	createmanglechain pretos
	createmanglechain outtos

	while read src dst protocol sport dport tos; do
	    expandv src dst protocol sport dport tos
	    rule="$(echo $src $dst $protocol $sport $dport $tos)"
	    process_tos_rule
	done < $TMP_DIR/tos

	run_iptables -t mangle -A PREROUTING -j pretos
	run_iptables -t mangle -A OUTPUT     -j outtos
    fi
}

#
# Display elements of a list with leading white space
#
display_list() # $1 = List Title, rest of $* = list to display
{
    [ $# -gt 1 ] && echo "   $*"
}

policy_rules() # $1 = chain to add rules to
	       # $2 = policy
	       # $3 = loglevel
{
    local target="$2"

    case "$target" in
	ACCEPT)
	    [ -n "$ACCEPT_common" ] && run_iptables -A $1 -j $ACCEPT_common
	    ;;
	DROP)
	    [ -n "$DROP_common" ] && run_iptables -A $1 -j $DROP_common
	    ;;
	REJECT)
	    [ -n "$REJECT_common" ] && run_iptables -A $1 -j $REJECT_common
	    target=reject
	    ;;
	QUEUE)
	    [ -n "$QUEUE_common" ] && run_iptables -A $1 -j $QUEUE_common
	    ;;
	CONTINUE)
	    target=
	    ;;
	*)
	    fatal_error "Invalid policy ($policy) for $1"
	    ;;
    esac

    if [ $# -eq 3 -a "x${3}" != "x-" ]; then
	log_rule $3 $1 $2
    fi

    [ -n "$target" ] && run_iptables -A $1 -j $target
}

#
# 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=
    local chain1

    jump_to_policy_chain() {
	#
	# Add a jump to from the canonical chain to the policy chain. On return,
	# $chain is set to the name of the policy chain
	#
	run_iptables -A $chain -j $chain1
	chain=$chain1
    }

    report_syn_flood_protection()
    {
	progress_message "      Enabled SYN flood protection"
    }

    apply_default()
    {
	#
	# Generate policy file column values from the policy chain
	#
	eval policy=\$${chain1}_policy
	eval loglevel=\$${chain1}_loglevel
	eval synparams=\$${chain1}_synparams
	#
	# Add the appropriate rules to the canonical chain ($chain) to enforce
	# the specified policy

	if [ "$chain" = "$chain1" ]; then
	    #
	    # The policy chain is the canonical chain; add policy rule to it
	    # The syn flood jump has already been added if required.
	    #
	    policy_rules $chain $policy $loglevel
	else
	    #
	    # The policy chain is different from the canonical chain -- approach
	    # depends on the policy
	    #
	    case $policy in
	    ACCEPT|QUEUE)
		if [ -n "$synparams" ]; then
		    #
		    # To avoid double-counting SYN packets, enforce the policy
		    # in this chain.
		    #
		    report_syn_flood_protection
		    policy_rules $chain $policy $loglevel
		else
		    #
		    # No problem with double-counting so just jump to the
		    # policy chain.
		    #
		    jump_to_policy_chain
		fi
		;;
	    CONTINUE)
		#
		# Silly to jump to the policy chain -- add any logging
		# rules and enable SYN flood protection if requested
		#
		[ -n "$synparams" ] && \
		    report_syn_flood_protection
		policy_rules $chain $policy $loglevel
		;;
	    *)
		#
		# DROP or REJECT policy -- enforce in the policy chain and
		# enable SYN flood protection if requested.
		#
		[ -n "$synparams" ] && \
		    report_syn_flood_protection
		jump_to_policy_chain
		;;
	    esac
	fi

	progress_message "   Policy $policy for $1 to $2 using chain $chain"
    }

    eval chain1=\$${1}2${2}_policychain

    if [ -n "$chain1" ]; then
	apply_default $1 $2
    else
	fatal_error "No default policy for zone $1 to zone $2"
    fi
}

#
# Complete a standard chain
#
#	- run any supplied user exit
#	- search the policy file for an applicable policy and add rules as
#	  appropriate
#	- If no applicable policy is found, add rules for an assummed
#	  policy of DROP INFO
#
complete_standard_chain() # $1 = chain, $2 = source zone, $3 = destination zone
{
    local policy=
    local loglevel=
    local policychain=

    run_user_exit $1

    eval policychain=\$${2}2${3}_policychain

    if [ -n "$policychain" ]; then
	eval policy=\$${policychain}_policy
	eval loglevel=\$${policychain}_loglevel
	eval

	policy_rules $1 $policy $loglevel
    else
	policy_rules $1 DROP info
    fi
}

#
# 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} local policy

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

    [ "$1" = "$2" ] && { echo ACCEPT; return; }

    eval chain=\$${chain}_policychain

    eval policy=\$${chain}_policy

    if [ "$policy" != CONTINUE ] ; then
	[ -n "$chain" ] && { echo $chain; return; }
	fatal_error "No policy defined for zone $1 to zone $2"
    fi
}

#
# Set up Routing
#
setup_routes()
{

    run_iptables -t mangle -A PREROUTING -m connmark ! --mark 0 -j CONNMARK --restore-mark
    run_iptables -t mangle -A OUTPUT     -m connmark ! --mark 0 -j CONNMARK --restore-mark
    createmanglechain routemark

    for interface in $ROUTEMARK_INTERFACES ; do

	iface=$(chain_base $interface)
	eval mark_value=\$${iface}_routemark

	run_iptables -t mangle -A PREROUTING -i $interface -m mark --mark 0 -j routemark
	run_iptables -t mangle -A routemark  -i $interface                  -j MARK --set-mark $mark_value

    done

    run_iptables -t mangle -A routemark -m mark ! --mark 0 -j CONNMARK --save-mark --mask 255

}

#
# Set up Source NAT (including masquerading)
#
setup_masq()
{
    do_ipsec_options() {
	local options="$(separate_list $ipsec)" option
	policy="-m policy --pol ipsec --dir out"

	for option in $options; do
	    case $option in
		[Yy]es)         ;;
		strict)         policy="$policy --strict" ;;
		next)           policy="$policy --next" ;;
		reqid=*)        policy="$policy --reqid ${option#*=}" ;;
		spi=*)          policy="$policy --spi ${option#*=}" ;;
		proto=*)        policy="$policy --proto ${option#*=}" ;;
		mode=*)         policy="$policy --mode ${option#*=}" ;;
		tunnel-src=*)   policy="$policy --tunnel-src ${option#*=}" ;;
		tunnel-dst=*)   policy="$policy --tunnel-dst ${option#*=}" ;;
		reqid!=*)       policy="$policy ! --reqid ${option#*=}" ;;
		spi!=*)         policy="$policy ! --spi ${option#*=}" ;;
		proto!=*)       policy="$policy ! --proto ${option#*=}" ;;
		mode!=*)        policy="$policy ! --mode ${option#*=}" ;;
		tunnel-src!=*)  policy="$policy ! --tunnel-src ${option#*=}" ;;
		tunnel-dst!=*)  policy="$policy ! --tunnel-dst ${option#*=}" ;;
		*)              fatal_error "Invalid IPSEC option \"$option\"" ;;
	    esac
	done
    }

    setup_one() {
	local add_snat_aliases=$ADD_SNAT_ALIASES pre_nat= policy= destnets=

	[ "x$ipsec" = x- ] && ipsec=

	case $ipsec in
	    Yes|yes)
		[ -n "$POLICY_MATCH" ] || \
		    fatal_error "IPSEC=Yes requires policy match support in your kernel and iptables"
		policy="-m policy --pol ipsec --dir out"
		;;
	    No|no)
		[ -n "$POLICY_MATCH" ] || \
		    fatal_error "IPSEC=No requires policy match support in your kernel and iptables"
		policy="-m policy --pol none --dir out"
		;;
	    *)
		if [ -n "$ipsec" ]; then
		    do_ipsec_options
		elif [ -n "$POLICY_MATCH" ]; then
		    policy="-m policy --pol none --dir out"
		fi
		;;
	esac

	case $fullinterface in
	    +*)
		pre_nat=Yes
		fullinterface=${fullinterface#+}
		;;
	esac

	case $fullinterface in
	    *::*)
		add_snat_aliases=
		destnets="${fullinterface##*:}"
		fullinterface="${fullinterface%:*}"
		;;
	    *:*:*)
		# Both alias name and networks
		destnets="${fullinterface##*:}"
		fullinterface="${fullinterface%:*}"
		;;
	    *:)
		add_snat_aliases=
	        fullinterface=${fullinterface%:}
		;;
	    *:*)
		# Alias name OR networks
		case ${fullinterface#*:} in
		    *.*)
			# It's a networks
			destnets="${fullinterface#*:}"
			fullinterface="${fullinterface%:*}"
			;;
		    *)
			#it's an alias name
			;;
		esac
		;;
	    *)
		;;
	esac

	interface=${fullinterface%:*}

	if ! list_search $interface $ALL_INTERFACES; then
	    fatal_error "Unknown interface $interface"
	fi

	if [ "$networks" = "${networks%!*}" ]; then
	    nomasq=
	else
	    nomasq="${networks#*!}"
	    networks="${networks%!*}"
	fi

	source="${networks:=0.0.0.0/0}"

	detectinterface=

	case $source in
	    *.*.*|+*|!+*)
		;;
	    *)
		detectinterface=$networks
		networks=
		;;
	esac

	[ "x$addresses" = x- ] && addresses=

	if [ -n "$addresses" -a -n "$add_snat_aliases" ]; then
	    for address in $(separate_list $addresses); do
		address=${address%:)}
		if [ -n "$address" ]; then
		    for addr in $(ip_range_explicit ${address%:*}) ; do
			if ! list_search $addr $ALIASES_TO_ADD; then
			    [ -n "$RETAIN_ALIASES" ] || save_command qt ip addr del $addr dev $interface
			    ALIASES_TO_ADD="$ALIASES_TO_ADD $addr $fullinterface"
			    case $fullinterface in
				*:*)
				    fullinterface=${fullinterface%:*}:$((${fullinterface#*:} + 1 ))
				    ;;
			    esac
			fi
		    done
		fi
	    done
	fi

	[ "x$proto" = x- ] && proto=
	[ "x$ports" = x- ] && ports=

	if [ -n "$proto" ]; then

	    displayproto="($proto)"

	    case $proto in
		tcp|TCP|udp|UDP|6|17)
		    if [ -n "$ports" ]; then
			displayproto="($proto $ports)"

			listcount=$(list_count $ports)

			if [ $listcount -gt 1 ]; then
			    case $ports in
				*:*)
				    if [ -n "$XMULTIPORT" ]; then
					if [ $(($listcount + $(list_count1 $(split $ports) ) )) -le 16 ]; then
					    ports="-m multiport --dports $ports"
					else
					    fatal_error "More than 15 entries in port list ($ports)"
					fi
				    else
					fatal_error "Port Range not allowed in list ($ports)"
				    fi
				    ;;
				*)
				    if [ -n "$MULTIPORT" ]; then
					[ $listcount -le 15 ] || fatal_error "More than 15 entries in port list ($ports)"
					ports="-m multiport --dports $ports"
				    else
					fatal_error "Port Ranges require multiport match support in your kernel ($ports)"
				    fi
				    ;;
			    esac
			else
			    ports="--dport $ports"
			fi
		    fi
		    ;;
		*)
		    [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)"
		    ;;
	    esac

	    proto="-p $proto"
	else
	    displayproto="(all)"
	    [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)"
	fi

	destination=${destnets:=0.0.0.0/0}

	[ -z "$pre_nat" ] && chain=$(masq_chain $interface) || chain=$(snat_chain $interface)

	ensurenatchain $chain

	case $destnets in
	    !*)
		destnets=${destnets#!}

		build_exclusion_chain newchain nat "$nomasq" "$destnets"

		if [ -n "$networks" ]; then
		    for s in $networks; do
			addnatrule $chain $(source_ip_range $s) $proto $ports $policy -j $newchain
		    done
		    networks=
		elif [ -n "$detectinterface" ]; then
		    cat >> $RESTOREBASE << __EOF__

${INDENT}networks="\$(get_routed_networks $detectinterface)"

${INDENT}[ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\""

${INDENT}for network in \$networks; do
${INDENT}    run_iptables -t nat -A $chain -s \$network $proto $ports $policy -j $newchain
${INDENT}done

__EOF__
		else
		    addnatrule $chain -j $newchain
		fi

		chain=$newchain
		destnets=0.0.0.0/0
		proto=
		ports=
		policy=

		[ -n "$nomasq" ] && source="$source except $nomasq"
		;;
	    *)
		if [ -n "$nomasq" ]; then
		    build_exclusion_chain newchain nat $nomasq

		    if [ -n "$networks" ]; then
			for s in $networks; do
			    for destnet in $(separate_list $destnets); do
				addnatrule $chain $(both_ip_ranges $s $destnet) $proto $ports $policy -j $newchain
			    done
			done
		    elif [ -n "$detectinterface" ]; then
			cat >> $RESTOREBASE << __EOF__

${INDENT}networks="\$(get_routed_networks $detectinterface)"

${INDENT}[ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\""

${INDENT}for network in \$networks; do
__EOF__
			for destnet in $(separate_list $destnets); do
			    cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -t nat -A $chain -s \$network $(dest_ip_range $destnet) $proto $sports $policy -j $netchain
__EOF__
			done
			cat >> $RESTOREBASE << __EOF__

${INDENT}done
__EOF__
		    else
			for destnet in $(separate_list $destnets); do
			    addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $newchain
			done
		    fi

		    chain=$newchain
		    networks=
		    destnets=0.0.0.0/0
		    proto=
		    ports=
		    policy=
		    source="$source except $nomasq"
		fi

		;;
	esac

	addrlist=
	target=MASQUERADE

	if [ -n "$addresses" ]; then
	    case "$addresses" in
		SAME:nodst:*)
		    target="SAME --nodst"
		    addresses=${addresses#SAME:nodst:}
		    for address in $(separate_list $addresses); do
			addrlist="$addrlist --to $address";
		    done
		    ;;
		SAME:*)
		    target="SAME"
		    addresses=${addresses#SAME:}
		    for address in $(separate_list $addresses); do
			addrlist="$addrlist --to $address";
		    done
		    ;;
		*)
		    for address in $(separate_list $addresses); do
			case $address in
			    *.*.*.*)
				target=SNAT
				addrlist="$addrlist --to-source $address"
				;;
			    *)
				addrlist="$addrlist --to-ports ${address#:}"
				;;
			esac
		    done
		    ;;
	    esac
	fi

	if [ -n "$networks" ]; then
	    for network in $networks; do
		for destnet in $(separate_list $destnets); do
		    addnatrule $chain $(both_ip_ranges $network $destnet) $proto $ports $policy -j $target $addrlist
		done

		if [ -n "$addresses" ]; then
		    progress_message "   To $destination $displayproto from $network through ${interface} using $addresses"
		else
		    progress_message "   To $destination $displayproto from $network through ${interface}"
		fi
	    done
	elif [ -n "$detectinterface" ]; then
	    cat >> $RESTOREBASE << __EOF__

${INDENT}networks="\$(get_routed_networks $detectinterface)"

${INDENT}[ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\""

${INDENT}for network in \$networks; do
__EOF__
	    for destnet in $(separate_list $destnets); do
		cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -t nat -A $chain -s \$network $(dest_ip_range $destnet) $proto $ports $policy -j $target $addrlist
__EOF__
	    done

	    if [ -n "$addresses" ]; then
		message="   To $destination $displayproto from \$network through ${interface} using $addresses"
	    else
		message="   To $destination $displayproto from \$network through ${interface}"
	    fi

	    cat >> $RESTOREBASE << __EOF__
${INDENT}    progress_message "$message"
${INDENT}done

__EOF__

	else
	    for destnet in $(separate_list $destnets); do
		addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $target $addrlist
	    done

	    if [ -n "$addresses" ]; then
		progress_message "   To $destination $displayproto from $source through ${interface} using $addresses"
	    else
		progress_message "   To $destination $displayproto from $source through ${interface}"
	    fi
	fi

    }

    strip_file masq $1

    if [ -n "$NAT_ENABLED" ]; then
	progress_message2 "Masqueraded Networks and Hosts:"
	save_progress_message "Setting up Masquerading/SNAT..."
    fi

    while read fullinterface networks addresses proto ports ipsec; do
	expandv fullinterface networks addresses proto ports ipsec
	if [ -n "$NAT_ENABLED" ]; then
	    setup_one
	else
	    error_message "WARNING: NAT disabled; masq rule ignored"
	fi
    done < $TMP_DIR/masq
}

#
# Add a record to the blacklst chain
#
#   $source      = address match
#   $proto       = protocol selector
#   $dport       = destination port selector
#
add_blacklist_rule() {
    if [ -n "$BLACKLIST_LOGLEVEL" ]; then
	log_rule $BLACKLIST_LOGLEVEL blacklst $BLACKLIST_DISPOSITION  $(fix_bang $source $proto $dport)
    fi

    run_iptables2 -A blacklst $source $proto $dport -j $disposition
}

#
# Process a record from the blacklist file
#
#   $networks	 = address/networks
#   $protocol    = Protocol Number/Name
#   $port        = Port Number/Name
#
process_blacklist_rec() {
    local source
    local addr
    local proto
    local dport
    local temp
    local setname

    for addr in $(separate_list $networks); do
	case $addr in
	    -)
		source=
		;;
	    ~*)
		addr=$(echo $addr | sed 's/~//;s/-/:/g')
		source="--match mac --mac-source $addr"
		;;
	    *)
		source="$(source_ip_range $addr)"
		;;
	esac

	if [ -n "$protocol" ]; then
	    proto=" -p $protocol "

	    case $protocol in
		tcp|TCP|6|udp|UDP|17)
		    if [ -n "$ports" ]; then
			if [ -n "$MULTIPORT" -a \
			    "$ports" != "${ports%,*}" -a \
			    "$ports" = "${ports%:*}" -a \
			    $(list_count $ports) -le 15 ]
			then
			    dport="-m multiport --dports $ports"
			    add_blacklist_rule
			else
			    for dport in $(separate_list $ports); do
				dport="--dport $dport"
				add_blacklist_rule
			    done
			fi
		    else
			add_blacklist_rule
		    fi
		    ;;
		icmp|ICMP|0)
		    if [ -n "$ports" ]; then
			for dport in $(separate_list $ports); do
			    dport="--icmp-type $dport"
			    add_blacklist_rule
			done
		    else
			add_blacklist_rule
		    fi
		    ;;
		*)
		    add_blacklist_rule
		    ;;
	    esac
	else
	    add_blacklist_rule
	fi

	if [ -n "$ports" ]; then
	    addr="$addr $protocol $ports"
	elif [ -n "$protocol" ]; then
	    addr="$addr $protocol"
	fi

	progress_message "   $addr added to Black List"
    done
}

#
# Setup the Black List
#
setup_blacklist() {
    local hosts="$(find_hosts_by_option blacklist)"
    local f=$(find_file blacklist)
    local disposition=$BLACKLIST_DISPOSITION
    local ipsec policy

    if [ -n "$hosts" -a -f $f ]; then
	progress_message2 "Setting up Blacklisting..."

	strip_file blacklist $f

	createchain blacklst no

	[ -n "$BLACKLISTNEWONLY" ] && state="-m state --state NEW,INVALID" || state=

	for host in $hosts; do
	    ipsec=${host%^*}
	    host=${host#*^}
	    [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	    interface=${host%%:*}
	    network=${host#*:}

	    for chain in $(first_chains $interface); do
		run_iptables -A $chain $state $(match_source_hosts $network) $policy -j blacklst
	    done

	    [ $network = 0/0.0.0.0 ] && network= || network=":$network"

	    progress_message "   Blacklisting enabled on ${interface}${network}"
	done

	[ "$disposition" = REJECT ] && disposition=reject

	if [ -z "$DELAYBLACKLISTLOAD" ]; then
	    while read networks protocol ports; do
		expandv networks protocol ports
		process_blacklist_rec
	    done < $TMP_DIR/blacklist
	fi
    fi
}

#
# Refresh the Black List
#
refresh_blacklist() {
    local f=$(find_file blacklist)
    local disposition=$BLACKLIST_DISPOSITION

    if qt $IPTABLES -L blacklst -n ; then
	progress_message2 "Loading Black List..."

	strip_file blacklist $f

	[ "$disposition" = REJECT ] && disposition=reject

	run_iptables -F blacklst

	while read networks protocol ports; do
	    expandv networks protocol ports
	    process_blacklist_rec
	done < $TMP_DIR/blacklist
    fi
}

#
# Verify the Black List
#
validate_blacklist() {
    local f=$(find_file blacklist)
    local disposition=$BLACKLIST_DISPOSITION

    progress_message2 "Checking Black List..."

    strip_file blacklist $f

    [ "$disposition" = REJECT ] && disposition=reject

    while read networks protocol ports; do
	expandv networks protocol ports
	process_blacklist_rec
    done < $TMP_DIR/blacklist
}

#
# Perform Initialization
#	- Delete all old rules
#	- Delete all user chains
#	- Set the POLICY on all standard chains and add a rule to allow packets
#	  that are part of established connections
#	- Determine the zones
#
initialize_netfilter () {
    setup_mss()
    {
	case $CLAMPMSS in
	    Yes)
		option="--clamp-mss-to-pmtu"
		;;
	    *)
		option="--set-mss $CLAMPMSS"
		;;
	esac

	run_iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS $option
    }

    report_capabilities

    if [ -n "$BRIDGING" ]; then
	[ -n "$PHYSDEV_MATCH" ] || startup_error "BRIDGING=Yes requires Physdev Match support in your Kernel and iptables"
    fi

    [ "$MACLIST_TTL" = "0" ] && MACLIST_TTL=

    if [ -n "$MACLIST_TTL" -a -z "$RECENT_MATCH" ]; then
	startup_error "MACLIST_TTL requires the Recent Match capability which is not present in your Kernel and/or iptables"
    fi

    [ -n "$RFC1918_STRICT" -a -z "$CONNTRACK_MATCH" ] && \
	startup_error "RFC1918_STRICT=Yes requires Connection Tracking match"

    progress_message2 "Determining Zones..."

    determine_zones

    if [ $VERBOSE -ge 1 ]; then
	display_list "IPv4 Zones:" $IPV4_ZONES
	[ -n "$IPSEC_ZONES" ] && \
	    display_list "IPSEC Zones:" $IPSEC_ZONES
	display_list "Firewall Zone:" $FW
    fi

    progress_message2 "Validating interfaces file..."

    validate_interfaces_file

    progress_message2 "Validating hosts file..."

    validate_hosts_file

    progress_message2 "Validating Policy file..."

    validate_policy

    progress_message2 "Determining Hosts in Zones..."

    determine_interfaces
    determine_hosts

    run_user_exit init

    #
    # Some files might be large so strip them while the firewall is still running
    # (restart command). This reduces the length of time that the firewall isn't
    # accepting new connections.
    #

    strip_file rules
    strip_file proxyarp
    strip_file maclist
    strip_file nat
    strip_file netmap

    progress_message2 "Pre-processing Actions..."
    process_actions1

    TERMINATOR=fatal_error

    deletechain shorewall

    if [ -n "$NAT_ENABLED" ]; then
	delete_nat
	for chain in PREROUTING POSTROUTING OUTPUT; do
	    qt_iptables -t nat -P $chain ACCEPT
	done
    fi

    delete_proxy_arp

    if [ -n "$MANGLE_ENABLED" ]; then
	run_iptables -t mangle -F
	run_iptables -t mangle -X
	for chain in PREROUTING INPUT FORWARD POSTROUTING; do
	    qt_iptables -t mangle -P $chain ACCEPT
	done
    fi

    if [ -n "$RAW_TABLE" ]; then
	run_iptables -t raw -F
	run_iptables -t raw -X
	for chain in PREROUTING OUTPUT; do
	    qt_iptables -t raw -P $chain ACCEPT
	done
    fi

    [ -n "$CLEAR_TC" ] && delete_tc

    progress_message2 "Deleting user chains..."

    save_progress_message "Deleting user chains..."

    exists_INPUT=Yes
    exists_OUTPUT=Yes
    exists_FORWARD=Yes

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then

	setpolicy INPUT ACCEPT
	setpolicy OUTPUT ACCEPT
	setpolicy FORWARD DROP

	deleteallchains

	enable_critical_hosts

	setpolicy INPUT DROP
	setpolicy OUTPUT DROP

	[ -n "$CLAMPMSS" ] && setup_mss

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    else

	setpolicy INPUT DROP
	setpolicy OUTPUT DROP
	setpolicy FORWARD DROP

	deleteallchains

	[ -n "$CLAMPMSS" ] && setup_mss

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    fi

    f=$(find_file ipsets)

    if [ -f $f ]; then
	progress_message2 "Processing $f ..."
	save_progress_message "Restoring IPSETS..."
	save_command "ipset -U :all: :all:"
	save_command "ipset -F"
	save_command "ipset -X"
	save_command "ipset -R < $f"
    fi

    run_user_exit continue

    f=$(find_file routestopped)

    progress_message2 "$DOING $f ..."

    strip_file routestopped $f

    process_routestopped -A

    if [ -n "$DISABLE_IPV6" ]; then
	save_command disable_ipv6
    fi

    save_progress_message "Enabling Loopback and DNS Lookups"

    #
    # Enable the Loopback interface for now
    #
    run_iptables -A INPUT  -i lo -j ACCEPT
    run_iptables -A OUTPUT -o lo -j ACCEPT

    #
    # Allow DNS lookups during startup for FQDNs
    #

    for chain in INPUT OUTPUT FORWARD; do
	run_iptables -A $chain -p udp --dport 53 -j ACCEPT
    done

    accounting_file=$(find_file accounting)

    [ -f $accounting_file ] && setup_accounting $accounting_file

    createchain reject	no
    createchain dynamic no
    createchain smurfs no

    echo >> $RESTOREBASE << __EOF__
${INDENT}if [ -f /var/lib/shorewall/save ]; then
${INDENT}    progress_message2 "Setting up dynamic rules..."
${INDENT}    while read target ignore1 ignore2 address rest; do
${INDENT}        case \$target in
${INDENT}            DROP|reject)
${INDENT}                run_iptables -A dynamic -s \$address -j \$target
${INDENT}                ;;
${INDENT}        esac
${INDENT}    done < /var/lib/shorewall/save
${INDENT}fi
__EOF__

    [ -n "$BLACKLISTNEWONLY" ] && state="-m state --state NEW,INVALID" || state=

    progress_message2 "Creating Interface Chains..."

    save_progress_message "Creating Interface Chains..."

    for interface in $ALL_INTERFACES; do
	createchain $(forward_chain $interface) no
	run_iptables -A $(forward_chain $interface) $state -j dynamic
	createchain $(input_chain $interface)   no
	run_iptables -A $(input_chain $interface) $state -j dynamic
    done
}

#
# Construct zone-independent rules
#
add_common_rules() {
    local savelogparms="$LOGPARMS"
    local broadcasts="$(find_broadcasts) 255.255.255.255 224.0.0.0/4"

    drop_broadcasts() {
	for interface in $(find_bcastdetect_interfaces); do
	    cat >> $RESTOREBASE << __EOF__

${INDENT}ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do
${INDENT}    run_iptables -A reject -d \$address -j DROP
${INDENT}done

__EOF__
	done

	for address in $broadcasts ; do
	    run_iptables -A reject -d $address -j DROP
	done
    }
    #
    # Populate the smurf chain
    #
    save_progress_message "Setting up SMURF control..."

    for interface in $(find_bcastdetect_interfaces); do
	cat >> $RESTOREBASE << __EOF__

${INDENT}ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do
__EOF__
	[ -n "$SMURF_LOG_LEVEL" ] && \
	    cat >> $RESTOREBASE << __EOF__
${INDENT}    log_rule $SMURF_LOG_LEVEL smurfs DROP -s \$address
__EOF__
	cat >> $RESTOREBASE << __EOF__
${INDENT}    run_iptables -A smurfs -s \$address -j DROP
${INDENT}done

__EOF__
    done

    for address in $broadcasts ; do
	[ -n "$SMURF_LOG_LEVEL" ] && log_rule $SMURF_LOG_LEVEL smurfs DROP -s $address
	run_iptables -A smurfs $(source_ip_range $address) -j DROP
	run_iptables -A reject -s $address -j DROP
    done
    #
    # Reject Rules -- Don't respond to broadcasts with an ICMP
    #
    if [ -n "$USEPKTTYPE" ]; then
	run_iptables -A reject -m pkttype --pkt-type broadcast -j DROP
	run_iptables -A reject -m pkttype --pkt-type multicast -j DROP
    else
	drop_broadcasts
    fi
    #
    # Don't feed the smurfs
    #
    for address in $broadcasts ; do
	run_iptables -A reject -s $address -j DROP
    done

    run_iptables -A reject -p tcp  -j REJECT --reject-with tcp-reset
    run_iptables -A reject -p udp  -j REJECT
    #
    # Not all versions of iptables support these so don't complain if they don't work
    #
    if [ -n "$ENHANCED_REJECT" ]; then
	run_iptables -A reject -p icmp -j REJECT --reject-with icmp-host-unreachable
	run_iptables -A reject -j REJECT --reject-with icmp-host-prohibited
    else
	run_iptables -A reject -j REJECT
    fi

    #
    # Create common action chains
    #
    for action in $USEDACTIONS; do
	createactionchain $action
    done

    run_user_exit initdone

    #
    # Process Black List
    #
    save_progress_message "Setting up Black List..."

    setup_blacklist

    #
    # SMURFS
    #
    hosts=$(find_hosts_by_option nosmurfs)

    if [ -n "$hosts" ]; then

	progress_message2 "Adding Anti-smurf Rules"

	save_progress_message "Adding Anti-smurf Jumps..."

	for host in $hosts; do
	    ipsec=${host%^*}
	    host=${host#*^}
	    [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	    interface=${host%%:*}
	    network=${host#*:}

	    for chain in $(first_chains $interface); do
		run_iptables -A $chain  -m state --state NEW,INVALID $(match_source_hosts $network) $policy -j smurfs
	    done
	done
    fi
    #
    # DHCP
    #
    interfaces=$(find_interfaces_by_option dhcp)

    if [ -n "$interfaces" ]; then

	progress_message2 "Adding rules for DHCP"

	save_progress_message "Setting up rules for DHCP..."

	for interface in $interfaces; do
	    if [ -n "$BRIDGING" ]; then
		is_bridge=$( brctl show $interface 2> /dev/null | grep ^$interface[[:space:]] )
		[ -n "$is_bridge" ] && \
		    do_iptables -A $(forward_chain $interface) -p udp -o $interface --dport 67:68 -j ACCEPT
            fi
	    run_iptables -A $(input_chain $interface) -p udp --dport 67:68 -j ACCEPT
	    run_iptables -A OUTPUT -o $interface      -p udp --dport 67:68 -j ACCEPT
	done
    fi
    #
    # RFC 1918
    #
    hosts="$(find_hosts_by_option norfc1918)"

    if [ -n "$hosts" ]; then
	progress_message2 "Enabling RFC1918 Filtering"

	save_progress_message "Setting up RFC1918 Filtering..."

	strip_file rfc1918

	createchain norfc1918 no

	createchain rfc1918 no

	log_rule $RFC1918_LOG_LEVEL rfc1918 DROP

	run_iptables -A rfc1918 -j DROP

	chain=norfc1918

	if [ -n "$RFC1918_STRICT" ]; then
	    #
	    # We'll generate two chains - one for source and one for destination
	    #
	    chain=rfc1918d
	    createchain $chain no
	elif [ -n "$MANGLE_ENABLED" -a -z "$CONNTRACK_MATCH" ]; then
	    #
	    # Mangling is enabled but conntrack match isn't available --
	    # create a chain in the mangle table to filter RFC1918 destination
            # addresses. This must be done in the mangle table before we apply
	    # any DNAT rules in the nat table
	    #
	    # Also add a chain to log and drop any RFC1918 packets that we find
	    #
	    createmanglechain man1918
	    createmanglechain rfc1918
	    log_rule $RFC1918_LOG_LEVEL rfc1918 DROP -t mangle
	    run_iptables -t mangle -A rfc1918 -j DROP
	fi

	while read networks target; do
	    case $target in
		logdrop)
		    target=rfc1918
		    s_target=rfc1918
		    ;;
		DROP)
		    s_target=DROP
		    ;;
		RETURN)
		    [ -n "$RFC1918_STRICT" ] && s_target=rfc1918d || s_target=RETURN
		    ;;
		*)
		    fatal_error "Invalid target ($target) for $networks"
		    ;;
	    esac

	    for network in $(separate_list $networks); do
		run_iptables2 -A norfc1918 $(source_ip_range $network) -j $s_target

		if [ -n "$CONNTRACK_MATCH" ]; then
		    #
		    # We have connection tracking match -- match on the original destination
		    #
		    run_iptables2 -A $chain -m conntrack --ctorigdst $network -j $target
		elif [ -n "$MANGLE_ENABLED" ]; then
		    #
		    # No connection tracking match but we have mangling -- add a rule to
		    # the mangle table
		    #
		    run_iptables2 -t mangle -A man1918 $(dest_ip_range $network) -j $target
		fi
	    done
	done < $TMP_DIR/rfc1918

	[ -n "$RFC1918_STRICT" ] && run_iptables -A norfc1918 -j rfc1918d

	for host in $hosts; do
	    ipsec=${host%^*}
	    host=${host#*^}
	    [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	    interface=${host%%:*}
	    networks=${host#*:}

	    for chain in $(first_chains $interface); do
		run_iptables -A $chain -m state --state NEW $(match_source_hosts $networks) $policy -j norfc1918
	    done

	    [ -n "$MANGLE_ENABLED" -a -z "$CONNTRACK_MATCH" ] && \
		run_iptables -t mangle -A PREROUTING -m state --state NEW -i $interface $(match_source_hosts $networks) -j man1918
	done
    fi

    hosts=$(find_hosts_by_option tcpflags)

    if [ -n "$hosts" ]; then
	progress_message2 "Setting up TCP Flags checking..."

	save_progress_message "Setting up TCP Flags checking..."

	createchain tcpflags no

	if [ -n "$TCP_FLAGS_LOG_LEVEL" ]; then
	    createchain logflags no

	    savelogparms="$LOGPARMS"

	    [ "$TCP_FLAGS_LOG_LEVEL" = ULOG ] || LOGPARMS="$LOGPARMS --log-ip-options"

	    log_rule $TCP_FLAGS_LOG_LEVEL logflags $TCP_FLAGS_DISPOSITION

	    LOGPARMS="$savelogparms"

	    case $TCP_FLAGS_DISPOSITION in
		REJECT)
		    run_iptables -A logflags -j REJECT --reject-with tcp-reset
		    ;;
		*)
		    run_iptables -A logflags -j $TCP_FLAGS_DISPOSITION
		    ;;
	    esac

	    disposition="-j logflags"
	else
	    disposition="-j $TCP_FLAGS_DISPOSITION"
	fi

	run_iptables -A tcpflags -p tcp --tcp-flags ALL FIN,URG,PSH $disposition
	run_iptables -A tcpflags -p tcp --tcp-flags ALL NONE        $disposition
	run_iptables -A tcpflags -p tcp --tcp-flags SYN,RST SYN,RST $disposition
	run_iptables -A tcpflags -p tcp --tcp-flags SYN,FIN SYN,FIN $disposition
	#
	# There are a lot of probes to ports 80, 3128 and 8080 that use a source
	# port of 0. This catches them even if they are directed at an IP that
	# hosts a web server.
	#
	run_iptables -A tcpflags -p tcp --syn --sport 0             $disposition

	for host in $hosts; do
	    ipsec=${host%^*}
	    host=${host#*^}
	    [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	    interface=${host%%:*}
	    network=${host#*:}

	    for chain in $(first_chains $interface); do
		run_iptables -A $chain -p tcp $(match_source_hosts $network) $policy -j tcpflags
	    done
	done
    fi
    #
    # ARP Filtering
    #
    save_progress_message "Setting up ARP filtering..."

    cat >> $RESTOREBASE << __EOF__
${INDENT}for f in /proc/sys/net/ipv4/conf/*; do
${INDENT}    [ -f \$f/arp_filter ] && echo 0 > \$f/arp_filter
${INDENT}    [ -f \$f/arp_ignore ] && echo 0 > \$f/arp_ignore
${INDENT}done

__EOF__

    interfaces=$(find_interfaces_by_option arp_filter)
    interfaces1=$(find_interfaces_by_option1 arp_ignore)

    if [ -n "${interfaces}${interfaces1}" ]; then
	progress_message2 "Setting up ARP Filtering..."

	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/arp_filter
	    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f $file ]; then
${INDENT}    echo 1 > $file
${INDENT}else
${INDENT}    error_message "WARNING: Cannot set ARP filtering on $interface"
${INDENT}fi
__EOF__
	done

	for interface in $interfaces1; do
	    file=/proc/sys/net/ipv4/conf/$interface/arp_ignore
	    eval value="\$$(chain_base $interface)_arp_ignore"
	    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f $file ]; then
${INDENT}    echo $value > $file
${INDENT}else
${INDENT}    error_message "WARNING: Cannot set ARP filtering on $interface"
${INDENT}fi
__EOF__
	done
    fi
    #
    # Route Filtering
    #
    interfaces="$(find_interfaces_by_option routefilter)"

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

	save_progress_message "Setting up Route Filtering..."

	cat >> $RESTOREBASE << __EOF__

${INDENT}for f in /proc/sys/net/ipv4/conf/*; do
${INDENT}    [ -f \$f/log_martians ] && echo 0 > \$f/rp_filter
${INDENT}done

__EOF__
	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/rp_filter

	    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f $file ]; then
${INDENT}    echo 1 > $file
${INDENT}else
${INDENT}    error_message "WARNING: Cannot set route filtering on $interface"
${INDENT}fi
__EOF__
	done

	save_command "echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter"

	if [ -n "$ROUTE_FILTER" ]; then
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter"
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter"
	fi

	save_command "[ -n \"\$NOROUTES\" ] || ip route flush cache"
    fi

    #
    # Martian Logging
    #
    interfaces="$(find_interfaces_by_option logmartians)"

    if [ -n "$interfaces" -o -n "$LOG_MARTIANS" ]; then
	progress_message2 "Setting up Martian Logging..."

	save_progress_message "Setting up Martian Logging..."

	cat >> $RESTOREBASE << __EOF__

${INDENT}for f in /proc/sys/net/ipv4/conf/*; do
${INDENT}    [ -f \$f/log_martians ] && echo 0 > \$f/log_martians
${INDENT}done

__EOF__
	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/log_martians

	    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f $file ]; then
${INDENT}    echo 1 > $file
${INDENT}else
${INDENT}    error_message "WARNING: Cannot set Martian logging on $interface"
${INDENT}fi
__EOF__
	    done

	if [ -n "$LOG_MARTIANS" ]; then
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/default/log_martians"
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/all/log_martians"
	fi

    fi

    #
    # Source Routing
    #
    save_progress_message "Setting up Accept Source Routing..."

    cat >> $RESTOREBASE << __EOF__
${INDENT}for f in /proc/sys/net/ipv4/conf/*; do
${INDENT}    [ -f \$f/accept_source_route ] && echo 0 > \$f/accept_source_route
${INDENT}done

__EOF__

    interfaces=$(find_interfaces_by_option sourceroute)

    if [ -n "$interfaces" ]; then
	progress_message2 "Setting up Accept Source Routing..."

	save_progress_message "Setting up Source Routing..."

	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/accept_source_route

	    cat >> $RESTOREBASE << __EOF__
${INDENT}if [ -f $file ]; then
${INDENT}    echo 1 > $file
${INDENT}else
${INDENT}    error_message "WARNING: Cannot set Accept Source Routing on $interface"
${INDENT}fi
__EOF__
	done
    fi

    if [ -n "$DYNAMIC_ZONES" ]; then
	echo "Setting up Dynamic Zone Chains..."

	for interface in $ALL_INTERFACES; do
	    for chain in $(dynamic_chains $interface); do
		createchain $chain no
	    done

	    chain=$(dynamic_in $interface)
	    createnatchain $chain

	    run_iptables -A $(input_chain $interface)   -j $chain
	    run_iptables -A $(forward_chain $interface) -j $(dynamic_fwd $interface)
	    run_iptables -A OUTPUT -o $interface        -j $(dynamic_out $interface)
	done
    fi
    #
    # UPnP
    #
    interfaces=$(find_interfaces_by_option upnp)

    if [ -n "$interfaces" ]; then
	progress_message2 "Setting up UPnP..."

	save_progress_message "Setting up UPnP..."

	createnatchain UPnP

	for interface in $interfaces; do
	    run_iptables -t nat -A PREROUTING -i $interface -j UPnP
	done
    fi

    setup_forwarding
}

#
# Scan the policy file defining the necessary chains
# Add the appropriate policy rule(s) to the end of each canonical chain
#
apply_policy_rules() {
    #
    # Create policy chains
    #
    for chain in $ALL_POLICY_CHAINS; do
	eval policy=\$${chain}_policy
	eval loglevel=\$${chain}_loglevel
	eval optional=\$${chain}_is_optional

	if [ "$policy" != NONE ]; then
	    if ! havechain $chain && [ -z "$optional" -a "$policy" != CONTINUE ]; then
		#
		# The chain doesn't exist. Create the chain and add policy
		# rules
		#
		createchain $chain yes
		#
		# If either client or server is 'all' then this MUST be
		# a policy chain and we must apply the appropriate policy rules
		#
		# Otherwise, this is a canonical chain which will be handled in
		# the for loop below
		#
		case $chain in
		    all2*|*2all)
			policy_rules $chain $policy $loglevel
			;;
		esac
	    fi
	fi
    done

    #
    # Add policy rules to canonical chains
    #
    for zone in $FW $ZONES; do
	for zone1 in $FW $ZONES; do
	    chain=${zone}2${zone1}
	    if havechain $chain; then
		run_user_exit $chain
		default_policy $zone $zone1
	    fi
	done
    done
}

#
# Activate the rules
#
activate_rules()
{
    local PREROUTING_rule=1
    local POSTROUTING_rule=1
    #
    # Jump to a NAT chain from one of the builtin nat chains
    #
    addnatjump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments
    {
	local sourcechain=$1 destchain=$2
	shift
	shift

	if havenatchain $destchain ; then
	    run_iptables2 -t nat -A $sourcechain $@ -j $destchain
	else
	    [ -n "$BRIDGING" -a -f $TMP_DIR/physdev ]      && -rm -f $TMP_DIR/physdev
	    [ -n "$IPRANGE_MATCH" -a -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange
	fi
    }

    #
    # Jump to a RULES chain from one of the builtin nat chains. These jumps are
    # are inserted before jumps to one-to-one NAT chains.
    #
    addrulejump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments
    {
	local sourcechain=$1 destchain=$2
	shift
	shift

	if havenatchain $destchain; then
	    eval run_iptables2 -t nat -I $sourcechain \
		\$${sourcechain}_rule $@ -j $destchain
	    eval ${sourcechain}_rule=\$\(\(\$${sourcechain}_rule + 1\)\)
	else
	    [ -n "$BRIDGING" -a -f $TMP_DIR/physdev ]      && rm -f $TMP_DIR/physdev
	    [ -n "$IPRANGE_MATCH" -a -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange

	fi
    }

    #
    # Create a dynamic chain for a zone and jump to it from a second chain
    #
    create_zone_dyn_chain() # $1 = zone, $2 = second chain
    {
	createchain ${1}_dyn No
	run_iptables -A $2 -j ${1}_dyn
    }
    #
    # Add jumps to early SNAT chains
    #
    for interface in $ALL_INTERFACES; do
	addnatjump POSTROUTING $(snat_chain $interface) -o $interface
    done
    #
    # Add jumps for dynamic nat chains
    #
    [ -n "$DYNAMIC_ZONES" ] && for interface in $ALL_INTERFACES ; do
	addrulejump PREROUTING $(dynamic_in $interface) -i $interface
    done
    #
    # Add jumps from the builtin chains to the nat chains
    #
    addnatjump PREROUTING  nat_in
    addnatjump POSTROUTING nat_out

    for interface in $ALL_INTERFACES; do
	addnatjump PREROUTING  $(input_chain $interface)  -i $interface
	addnatjump POSTROUTING $(output_chain $interface) -o $interface
    done

    > $STATEDIR/chains
    echo "$FW firewall" > $STATEDIR/zones
    #
    # Create forwarding chains for complex zones and generate jumps for IPSEC source hosts to that chain.
    #
    for zone in $ZONES; do
	if eval test -n \"\$${zone}_is_complex\" ; then
	    frwd_chain=${zone}_frwd
	    createchain $frwd_chain No

	    if [ -n "$POLICY_MATCH" ]; then
		#
		# Because policy match only matches an 'in' or an 'out' policy (but not both), we have to place the
	        # '--pol ipsec --dir in' rules at the front of the interface forwarding chains. Otherwise, decrypted packets
                # can match '--pol none --dir out' rules and send the packets down the wrong rules chain.
		#
	        eval is_ipsec=\$${zone}_is_ipsec

		if [ -n "$is_ipsec" ]; then
		    eval source_hosts=\$${zone}_hosts
		    [ -n "$DYNAMIC_ZONES" ] && create_zone_dyn_chain $zone $frwd_chain
		else
		    eval source_hosts=\$${zone}_ipsec_hosts
		    [ -n "$DYNAMIC_ZONES" -a -n "$source_hosts" ] && create_zone_dyn_chain $zone $frwd_chain
		fi

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

		    run_iptables2 -A $(forward_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $frwd_chain
		done
	    fi
	fi
    done

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

	chain1=$(rules_chain $FW $zone)
	chain2=$(rules_chain $zone $FW)

	eval complex=\$${zone}_is_complex
	eval type=\$${zone}_type

	[ -n "$complex" ] && frwd_chain=${zone}_frwd

	echo $zone $type $source_hosts >> $STATEDIR/zones

	if [ -n "$DYNAMIC_ZONES" ]; then
	    echo "$FW $zone $chain1" >> $STATEDIR/chains
	    echo "$zone $FW $chain2" >> $STATEDIR/chains
	fi

	need_broadcast=

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

	    [ -n "$chain1" ] && run_iptables2 -A OUTPUT -o $interface $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j $chain1

	    #
	    # Add jumps from the builtin chain for DNAT rules
	    #
	    addrulejump PREROUTING  $(dnat_chain $zone) -i $interface $(match_source_hosts $networks) $(match_ipsec_in $zone $host)

	    [ -n "$chain2" ] && run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $chain2

	    if [ -n "$complex" ] && ! is_ipsec_host $zone $host ; then
                run_iptables2 -A $(forward_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $frwd_chain
	    fi

	    case $networks in
		*.*.*.*|+*)
		    if [ "$networks" != 0.0.0.0/0 ]; then
			if ! list_search $interface $need_broadcast ; then
			    interface_has_option $interface detectnets && need_broadcast="$need_broadcast $interface"
			fi
		    fi
		    ;;
	    esac
	done

	if [ -n "$chain1" ]; then
	    for interface in $need_broadcast ; do
		run_iptables -A OUTPUT -o $interface -d 255.255.255.255 -j $chain1
		run_iptables -A OUTPUT -o $interface -d 224.0.0.0/4     -j $chain1
	    done
	fi

	for zone1 in $ZONES; do

	    eval policy=\$${zone}2${zone1}_policy

	    [ "$policy" = NONE ] && continue

	    eval dest_hosts=\$${zone1}_hosts

	    chain="$(rules_chain $zone $zone1)"

	    [ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain.

	    [ -n "$DYNAMIC_ZONES" ] && echo "$zone $zone1 $chain" >> $STATEDIR/chains

	    if [ $zone = $zone1 ]; then
		#
		# Try not to generate superfluous intra-zone rules
		#
		eval routeback=\"\$${zone}_routeback\"
		eval interfaces=\"\$${zone}_interfaces\"
		eval ports="\$${zone}_ports"

		num_ifaces=$(list_count1 $interfaces)
		#
		# If the zone has a single interface then what matters is how many ports it has
		#
		[ $num_ifaces -eq 1 -a -n "$ports" ] && num_ifaces=$(list_count1 $ports)
		#
		# If we don't need to route back and if we have only one interface or one port to
		# the zone then assume that hosts in the zone can communicate directly.
		#
		if [ $num_ifaces -lt 2 -a -z "$routeback" ] ; then
		    continue
		fi
	    else
		routeback=
		num_ifaces=0
	    fi

	    if [ -n "$complex" ]; then
		for host1 in $dest_hosts; do
		    interface1=${host1%%:*}
		    networks1=${host1#*:}
		    #
		    # Only generate an intrazone rule if the zone has more than one interface (port) or if
		    # routeback was specified for this host group
		    #
		    if [ $zone != $zone1 -o $num_ifaces -gt 1 ] || list_search $host1 $routeback ; then
			run_iptables2 -A $frwd_chain -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain
		    fi
		done
	    else
		for host in $source_hosts; do
		    interface=${host%%:*}
		    networks=${host#*:}

		    chain3=$(forward_chain $interface)

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

			if [ "$host" != "$host1" ] || list_search $host $routeback; then
			    run_iptables2 -A $chain3 $(match_source_hosts $networks) -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain
			fi
		    done
		done
	    fi
	done
    done

    for interface in $ALL_INTERFACES ; do
	run_iptables -A FORWARD -i $interface -j $(forward_chain $interface)
	run_iptables -A INPUT   -i $interface -j $(input_chain $interface)
	addnatjump POSTROUTING $(masq_chain $interface) -o $interface
    done

    chain=${FW}2${FW}

    if havechain $chain; then
	#
	# There is a fw->fw chain. Send loopback output through that chain
	#
	run_iptables -A OUTPUT -o lo -j $chain
	#
	# And delete the unconditional ACCEPT rule
	#
	run_iptables -D OUTPUT -o lo -j ACCEPT
    fi

    complete_standard_chain INPUT all $FW
    complete_standard_chain OUTPUT $FW all
    complete_standard_chain FORWARD all all
    #
    # Remove rules added to keep the firewall alive during [re]start"
    #
    disable_critical_hosts

    for chain in INPUT OUTPUT FORWARD; do
	[ -n "$FASTACCEPT" ] || run_iptables -D $chain -m state --state ESTABLISHED,RELATED -j ACCEPT
	run_iptables -D $chain -p udp --dport 53 -j ACCEPT
    done

    process_routestopped -D

    if [ -n "$LOGALLNEW" ]; then
	for table in mangle nat filter; do
	    case $table in
		mangle)
		    chains="PREROUTING INPUT FORWARD POSTROUTING"
		    ;;
		nat)
		    chains="PREROUTING POSTROUTING OUTPUT"
		    ;;
		*)
		    chains="INPUT FORWARD OUTPUT"
		    ;;
	    esac

	    for chain in $chains; do
		log_rule_limit $LOGALLNEW $chain $table $chain "" "" -I -m state --state NEW -t $table
	    done
	done
    fi
}

#
# Compile a script that will stop the firewall
#
# This function is called by compile_firewall() so all of the overloaded functions
# from that script are available here
#
compile_stop_firewall() {
    local IPTABLES_COMMAND="\$IPTABLES"
    local INDENT="    "

    cat >> $RESTOREBASE << __EOF__

stop_firewall() {

    deletechain() {
	qt \$IPTABLES -L \$1 -n && qt \$IPTABLES -F \$1 && qt \$IPTABLES -X \$1
    }

    deleteallchains() {
	\$IPTABLES -F
	\$IPTABLES -X
    }

    setcontinue() {
	\$IPTABLES -A \$1 -m state --state ESTABLISHED,RELATED -j ACCEPT
    }

    case \$COMMAND in
	stop|clear)
	    ;;
	*)
	    set +x

	    [ -n "\${RESTOREFILE:=restore}" ]

	    RESTOREPATH=/var/lib/shorewall/\$RESTOREFILE

	    if [ -x \$RESTOREPATH ]; then

		if [ -x \${RESTOREPATH}-ipsets ]; then
		    progress_message2 Restoring Ipsets...
		    #
		    # We must purge iptables to be sure that there are no
		    # references to ipsets
		    #
		    for table in mangle nat filter; do
			\$IPTABLES -t \$table -F
			\$IPTABLES -t \$table -X
		    done

		    \${RESTOREPATH}-ipsets
		fi

		echo Restoring Shorewall...

		if \$RESTOREPATH; then
		    echo "Shorewall restored from \$RESTOREPATH"
		    set_state "Started"
		else
		    set_state "Unknown"
		fi

		kill \$\$
		exit 2
	    fi
	    ;;
    esac

    set_state "Stopping"

    STOPPING="Yes"

    TERMINATOR=

    deletechain shorewall

    determine_capabilities

    run_user_exit stop

    if [ -n "\$MANGLE_ENABLED" ]; then
	run_iptables -t mangle -F
	run_iptables -t mangle -X
	for chain in PREROUTING INPUT FORWARD POSTROUTING; do
	    qt \$IPTABLES -t mangle -P \$chain ACCEPT
	done
    fi

    if [ -n "\$RAW_TABLE" ]; then
	run_iptables -t raw -F
	run_iptables -t raw -X
	for chain in PREROUTING OUTPUT; do
	    qt \$IPTABLES -t raw -P \$chain ACCEPT
	done
    fi

    if [ -n "\$NAT_ENABLED" ]; then
	delete_nat
	for chain in PREROUTING POSTROUTING OUTPUT; do
	    qt \$IPTABLES -t nat -P \$chain ACCEPT
	done
    fi

    if [ -f /var/lib/shorewall/proxyarp ]; then
	while read address interface external haveroute; do
	    qt arp -i \$external -d \$address pub
	    [ -z "\${haveroute}\${NOROUTES}" ] && qt ip route del \$address dev \$interface
	done < /var/lib/shorewall/proxyarp
    fi

    for f in /proc/sys/net/ipv4/conf/*; do
	    [ -f \$f/proxy_arp ] && echo 0 > \$f/proxy_arp
    done

__EOF__
    [ -n "$CLEAR_TC" ] && save_command "delete_tc1"

    [ -n "$DISABLE_IPV6" ] && save_command "disable_ipv6"

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then
	if [ -z "$ADMINISABSENTMINDED" ]; then
	    cat >> $RESTOREBASE << __EOF__

    for chain in INPUT OUTPUT; do
	setpolicy \$chain ACCEPT
    done

    setpolicy FORWARD DROP

    deleteallchains

    for host in $CRITICALHOSTS; do
	interface=\${host%:*}
	networks=\${host#*:}
	\$IPTABLES -A INPUT  -i \$interface \$(source_ip_range \$networks) -j ACCEPT
	\$IPTABLES -A OUTPUT -o \$interface \$(dest_ip_range \$networks) -j ACCEPT
    done

    for chain in INPUT OUTPUT; do
	setpolicy $\chain DROP
    done

__EOF__
	else
	    cat >> $RESTOREBASE << __EOF__

    for chain in INPUT OUTPUT; do
	setpolicy \$chain ACCEPT
    done

    setpolicy FORWARD DROP

    deleteallchains

    for host in $CRITICALHOSTS; do
	interface=\${host%:*}
	networks=\${host#*:}
	\$IPTABLES -A INPUT  -i \$interface \$(source_ip_range \$networks) -j ACCEPT
	\$IPTABLES -A OUTPUT -o \$interface \$(dest_ip_range \$networks) -j ACCEPT
    done

    setpolicy INPUT DROP

    for chain in INPUT FORWARD; do
	setcontinue \$chain
    done

__EOF__
	fi
    elif [ -z "$ADMINISABSENTMINDED" ]; then
	cat >> $RESTOREBASE << __EOF__

    for chain in INPUT OUTPUT FORWARD; do
	setpolicy \$chain DROP
    done

    deleteallchains

__EOF__
    else
	cat >> $RESTOREBASE << __EOF__

    for chain in INPUT FORWARD; do
	setpolicy \$chain DROP
    done

    setpolicy OUTPUT ACCEPT

    deleteallchains

    for chain in INPUT FORWARD; do
	setcontinue \$chain
    done

__EOF__
    fi

    process_routestopped -A

    save_command "\$IPTABLES -A INPUT  -i lo -j ACCEPT"

    [ -z "$ADMINISABSENTMINDED" ] && \
	save_command "\$IPTABLES -A OUTPUT -o lo -j ACCEPT"

    for interface in $(find_interfaces_by_option dhcp); do
	save_command "\$IPTABLES -A INPUT  -p udp -i $interface --dport 67:68 -j ACCEPT"
	[ -z "$ADMINISABSENTMINDED" ] && \
	    save_command "\$IPTABLES -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT"
	#
	# This might be a bridge
	#
	save_command "\$IPTABLES -A FORWARD -p udp -i $interface -o $interface --dport 67:68 -j ACCEPT"
    done

    save_command

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

    cat >> $RESTOREBASE << __EOF__

    run_user_exit stopped

    set_state "Stopped"

    logger "Shorewall Stopped"

    case \$COMMAND in
    stop|clear)
	;;
    *)
	#
	# The firewall is being stopped when we were trying to do something
	# else. Remove the lock file and Kill the shell in case we're in a
	# subshell
	#
	kill \$\$
	;;
    esac
}
__EOF__
}

#
# Compile a Restore Script
#
compile_firewall() # $1 = File Name
{
    local IPTABLES_COMMAND=run_iptables
    local INDENT=""

    outfile=$1 dir=
    #
    # Overload a couple of functions from the library
    #
    deletechain() # $1 = name of chain
    {
	save_command "qt \$IPTABLES -L $1 -n && qt \$IPTABLES -F $1 && qt \$IPTABLES -X $1"
    }

    fix_bang() {
	echo $@ | sed 's/!/! /g'
    }
    #
    # END OVERLOADED FUNCTIONS
    #

    if [ $COMMAND = compile ]; then
	dir=$(dirname $1)
	[ -d $dir ] || startup_error "Directory $dir does not exist"
	[ -h $dir ] && startup_error "$dir is a Symbolic Link"
	[ -d $outfile ] && startup_error "$outfile is a Directory"
	[ -h $outfile ] && startup_error "$outfile is a Symbolic Link"
	[ -f $outfile -a ! -x $outfile ] && startup_error "$outfile exists and is not a restore file"

	DOING=Compiling
	DONE=compiled
    else
	DOING=Checking
	DONE=checked
    fi

    RESTOREBASE=$(mktempfile /tmp)

    STATEDIR=$(mktempdir)

    [ -n "$RESTOREBASE" ] || startup_error "Cannot create temporary file in /tmp"

    [ -z "$PROGRAM" ] && save_command "#! $SHOREWALL_SHELL --"

    cat >> $RESTOREBASE << __EOF__
#
# Compiled startup file generated by Shorewall $VERSION - $(date)"
#
__EOF__

    if [ -n "$EXPORT" ]; then
	cat /usr/share/shorewall/functions >> $RESTOREBASE
    else
	cat >> $RESTOREBASE << __EOF__

. /usr/share/shorewall/functions
__EOF__

    fi

    compile_stop_firewall

cat >> $RESTOREBASE << __EOF__

setpolicy() {
    \$IPTABLES -P \$1 \$2
}

clear_firewall() {
    stop_firewall

    setpolicy INPUT ACCEPT
    setpolicy FORWARD ACCEPT
    setpolicy OUTPUT ACCEPT

    run_iptables -F

    echo 1 > /proc/sys/net/ipv4/ip_forward

__EOF__
    if [ -n "$DISABLE_IPV6" ]; then
	cat >> $RESTOREBASE << __EOF__
    if qt mywhich ip6tables; then
	ip6tables -P INPUT   ACCEPT 2> /dev/null
	ip6tables -P OUTPUT  ACCEPT 2> /dev/null
	ip6tables -P FORWARD ACCEPT 2> /dev/null
    fi

__EOF__
    fi

    cat >> $RESTOREBASE << __EOF__
    run_user_exit clear

    set_state "Cleared"

    logger "Shorewall Cleared"
}

fatal_error()
{
    echo "   ERROR: \$@" >&2
    stop_firewall
    exit 2
}

run_iptables()
{
    if ! \$IPTABLES \$@; then
	error_message "ERROR: Command \"\$IPTABLES \$@\" Failed"
	stop_firewall
	exit 2
    fi
}

run_ip()
{
    if ! ip \$@; then
	error_message "ERROR: Command \"ip \$@\" Failed"
	stop_firewall
	exit 2
    fi
}

run_tc() {
    if ! tc \$@ ; then
	error_message "ERROR: Command \"tc \$@\" Failed"
	stop_firewall
	exit 2
    fi
}

initialize() {
__EOF__
    if [ -z "$EXPORT" ]; then
	cat >> $RESTOREBASE << __EOF__
    if [ ! -f /usr/share/shorewall/version ] || [ \$(cat /usr/share/shorewall/version) != $VERSION ]; then
	error_message "ERROR: This script requires Shorewall version $VERSION"
	exit 2
    fi

__EOF__
    fi

    cat >> $RESTOREBASE << __EOF__
    #
    # These variables are required by the library functions called in this script
    #
    [ -n \${COMMAND:=restart} ]
    VERBOSE=${VERBOSE:=0}
    MODULESDIR="$MODULESDIR"
    MODULE_SUFFIX="$MODULE_SUFFIX"
    LOGLIMIT="$LOGLIMIT"
    LOGTAGONLY="$LOGTAGONLY"
    LOGRULENUMBERS="$LOGRULENUMBERS"
    LOGFORMAT="$LOGFORMAT"
    RESTOREFILE="$RESTOREFILE"
    VERSION="$VERSION"
    CONFIG_PATH="$CONFIG_PATH"

__EOF__
    if [ -n "$IPTABLES" ]; then
	cat >> $RESTOREBASE << __EOF__
    IPTABLES="$IPTABLES"

    [ -e "$IPTABLES" ] || startup_error "\$IPTABLES=$IPTABLES does not exist or is not executable"
__EOF__
   else
	cat >> $RESTOREBASE << __EOF__
    IPTABLES=\$(mywhich iptables 2> /dev/null)

    [ -z "\$IPTABLES" ] && startup_error "Can't find iptables executable"
__EOF__
    fi

    cat >> $RESTOREBASE << __EOF__

    STOPPING=
    #
    # The library requires that /var/lib/shorewall exist
    #
    [ -d /var/lib/shorewall ] || mkdir -p /var/lib/shorewall
}

__EOF__
    save_command "define_firewall() {"
    INDENT="    "

cat >> $RESTOREBASE << __EOF__

    load_kernel_modules

__EOF__

    progress_message2 "Initializing..."
    save_progress_message_short "Initializing..."

    initialize_netfilter

    progress_message2 "$DOING Proxy ARP";               setup_proxy_arp
    #
    # [re]-Establish routing
    #
    setup_providers $(find_file providers)
    [ -n "$ROUTEMARK_INTERFACES" ] &&                      setup_routes

    progress_message2 "$DOING NAT...";                  setup_nat
    progress_message2 "$DOING NETMAP...";               setup_netmap
    progress_message2 "$DOING Common Rules";            add_common_rules

    save_progress_message "Setting up SYN Flood Protection..."

    setup_syn_flood_chains

    save_progress_message "Setting up IPSEC management..."

    setup_ipsec

    maclist_hosts=$(find_hosts_by_option maclist)

    if [ -n "$maclist_hosts" ]; then
	save_progress_message "Setting up MAC Filtration..."
	setup_mac_lists
    fi

    progress_message2 "$DOING $(find_file rules)..."
    save_progress_message "Setting up Rules..."
    process_rules

    tunnels=$(find_file tunnels)
    if [ -f $tunnels ]; then
	progress_message2 "$DOING $tunnels..."
	save_progress_message "Setting up Tunnels..."
	setup_tunnels $tunnels
    fi

    save_progress_message "Setting up Actions..."

    progress_message2 "$DOING Actions...";               process_actions2
                                                            process_actions3

    save_progress_message "Applying Policies..."

    progress_message2 "$DOING $(find_file policy)...";   apply_policy_rules

    masq=$(find_file masq)
    if [ -f $masq ]; then
	setup_masq $masq
    fi

    tos=$(find_file tos)
    if [ -f $tos -a -n "$MANGLE_ENABLED" ]; then
	save_progress_message "Setting up TOS..."
	process_tos $tos
    fi

    ecn=$(find_file ecn)
    if [ -f $ecn -a -n "$MANGLE_ENABLED" ]; then
	save_progress_message "Setting up ECN..."
	setup_ecn $ecn
    fi

    if [ -n "$MANGLE_ENABLED" ]; then
	save_progress_message "Setting up TC Rules..."
	setup_tc
    fi

    progress_message2 "$DOING Rule Activation..."
    save_progress_message "Activating Rules..."
    activate_rules

    if [ -n "$ALIASES_TO_ADD" ]; then
	save_command add_ip_aliases $ALIASES_TO_ADD
    fi

    for file in chains nat proxyarp zones; do
	append_file $file
    done

    save_command "date > /var/lib/shorewall/restarted"

    run_user_exit start

    [ -n "$DELAYBLACKLISTLOAD" ] && refresh_blacklist

    createchain shorewall no

    save_command set_state "Started"

    run_user_exit started

    INDENT=
    save_command "}"
    save_command ""

    if [ -n "$PROGRAM" ]; then
	cat $(find_file prog.header) $RESTOREBASE $(find_file prog.footer) > $outfile
	rm $RESTOREBASE
    elif [ $COMMAND = compile ]; then
	save_command "initialize"
	save_command "define_firewall"
	mv -f $RESTOREBASE $outfile
    fi

    if [ $COMMAND = check ]; then
	rm -f $RESTOREBASE
	echo "Shorewall configuration verified"
    else
	chmod 700 $outfile

	progress_message2 "Shorewall configuration compiled to $outfile"
    fi

    rm -rf $TMP_DIR
    rm -rf $STATEDIR

}

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

    if [ -z "$val" ]; then
	echo "Yes"
    else case $val in
	[Yy][Ee][Ss])
	    echo "Yes"
	    ;;
	[Nn][Oo])
	    echo ""
	    ;;
	*)
	    startup_error "Invalid value ($val) for $1"
	    ;;
	esac
    fi
}

#
# Determine the value for a parameter that defaults to No
#
added_param_value_no() # $1 = Parameter Name, $2 = Parameter value
{
    local val="$2"

    if [ -z "$val" ]; then
	echo ""
    else case $val in
	[Yy][Ee][Ss])
	    echo "Yes"
	    ;;
	[Nn][Oo])
	    echo ""
	    ;;
	*)
	    startup_error "Invalid value ($val) for $1"
	    ;;
	esac
    fi
}

#
# Initialize this program
#
do_initialize() {

    # Run all utility programs using the C locale
    #
    # Thanks to Vincent Planchenault for this tip #

    export LC_ALL=C

    # Make sure umask is sane
    umask 077

    PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
    #
    # Establish termination function
    #
    TERMINATOR=startup_error
    #
    # Clear all configuration variables
    #
    VERSION=
    IPTABLES=
    FW=
    SUBSYSLOCK=
    ALLOWRELATED=Yes
    LOGRATE=
    LOGBURST=
    LOGPARMS=
    LOGLIMIT=
    ADD_IP_ALIASES=
    ADD_SNAT_ALIASES=
    TC_ENABLED=
    BLACKLIST_DISPOSITION=
    BLACKLIST_LOGLEVEL=
    CLAMPMSS=
    ROUTE_FILTER=
    LOG_MARTIANS=
    DETECT_DNAT_IPADDRS=
    MUTEX_TIMEOUT=
    FORWARDPING=
    MACLIST_DISPOSITION=
    MACLIST_LOG_LEVEL=
    TCP_FLAGS_DISPOSITION=
    TCP_FLAGS_LOG_LEVEL=
    RFC1918_LOG_LEVEL=
    MARK_IN_FORWARD_CHAIN=
    SHARED_DIR=/usr/share/shorewall
    FUNCTIONS=
    VERSION_FILE=
    LOGFORMAT=
    LOGRULENUMBERS=
    ADMINISABSENTMINDED=
    BLACKLISTNEWONLY=
    MODULE_SUFFIX=
    ACTIONS=
    USEDACTIONS=
    SMURF_LOG_LEVEL=
    DISABLE_IPV6=
    BRIDGING=
    DYNAMIC_ZONES=
    PKTTYPE=
    USEPKTYPE=
    RETAIN_ALIASES=
    DELAYBLACKLISTLOAD=
    LOGTAGONLY=
    LOGALLNEW=
    RFC1918_STRICT=
    MACLIST_TTL=
    SAVE_IPSETS=
    RESTOREFILE=
    MAPOLDACTIONS=

    RESTOREBASE=
    TMP_DIR=
    ALL_INTERFACES=
    ROUTEMARK_INTERFACES=
    IPSECMARK=256
    PROVIDERS=
    CRITICALHOSTS=
    IPSECFILE=
    EXCLUSION_SEQ=1
    STOPPING=
    HAVE_MUTEX=
    ALIASES_TO_ADD=
    SECTION=ESTABLISHED
    SECTIONS=
    ALL_PORTS=

    FUNCTIONS=$SHARED_DIR/functions

    if [ -f $FUNCTIONS ]; then
	[ $VERBOSE -ge 2 ] && echo "Loading $FUNCTIONS..."
	. $FUNCTIONS
    else
	startup_error "$FUNCTIONS does not exist!"
    fi

    TMP_DIR=$(mktempdir)

    [ -n "$TMP_DIR" ] && chmod 700 $TMP_DIR || \
       startup_error "Can't create a temporary directory"

    trap "[ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE;rm -rf $TMP_DIR; exit 2" 1 2 3 4 5 6 9

    ensure_config_path

    VERSION_FILE=$SHARED_DIR/version

    [ -f $VERSION_FILE ] && VERSION=$(cat $VERSION_FILE)

    run_user_exit params

    config=$(find_file shorewall.conf)

    if [ -f $config ]; then
	if [ -r $config ]; then
	    progress_message "Processing $config..."
	    . $config
	else
	    startup_error "Cannot read $config (Hint: Are you root?)"
	fi
    else
	startup_error "$config does not exist!"
    fi
    #
    # Restore CONFIG_PATH if the shorewall.conf file cleared it
    #
    ensure_config_path
    #
    # Determine the capabilities of the installed iptables/netfilter
    # We load the kernel modules here to accurately determine
    # capabilities when module autoloading isn't enabled.
    #
    PKTTYPE=$(added_param_value_no PKTTYPE $PKTTYPE)

    [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ]
    if [ -z "$EXPORT" ]; then

	load_kernel_modules

	if [ -z "$IPTABLES" ]; then
	    IPTABLES=$(mywhich iptables 2> /dev/null)

	    [ -z "$IPTABLES" ] && startup_error "Can't find iptables executable"
	else
	    [ -e "$IPTABLES" ] || startup_error "\$IPTABLES=$IPTABLES does not exist or is not executable"
	fi
	determine_capabilities

    else
	f=$(find_file capabilities)

	[ -f $f ] && . $f || startup_error "The -e flag requires a capabilities file"
    fi

    ALLOWRELATED="$(added_param_value_yes ALLOWRELATED $ALLOWRELATED)"
    [ -n "$ALLOWRELATED" ] || \
	startup_error "ALLOWRELATED=No is not supported"
    ADD_IP_ALIASES="$(added_param_value_yes ADD_IP_ALIASES $ADD_IP_ALIASES)"

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

    if [ -n "$IP_FORWARDING" ]; then
	case "$IP_FORWARDING" in
	[Oo][Nn]|[Oo][Ff][Ff]|[Kk][Ee][Ee][Pp])
	    ;;
	*)
	    startup_error "Invalid value ($IP_FORWARDING) for IP_FORWARDING"
	    ;;
	esac
    else
	IP_FORWARDING=On
    fi

   [ -n "${BLACKLIST_DISPOSITION:=DROP}" ]

    case "$CLAMPMSS" in
	[0-9]*)
	    ;;
	*)
	    CLAMPMSS=$(added_param_value_no CLAMPMSS $CLAMPMSS)
	    ;;
    esac

    ADD_SNAT_ALIASES=$(added_param_value_no ADD_SNAT_ALIASES $ADD_SNAT_ALIASES)
    ROUTE_FILTER=$(added_param_value_no ROUTE_FILTER $ROUTE_FILTER)
    LOG_MARTIANS=$(added_param_value_no LOG_MARTIANS $LOG_MARTIANS)
    DETECT_DNAT_IPADDRS=$(added_param_value_no DETECT_DNAT_IPADDRS $DETECT_DNAT_IPADDRS)
    FORWARDPING=$(added_param_value_no FORWARDPING $FORWARDPING)
    [ -n "$FORWARDPING" ] && \
	startup_error "FORWARDPING=Yes is no longer supported"

    maclist_target=reject

    if [ -n "$MACLIST_DISPOSITION" ] ; then
	case $MACLIST_DISPOSITION in
	    REJECT)
		;;
	    DROP)
		maclist_target=DROP
		;;
	    ACCEPT)
		maclist_target=RETURN
		;;
	    *)
		startup_error "Invalid value ($MACLIST_DISPOSITION) for MACLIST_DISPOSITION"
		;;
	esac
    else
	MACLIST_DISPOSITION=REJECT
    fi

    if [ -n "$TCP_FLAGS_DISPOSITION" ] ; then
	case $TCP_FLAGS_DISPOSITION in
	    REJECT|ACCEPT|DROP)
		;;
	    *)
		startup_error "Invalid value ($TCP_FLAGS_DISPOSITION) for TCP_FLAGS_DISPOSITION"
		;;
	esac
    else
	TCP_FLAGS_DISPOSITION=DROP
    fi

    [ -n "${RFC1918_LOG_LEVEL:=info}" ]

    MARK_IN_FORWARD_CHAIN=$(added_param_value_no MARK_IN_FORWARD_CHAIN $MARK_IN_FORWARD_CHAIN)
    [ -n "$MARK_IN_FORWARD_CHAIN" ] && MARKING_CHAIN=tcfor || MARKING_CHAIN=tcpre
    CLEAR_TC=$(added_param_value_yes CLEAR_TC $CLEAR_TC)

    if [ -n "$LOGFORMAT" ]; then
	if [ -n "$(echo $LOGFORMAT | grep '%d')" ]; then
	    LOGRULENUMBERS=Yes
	    temp=$(printf "$LOGFORMAT" fooxx 1 barxx 2> /dev/null)
	    if [ $? -ne 0 ]; then
		startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\""
	    fi
	else
	    temp=$(printf "$LOGFORMAT" fooxx barxx 2> /dev/null)
	    if [ $? -ne 0 ]; then
		startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\""
	    fi
	fi

	[ ${#temp} -le 29 ] || startup_error "LOGFORMAT string is longer than 29 characters: \"$LOGFORMAT\""
    else
	LOGFORMAT="Shorewall:%s:%s:"
    fi
    ADMINISABSENTMINDED=$(added_param_value_no ADMINISABSENTMINDED $ADMINISABSENTMINDED)
    BLACKLISTNEWONLY=$(added_param_value_no BLACKLISTNEWONLY $BLACKLISTNEWONLY)
    DISABLE_IPV6=$(added_param_value_no DISABLE_IPV6 $DISABLE_IPV6)
    BRIDGING=$(added_param_value_no BRIDGING $BRIDGING)
    DYNAMIC_ZONES=$(added_param_value_no DYNAMIC_ZONES $DYNAMIC_ZONES)
    STARTUP_ENABLED=$(added_param_value_yes STARTUP_ENABLED $STARTUP_ENABLED)
    RETAIN_ALIASES=$(added_param_value_no RETAIN_ALIASES $RETAIN_ALIASES)
    [ -n "${ADD_IP_ALIASES}${ADD_SNAT_ALIASES}" ] || RETAIN_ALIASES=
    DELAYBLACKLISTLOAD=$(added_param_value_no DELAYBLACKLISTLOAD $DELAYBLACKLISTLOAD)
    LOGTAGONLY=$(added_param_value_no LOGTAGONLY $LOGTAGONLY)
    RFC1918_STRICT=$(added_param_value_no RFC1918_STRICT $RFC1918_STRICT)
    SAVE_IPSETS=$(added_param_value_no SAVE_IPSETS $SAVE_IPSETS)
    MAPOLDACTIONS=$(added_param_value_yes MAPOLDACTIONS $MAPOLDACTIONS)
    FASTACCEPT=$(added_param_value_no FASTACCEPT $FASTACCEPT)

    case ${IPSECFILE:=ipsec} in
	ipsec|zones)
	    ;;
	*)
	    startup_error "Invalid value ($IPSECFILE) for IPSECFILE option"
	    ;;
    esac

    case ${MACLIST_TABLE:=filter} in
	filter)
	    ;;
	mangle)
	    [ $MACLIST_DISPOSITION = reject ] && startup_error "MACLIST_DISPOSITION=REJECT is not allowed with MACLIST_TABLE=mangle"
	    ;;	*)
	    startup_error "Invalid value ($MACLIST_TABLE) for MACLIST_TABLE option"
	    ;;
    esac

   TC_SCRIPT=

   if [ -n "$TC_ENABLED" ] ; then
	case "$TC_ENABLED" in
	    [Yy][Ee][Ss])
		TC_ENABLED=
		TC_SCRIPT=$(find_file tcstart)
		[ -f $TC_SCRIPT ] || startup_error "Unable to find tcstart file"
		;;
	    [Ii][Nn][Tt][Ee][Rr][Nn][Aa][Ll])
		TC_ENABLED=Yes
		;;
	    [Nn][Oo])
		TC_ENABLED=
		;;
	esac
    else
	TC_ENABLED=Yes
    fi

    if [ -n "$TC_ENABLED" ];then
	[ -n "$MANGLE_ENABLED" ] || startup_error "Traffic Shaping requires mangle support in your kernel and iptables"
    fi

    [ "x${SHOREWALL_DIR}" = "x." ] && SHOREWALL_DIR="$PWD"

    #
    # Strip the files that we use often
    #
    strip_file interfaces
    strip_file hosts
    #
    # Check out the user's shell
    #
    [ -n "${SHOREWALL_SHELL:=/bin/sh}" ]

    temp=$(decodeaddr 192.168.1.1)
    if [ $(encodeaddr $temp) != 192.168.1.1 ]; then
	startup_error "Shell $SHOREWALL_SHELL is broken and may not be used with Shorewall"
    fi

    rm -f $TMP_DIR/physdev
    rm -f $TMP_DIR/iprange
}

#
# Give Usage Information
#
usage() {
    echo "Usage: $0 [debug] check|{generate|compile} <filename>}"
    exit 1
}

#
# E X E C U T I O N    B E G I N S   H E R E
#
#
# Start trace if first arg is "debug"
#
[ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; }

NOLOCK=

[ $# -gt 1 ] && [ "$1" = "nolock" ] && { NOLOCK=Yes; shift ; }

trap "exit 2" 1 2 3 4 5 6 9

COMMAND="$1"

case "$COMMAND" in

    check)
	[ $# -ne 1 ] && usage
	do_initialize
	compile_firewall
	;;

    compile|generate)
	[ $# -ne 2 ] && usage
	do_initialize
	COMMAND=compile
	compile_firewall $2
	;;

   call)
	#
	# Undocumented way to call functions in /usr/share/shorewall/firewall directly
	#
	shift
	do_initialize
	EMPTY=
	$@
	;;

    *)
	usage
	;;

esac