#!/bin/sh
#
#     The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V3.0
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
#     (c) 1999,2000,2001,2002,2003,2004,2005 - 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:
#
#	   shorewall start			  Starts the firewall
#	   shorewall restart			  Restarts the firewall
#	   shorewall stop			  Stops the firewall
#	   shorewall reset			  Resets iptables packet and
#						  byte counts
#	   shorewall clear			  Remove all Shorewall chains
#						  and rules/policies.
#	   shorewall refresh	.		  Rebuild the common chain
#          shorewall check                        Verify the more heavily-used
#                                                 configuration files.
#
# 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.
#
my_mutex_on() {
    [ -n "$nolock" ] || { mutex_on; HAVE_MUTEX=Yes; }
}

my_mutex_off() {
    [ -n "$HAVE_MUTEX" ] && { mutex_off; HAVE_MUTEX=; }
}

#
# Message to stderr
#
error_message() # $* = Error Message
{
   echo "   $@" >&2
}

#
# Fatal error -- stops the firewall after issuing the error message
#
fatal_error() # $* = Error Message
{
    echo "   ERROR: $@" >&2
    if [ $COMMAND = check ]; then
	[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    else
	stop_firewall
    fi
    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
    my_mutex_off
    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE
    kill $$
    exit 2
}

#
# Send a message to STDOUT and the System Log
#
report () { # $* = message
    echo "$@"
    logger "$@"
}

#
# Write the passed args to $RESTOREBASE
#
save_command()
{
    echo "$@" >> $RESTOREBASE
}

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

#
# Save the passed command in the restore script then run it -- returns the status of the command
# If the command involves file redirection then it must be enclosed in quotes as in:
#
#     run_and_save_command "echo 1 > /proc/sys/net/ipv4/ip_forward"
#
run_and_save_command()
{
    echo "$@" >> $RESTOREBASE
    eval $*
}

#
# Run the passed command and if it succeeds, save it in the restore script. If it fails, stop the firewall and die
#
ensure_and_save_command()
{
    if eval $* ; then
	echo "$@" >> $RESTOREBASE
    else
	[ -z "$STOPPING" ] && { stop_firewall; exit 2; }
    fi
}

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

#
# Run iptables and if an error occurs, stop the firewall and quit
#
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

    if ! $IPTABLES $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"$IPTABLES $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

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

    case "$@" in
	*!*)
	    run_iptables $(fix_bang $@)
	    ;;
	*)
	    run_iptables $@
	    ;;
    esac

}

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

    qt $IPTABLES $@
}

#
# Run ip and if an error occurs, stop the firewall and quit
#
run_ip() {
    if ! ip $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"ip $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

#
# Run tc and if an error occurs, stop the firewall and quit
#
run_tc() {
    if ! tc $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"tc $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

#
# Run ipset and if an error occurs, stop the firewall and quit
#
run_ipset() {
    if ! ipset $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"ipset $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

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

    if [ "$COMMAND" != check ]; then
	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
    fi
}

#
# Create a filter chain
#
# If the chain isn't one of the common chains then add a rule to the chain
# allowing packets that are part of an established connection. Create a
# variable 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)

    if $IPTABLES -N $1; then

	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
    fi

}

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

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

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

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

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

#
# Delete a chain if it exists
#
deletechain() # $1 = name of chain
{
    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"
}

#
# Source IP range
#
source_ip_range() # $1 = Address or Address Range
{
    case $1 in
	*.*.*.*-*.*.*.*)
	    case $1 in
		!*)
		    iprange_echo "! --src-range ${1#!}"
		    ;;
		*)
		    iprange_echo "--src-range $1"
		    ;;
	    esac
	    ;;
	!+*)
	    echo "-m set ! $(get_set_flags ${1#!} src)"
	    ;;
	+*)
	    echo "-m set $(get_set_flags $1 src)"
	    ;;
	*)
	    echo "-s $1"
	    ;;
    esac
}

#
# Destination IP range
#
dest_ip_range() # $1 = Address or Address Range
{
    case $1 in
	*.*.*.*-*.*.*.*)
	    case $1 in
		!*)
		    iprange_echo "! --dst-range ${1#!}"
		    ;;
		*)
		    iprange_echo "--dst-range $1"
		    ;;
	    esac
	    ;;
	!+*)
	    echo "-m set ! $(get_set_flags ${1#!} dst)"
	    ;;
	+*)
	    echo "-m set $(get_set_flags $1 dst)"
	    ;;
	*)
	    echo "-d $1"
	    ;;
    esac
}

both_ip_ranges() # $1 = Source address or range, $2 = dest address or range
{
    local rangeprefix= setprefix= rangematch= setmatch=

    case $1 in
	*.*.*.*-*.*.*.*)
	    rangeprefix="-m iprange"
	    rangematch="--src-range $1"
	    ;;
	!+*)
	    setprefix="-m set"
	    setmatch="! $(get_set_flags ${1#!} src)"
	    ;;
	+*)
	    setprefix="-m set"
	    setmatch="$(get_set_flags $1 src)"
	    ;;
	*)
	    rangematch="-s $1"
	    ;;
    esac

    case $2 in
	*.*.*.*-*.*.*.*)
	    rangeprefix="-m iprange"
	    rangematch="$rangematch --dst-range $2"
	    ;;
	!+*)
	    setprefix="-m set"
	    match="$setmatch ! $(get_set_flags ${2#!} dst)"
	    ;;
	+*)
	    setprefix="-m set"
	    setmatch="$setmatch $(get_set_flags $2 dst)"
	    ;;
	*)
	    rangematch="$rangematch -d $2"
	    ;;
    esac

    echo "$rangeprefix $rangematch $setprefix $setmatch"
}

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

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

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

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

#
# 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
	    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|logmartians|sourceroute|blacklist|proxyarp|maclist|nosmurfs|upnp|-)
		    ;;
		norfc1918)
		    addr=$(ip -f inet addr show $interface 2> /dev/null | grep inet | head -n1)
		    if [ -n "$addr" ]; then
			addr=$(echo $addr | sed 's/inet //;s/\/.*//;s/ peer.*//')
			for network in 10.0.0.0/8 176.16.0.0/12 192.168.0.0/16; do
			    if in_network $addr $network; then
				startup_error "The 'norfc1918' option may not be specified on an interface with an RFC 1918 address. Interface:$interface"
			    fi
			done
		    fi
		    ;;
		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"
		    ;;
		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)

    copy_table() {
	run_ip route show table $duplicate | while read net route; do
	    case $net in
		default|nexthop)
		    ;;
		*)
		    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add table $number $net $route"
		    ;;
	    esac
	done
    }

    copy_and_edit_table() {
	run_ip route show table $duplicate | while read net route; do
	    case $net in
		default|nexthop)
		    ;;
		*)
		    if list_search $(find_device $route) $copy; then
			ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add table $number $net $route"
		    fi
		    ;;
	    esac
	done
    }

    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

	if [ $COMMAND != check ]; then
	    run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip route flush table $number"

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

	if [ "x$gateway" = xdetect ] ; then
	    #
	    # First assume that this is some sort of point-to-point interface
	    #
	    gateway=$( find_peer $(ip addr ls $interface ) )
	    #
	    # Maybe there's a default route through this gateway already
	    #
	    [ -n "$gateway" ] || gateway=$(find_gateway $(ip route ls dev $interface))
	    #
	    # Last hope -- is there a load-balancing route through the interface?
	    #
	    [ -n "$gateway" ] || gateway=$(find_nexthop $interface)
	    #
	    # Be sure we found one
	    #
	    [ -n "$gateway" ] || fatal_error "Unable to detect the gateway through interface $interface"
	fi

	if [ $COMMAND != check ]; then
	    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace $gateway dev $interface table $number"
	    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add default via $gateway dev $interface table $number"
	fi

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

	    eval ${table}_mark=$mark

	    if [ $COMMAND != check ]; then
		run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip rule del fwmark $mark"
		ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip rule add fwmark $mark pref $((10000 + $mark)) table $number"
	    fi
	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=*)
		    DEFAULT_ROUTE="$DEFAULT_ROUTE nexthop via $gateway dev $interface weight ${option#*=}"
		    ;;
		balance)
		    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 [ $COMMAND != check ]; then
	    find_interface_addresses $interface | while read address; do
		run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip rule del from $address"
		if [ -z "$loose" ]; then
		    pref=$((20000 + $rulenum * 1000 + $number ))
		    rulenum=$(($rulenum + 1))
		    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip rule add from $address pref $pref table $number"
		fi
	    done
	fi
    }

    strip_file providers $1

    if [ -s $TMP_DIR/providers ]; then
	if [ $COMMAND != check ]; then
	    echo "Processing $1..."

	    save_progress_message "Restoring Providers..."
	else
	    echo "Validating $1..."
	fi

	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 Added"
	done < $TMP_DIR/providers

	if [ $COMMAND != check ]; then
	    if [ -n "$PROVIDERS" ]; then
		if [ -n "$DEFAULT_ROUTE" ]; then
		    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace default scope global $DEFAULT_ROUTE"
		    progress_message "   Default route $DEFAULT_ROUTE Added."
		fi

		cat > /etc/iproute2/rt_tables <<EOF
#
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
EOF

		for table in $PROVIDERS; do
		    eval number=\$${table}_number
		    ${echobin:-echo} -e "$number\t$table" >>  /etc/iproute2/rt_tables
		done

		save_command "cat > /etc/iproute2/rt_tables << __EOF__"
		cat /etc/iproute2/rt_tables >> $RESTOREBASE
		save_command __EOF__

	    fi

	    ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route flush cache"
	fi
    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

       eval ports=\$${iface}_ports
       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
		   maclist|norfc1918|blacklist|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

       if [ -n "$ports" ]; then
	   eval ${iface}_ports=\"$ports\"
	   eval ${z}_ports=\"$zports\"
       fi

   done < $TMP_DIR/hosts

   [ -n "$all_ports" ] && echo "   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
    {
	[ $COMMAND != check ] || \
	    [ $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
#
find_broadcasts() {
    for interface in $ALL_INTERFACES; do
	eval bcast=\$$(chain_base $interface)_broadcast
	if [ "x$bcast" = "xdetect" ]; then
	    ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u
	elif [ "x${bcast}" != "x-" ]; then
	    echo $(separate_list $bcast)
	fi
    done
}

#
# Find interface address--returns the first IP address assigned to the passed
# device
#
find_first_interface_address() # $1 = interface
{
    #
    # get the line of output containing the first IP address
    #
    addr=$(ip -f inet addr show $1 2> /dev/null | grep inet | head -n1)
    #
    # If there wasn't one, bail out now
    #
    [ -n "$addr" ] || fatal_error "Can't determine the IP address of $1"
    #
    # Strip off the trailing VLSM mask (or the peer IP in case of a P-t-P link)
    # along with everything else on the line
    #
    echo $addr | sed 's/inet //;s/\/.*//;s/ peer.*//'
}

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

##
# Source a user exit file if it exists
#
run_user_exit() # $1 = file name
{
    local user_exit=$(find_file $1)

    if [ -f $user_exit ]; then
	progress_message "Processing $user_exit ..."
	. $user_exit
    fi
}

#
# Add a logging rule.
#
log_rule_limit() # $1 = log level, $2 = chain, $3 = display Chain $4 = disposition , $5 = rate limit $6=log tag $7=command $... = predicates for the rule
{
    local level=$1
    local chain=$2
    local displayChain=$3
    local disposition=$4
    local rulenum=
    local limit="${5:-$LOGLIMIT}"
    local tag=${6:+$6 }
    local command=${7:--A}
    local prefix
    local base=$(chain_base $displayChain)

    shift;shift;shift;shift;shift;shift;shift

    if [ -n "$tag" -a -n "$LOGTAGONLY" ]; then
	displayChain=$tag
	tag=
    fi

    if [ -n "$LOGRULENUMBERS" ]; then
	eval rulenum=\$${base}_logrules

	rulenum=${rulenum:-1}

	prefix="$(printf "$LOGFORMAT" $displayChain $rulenum $disposition)${tag}"

	rulenum=$(($rulenum + 1))
	eval ${base}_logrules=$rulenum
    else
	prefix="$(printf "$LOGFORMAT" $displayChain $disposition)${tag}"
    fi

    if [ ${#prefix} -gt 29 ]; then
	prefix="$(echo $prefix | truncate 29)"
	error_message "WARNING: Log Prefix shortened to \"$prefix\""
    fi

    case $level in
	ULOG)
	    if ! $IPTABLES $command $chain $@ $limit -j ULOG $LOGPARMS --ulog-prefix "$prefix" ; then
		if [ -z "$STOPPING" ]; then
		    error_message "ERROR: Command \"$IPTABLES $command $chain $@ $limit -j ULOG $LOGPARMS --ulog-prefix \"$prefix\"\" Failed"
		    stop_firewall
		    exit 2
		fi
	    fi
	    ;;
	*)
	    if ! $IPTABLES $command $chain $@ $limit -j LOG $LOGPARMS --log-level $level --log-prefix "$prefix"; then
		if [ -z "$STOPPING" ]; then
		    error_message "ERROR: Command \"$IPTABLES $command $chain $@ $limit -j  LOG $LOGPARMS --log-level $level --log-prefix \"$prefix\"\" Failed"
		    stop_firewall
		    exit 2
		fi
	    fi
	    ;;
    esac

    if [ $? -ne 0 ] ; then
	[ -z "$STOPPING" ] && { stop_firewall; exit 2; }
    fi
}

log_rule() # $1 = log level, $2 = chain, $3 = disposition , $... = predicates for the rule
{
    local level=$1
    local chain=$2
    local disposition=$3

    shift;shift;shift

    log_rule_limit $level $chain $chain $disposition "$LOGLIMIT" "" -A $@
}

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

    save_progress_message "Restoring IP Forwarding..."

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

#
# Disable IPV6
#
disable_ipv6() {
    local foo="$(ip -f inet6 addr ls 2> /dev/null)"

    if [ -n "$foo" ]; then
	if qt mywhich ip6tables; then
	    save_progress_message "Disabling IPV6..."
	    ip6tables -P FORWARD DROP && save_command ip6tables -P FORWARD DROP
	    ip6tables -P INPUT DROP   && save_command ip6tables -P INPUT DROP
	    ip6tables -P OUTPUT DROP  && save_command ip6tables -P OUTPUT DROP
	else
	    error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables"
	fi
    fi
}

disable_ipv6_1() {
    local foo="$(ip -f inet6 addr ls 2> /dev/null)"

    if [ -n "$foo" ]; then
	if qt mywhich ip6tables; then
	    progress_message "Disabling IPV6..."
	    ip6tables -P FORWARD DROP
	    ip6tables -P INPUT DROP
	    ip6tables -P OUTPUT DROP
	else
	    error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables"
	fi
    fi
}

#
# 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#*:}
	$IPTABLES $1 INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	[ -z "$ADMINISABSENTMINDED" -o $COMMAND != stop ] && \
	    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#*:}
	$IPTABLES -A INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	$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#*:}
	$IPTABLES -D INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	$IPTABLES -D OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
    done
}

#
# Stop the Firewall
#
stop_firewall() {
    #
    # Turn off trace unless we were tracing "stop" or "clear"
    #

    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE

    case $COMMAND in
	stop|clear)
	    ;;
	check)
	    kill $$
	    exit 2
	    ;;
	*)
	    set +x

	    [ -n "${RESTOREFILE:=restore}" ]

	    RESTOREPATH=/var/lib/shorewall/$RESTOREFILE

	    if [ -x $RESTOREPATH ]; then

		if [ -x ${RESTOREPATH}-ipsets ]; then
		    echo Restoring Ipsets...
		    #
		    # We must purge iptables to be sure that there are no
		    # references to ipsets
		    #
		    iptables -F
		    iptables -X
		    ${RESTOREPATH}-ipsets
		fi

		echo Restoring Shorewall...

		if $RESTOREPATH; then
		    echo "Shorewall restored from $RESTOREPATH"
		    set_state "Started"
		else
		    set_state "Unknown"
		fi

		my_mutex_off
		kill $$
		exit 2
	    fi
	    ;;
    esac

    set_state "Stopping"

    STOPPING="Yes"

    TERMINATOR=

    deletechain shorewall

    run_user_exit stop

    [ -n "$MANGLE_ENABLED" ] && \
	run_iptables -t mangle -F && \
	run_iptables -t mangle -X

    [ -n "$RAW_TABLE" ] && \
	run_iptables -t raw -F && \
	run_iptables -t raw -X

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

    [ -n "$DISABLE_IPV6" ] && disable_ipv6_1

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then
	if [ -z "$ADMINISABSENTMINDED" ]; then
	    for chain in INPUT OUTPUT; do
		setpolicy $chain ACCEPT
	    done

	    setpolicy FORWARD DROP

	    deleteallchains

	    enable_critical_hosts

	    for chain in INPUT OUTPUT; do
		setpolicy $chain DROP
	    done
	else
	    for chain in INPUT OUTPUT; do
		setpolicy $chain ACCEPT
	    done

	    setpolicy FORWARD DROP

	    deleteallchains

	    enable_critical_hosts

	    setpolicy INPUT DROP

	    for chain in INPUT FORWARD; do
		setcontinue $chain
	    done
	fi
    elif [ -z "$ADMINISABSENTMINDED" ]; then
	for chain in INPUT OUTPUT FORWARD; do
	    setpolicy $chain DROP
	done

	deleteallchains
    else
	for chain in INPUT FORWARD; do
	    setpolicy $chain DROP
	done

	setpolicy OUTPUT ACCEPT

	deleteallchains

	for chain in INPUT FORWARD; do
	    setcontinue $chain
	done
    fi

    process_routestopped -A

    $IPTABLES -A INPUT  -i lo -j ACCEPT
    [ -z "$ADMINISABSENTMINDED" ] && \
	$IPTABLES -A OUTPUT -o lo -j ACCEPT

    for interface in $(find_interfaces_by_option dhcp); do
	$IPTABLES -A INPUT  -p udp -i $interface --dport 67:68 -j ACCEPT
	[ -z "$ADMINISABSENTMINDED" ] && \
	    $IPTABLES -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT
	#
	# This might be a bridge
	#
	$IPTABLES -A FORWARD -p udp -i $interface -o $interface --dport 67:68 -j ACCEPT
    done

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

    run_user_exit stopped

    set_state "Stopped"

    logger "Shorewall Stopped"

    rm -rf $TMP_DIR

    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
	#
	my_mutex_off
	kill $$
	;;
    esac
}

#
# Remove all rules and remove all user-defined chains
#
clear_firewall() {
    stop_firewall

    setpolicy INPUT ACCEPT
    setpolicy FORWARD ACCEPT
    setpolicy OUTPUT ACCEPT

    run_iptables -F

    echo 1 > /proc/sys/net/ipv4/ip_forward

    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

    run_user_exit clear

    set_state "Cleared"

    logger "Shorewall Cleared"
}

#
# 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##*:}
		;;
	    *:*)
		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##*:}
		;;
	    *:*)
		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##*:}
		;;
	    *:*)
		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], $3 = Gateway Zone
    {
	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

	for z in $(separate_list $3); do
	    if validate_zone $z; then
		addrule ${FW}2${z} -p $protocol $p -j ACCEPT
		addrule ${z}2${FW} -p $protocol $p -j ACCEPT
	    else
		error_message "Warning: Invalid gateway zone ($z)" \
		" -- Tunnel \"$tunnel\" may encounter problems"
	    fi
	done

	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 $z1
		    ;;
		*)
		    error_message "Tunnels of type $kind are not supported:" \
			"Tunnel \"$tunnel\" Ignored"
		    ;;
	    esac
	else
	    error_message "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
	    case $COMMAND in
		start|restart)
		    ensurechain $1
		    run_iptables -I $1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss $2
		;;
	    esac
	fi
    }
    #
    # Set up rules to set MSS to and/or from zone "$zone"
    #
    set_mss() # $1 = MSS value, $2 = _in, _out or ""
    {
	if [ $COMMAND != check ]; then
	    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
	fi
    }

    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 "Processing $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 [ $COMMAND != check ]; then
	    if [ -z "$haveroute" ]; then
		ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace $address dev $interface"
		[ -n "$persistent" ] && haveroute=yes
	    fi

	    ensure_and_save_command arp -i $external -Ds $address $external pub

	    echo $address $interface $external $haveroute >> /var/lib/shorewall/proxyarp
	fi

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

    if [ $COMMAND != check ]; then
	> /var/lib/shorewall/proxyarp

	save_progress_message "Restoring Proxy ARP..."
    fi

    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

    if [ $COMMAND != check ]; then
	for interface in $resetlist; do
	    list_search $interface $setlist || \
		run_and_save_command "echo 0 > /proc/sys/net/ipv4/conf/$interface/proxy_arp"
	done

	for interface in $setlist; do
	    run_and_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 echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp 2> /dev/null; then
		progress_message "   Enabled proxy ARP on $interface"
		save_command "echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp"
	    else
		error_message "WARNING: Unable to enable proxy ARP on $interface"
	    fi
	done
    fi
}

#
# 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
		;;
	    *)
		run_iptables -t mangle -N $1
		;;
	esac
    }

    have_mac_chain()
    {
	local result

	case $MACLIST_TABLE in
	    filter)
		havechain $1 && result=0 || result=1
		;;
	    *)
		mangle_chain_exists $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 [ -n "$MACLIST_TTL" ]; then
	    chain1=$(macrecent_target $interface)
	    create_mac_chain $chain1
	    run_iptables -t $MACLIST_TABLE -A $chain  -m recent --rcheck --seconds $MACLIST_TTL --name $chain -j RETURN
	    run_iptables -t $MACLIST_TABLE -A $chain                                                          -j $chain1
	    run_iptables -t $MACLIST_TABLE -A $chain  -m recent --update                        --name $chain -j RETURN
	    run_iptables -t $MACLIST_TABLE -A $chain  -m recent --set                           --name $chain
	fi
    done
    #
    # Process the maclist file producing the verification rules
    #
    while read interface mac addresses; do
	expandv interface mac addresses

	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

	macpart=$(mac_match $mac)

	if [ -z "$addresses" ]; then
	    run_iptables -t $MACLIST_TABLE -A $chain $macpart $physdev_part -j RETURN
	else
	    for address in $(separate_list $addresses) ; do
		run_iptables2 -t $MACLIST_TABLE -A $chain $macpart -s $address $physdev_part -j RETURN
	    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)

	blob=$(ip link show $interface 2> /dev/null)

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

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

	    run_iptables -t $MACLIST_TABLE -A $chain -s $address -d 255.255.255.255 -j RETURN
	    run_iptables -t $MACLIST_TABLE -A $chain -s $address -d 224.0.0.0/4     -j RETURN
	done

	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 -t $MACLIST_TABLE -A $chain -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() {
    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

	rm -f /var/lib/shorewall/proxyarp
    fi

    [ -d /var/lib/shorewall ] && touch /var/lib/shorewall/proxyarp

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

#
# 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" ] || run_and_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 [ $COMMAND != check ]; then
	    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
	fi

	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
    #
    [ $COMMAND = check ] || > /var/lib/shorewall/nat

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

    [ -n "$RETAIN_ALIASES" -o $COMMAND = check ] || save_progress_message "Restoring 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

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

	rm -f {/var/lib/shorewall}/nat
    fi

    [ -d /var/lib/shorewall ] && touch /var/lib/shorewall/nat
}

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

    echo "Processing $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 mangle_chain_exists $chain; then
		flushmangle $chain
	    else
		run_iptables -t mangle -N $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

    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() {
	echo "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() {
	echo "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)
	run_and_save_command qt tc qdisc del dev $device root
	run_and_save_command qt tc qdisc del dev $device ingress
	ensure_and_save_command tc qdisc add dev $device root handle $devnum: htb default $defmark
	ensure_and_save_command tc class add dev $device parent $devnum: classid $devnum:1 htb rate $outband
	ensure_and_save_command tc qdisc add dev $device handle ffff: ingress
	ensure_and_save_command 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_command tc class add dev $device parent $devnum:1 classid $classid htb rate $rate ceil $ceil prio $prio quantum $(calculate_quantum $rate)
	ensure_and_save_command 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_command 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_command 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_command 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_command 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_command 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_command 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_command 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 [ $COMMAND != check ]; then
	if [ -s $TMP_DIR/tcdevices ]; then
	    save_progress_message "Restoring Traffic Control..."
	    echo "Processing $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 Added."
	    done < $TMP_DIR/tcdevices
	fi

	if [ -s $TMP_DIR/tcclasses ]; then
	    echo "Processing $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\" Added."
	    done < $TMP_DIR/tcclasses
	fi
    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

	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\" added"
}

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

    run_iptables -t mangle -N tcpre
    run_iptables -t mangle -N tcfor
    run_iptables -t mangle -N tcout
    run_iptables -t mangle -N tcpost
    #
    # Process the TC Rules File
    #
    strip_file tcrules

    while read mark sources dests proto ports sports user testval; do
	expandv mark sources dests proto ports sports user testval
	rule=$(echo "$mark $sources $dests $proto $ports $sports $user $testval")
	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

    f=$(find_file tcstart)

    if [ -f $f ]; then

	run_user_exit tcstart

	f=$(find_file tcstart) # In case the script used this variable

        save_progress_message "Restoring Traffic Control..."
	save_command . $f
    else
	setup_traffic_shaping
    fi
}

setup_tc() {

    echo "Setting up Traffic Control Rules..."

    setup_tc1
}

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

    }

    save_progress_message "Clearing Traffic Control/QOS"

    run_user_exit tcclear

    run_ip link list | \
    while read inx interface details; do
	case $inx in
	    [0-9]*)
		clear_one_tc ${interface%:}
		;;
	    *)
		;;
	esac
    done
}

delete_tc1()
{
    clear_one_tc() {
	tc qdisc del dev $1 root 2> /dev/null
	tc qdisc del dev $1 ingress 2> /dev/null

    }

    run_user_exit tcclear

    run_ip link list | \
    while read inx interface details; do
	case $inx in
	    [0-9]*)
		clear_one_tc ${interface%:}
		;;
	    *)
		;;
	esac
    done
}

#
# 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 $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 Added
    else
	accounting_error
    fi
}

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

    echo "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

}

#
# Check the configuration
#
check_config() {

    disclaimer() {
	echo
	echo "Notice:  The 'check' command is provided to catch"
	echo "         obvious errors in a Shorewall configuration."
	echo "         It is not designed to catch all possible errors"
	echo "         so please don't submit problem reports about"
	echo "         error conditions that 'check' doesn't find"
	echo
    }

    report_capabilities

    echo "Verifying Configuration..."

    verify_os_version

    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

    echo "Determining Zones..."

    determine_zones

    display_list "IPv4_Zones:" $IPV4_ZONES
    [ -n "$IPSEC_ZONES" ] && \
        display_list "IPSEC Zones:" $IPSEC_ZONES
    display_list "Firewall Zone:" $FW

    setup_ipsec

    echo "Validating interfaces file..."

    validate_interfaces_file

    echo "Validating hosts file..."

    validate_hosts_file

    echo "Determining Hosts in Zones..."

    determine_interfaces
    determine_hosts

    echo "Validating policy file..."

    validate_policy

    setup_providers $(find_file providers)

    validate_blacklist

    echo "Validating Proxy ARP"
    strip_file proxyarp
    setup_proxy_arp

    echo "Validating NAT..."
    strip_file nat
    setup_nat

    echo "Pre-validating Actions..."

    process_actions1

    echo "Validating rules file..."

    rules=$(find_file rules)
    strip_file rules $rules
    process_rules

    echo "Validating Actions..."

    process_actions2
    process_actions3

    masq=$(find_file masq)
    [ -f $masq ] && setup_masq $masq

    setup_traffic_shaping

    rm -rf $TMP_DIR
    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE

    echo "Configuration Validated"

    disclaimer

}

#
# Refresh queuing and classes
#
refresh_tc() {

    echo "Refreshing Traffic Control Rules..."

    [ -n "$CLEAR_TC" ] && delete_tc1

    [ -n "$MARK_IN_FORWARD_CHAIN" ] && chain=tcfor || chain=tcpre

    if mangle_chain_exists $chain; then
        #
        # Flush the TC mangle chains
        #
	run_iptables -t mangle -F tcfor
	run_iptables -t mangle -F tcpre
        run_iptables -t mangle -F tcout
        run_iptables -t mangle -F tcpost
        #
        # Process the TC Rules File
        #
        strip_file tcrules

	while read mark sources dests proto ports sports user testval; do
	    expandv mark sources dests proto ports sports user testval
	    rule=$(echo "$mark $sources $dests $proto $ports $sports $user $testval")
	    process_tc_rule
        done < $TMP_DIR/tcrules
    else
	setup_tc1
    fi

    if [ -n "$TC_ENABLED" ]; then
	f=$(find_file tcstart)

	if [ -x $f ]; then
	    export CONFIG_PATH SHOREWALL_DIR
	    eval $f
	fi
    else
	setup_traffic_shaping
    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. If 'check', we're executing a 'check'
#                   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
#    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 [ $COMMAND != check ]; then
	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
    fi
}

#
# Process a record from an action file for the 'start', 'restart' or 'check' commands
#
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
    #
    if [ $COMMAND = check ]; then
	progress_message "   Rule \"$rule\" checked."
    else
	progress_message "   Rule \"$rule\" added."
    fi
}

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

    [ "$COMMAND" != check ] && \
	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))

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

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

	run_user_exit $1
    fi

    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
	    if [ $COMMAND != check ]; then
		LEVEL=
		TAG=
		createchain $CHAIN no
		run_user_exit $CHAIN
	    fi
	    ;;
    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#*:}
	    ;;
	*)
	    if [ $COMMAND != check ]; then
		havechain $action ||  fatal_error "Fatal error in find_logactionchain"
	    fi

	    echo $action
	    return
	    ;;
    esac

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

    set -- $chains

    while [ $# -gt 0 ]; do
	[ "$1" = "$level" ] && { echo $2 ; return ; }
	shift;shift
    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
	fi
    fi

    echo $1
}

#
# 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
		    echo "   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
	#
	# Handle Builtin actions
	#
	case $xaction1 in
	    dropBcast)
		if [ "$COMMAND" != check ]; then
		    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 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
		fi
		;;
	    allowBcast)
		if [ "$COMMAND" != check ]; then
		    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 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
		fi
		;;
	    dropNotSyn)
		if [ "$COMMAND" != check ]; then
		    [ -n "$xlevel" ] && \
			log_rule_limit ${xlevel%\!} $xchain dropNotSyn DROP "" "$xtag" -A -p tcp ! --syn
		    run_iptables -A $xchain -p tcp ! --syn -j DROP
		fi
		;;
	    rejNotSyn)
		if [ "$COMMAND" != check ]; then
		    [ -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
		fi
		;;
	    dropInvalid)
		if [ "$COMMAND" != check ]; then
		    [ -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
		fi
		;;
	    allowInvalid)
		if [ "$COMMAND" != check ]; then
		    [ -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
		fi
		;;
	    forwardUPnP)
		;;
	    allowinUPnP)
		if [ "$COMMAND" != check ]; then
		    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
		fi
		;;
	    allowoutUPnP)
		if [ "$COMMAND" != check ]; then
		    [ -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
		fi
		;;
	    *)
		#
		# Not a builtin
		#
		f=action.$xaction1

		echo "Processing $(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=${mclients}:${xclients}
					;;
				esac
			    else
				mclients=${xclients}
			    fi

			    if [ -n "$mservers" ]; then
				case $mservers in
				    -)
					mservers=${xservers}
					;;
				    *)
					mservers=${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 -- if 'check', we just go through
#                     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)
	    addr=
	    if [ -n "$DETECT_DNAT_IPADDRS" -a "$source" != "$FW" ]; then
		eval interfaces=\$${source}_interfaces
		for interface in $interfaces; do
		    addr=${addr:+$addr,}$(find_first_interface_address $interface)
		done
	    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 [ $COMMAND != check ]; then
	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

		for adr in $(separate_list $addr); do
		    addnatrule $(dnat_chain $source) $cli $proto $multiport $sports $dports $(dest_ip_range $adr) -j $chain
		done

		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)
		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 for the 'start', 'restart' or 'check' commands
#
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. If 'check', we're executing a 'check'
    #			  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
	    if [ $COMMAND != check ]; 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
				    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
				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
	    fi
	else

	    # Destination is a simple zone

	    if [ $COMMAND != check ]; then
		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
	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}

    [ $COMMAND = check ] || 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
    #
    if [ $COMMAND = check ]; then
	progress_message "   Rule \"$rule\" checked."
    else
	progress_message "   Rule \"$rule\" added."
    fi
}

#
# 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=${mclients}:${iclients}
		    ;;
	    esac
	else
	    mclients=${iclients}
	fi

	if [ -n "$mservers" ]; then
	    case $mservers in
		-)
		    mservers=${iservers}
		    ;;
		*)
		    mservers=${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 for the 'start', 'restart' or 'check' command.
#
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\" added."
}

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

    strip_file tos $1

    if [ -s $TMP_DIR/tos ] ; then
	run_iptables -t mangle -N pretos
	run_iptables -t mangle -N 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
}

#
#  echo the list of networks routed out of a given interface
#
get_routed_networks() # $1 = interface name
{
    local address
    local rest

    ip route show dev $1 2> /dev/null |
	while read address rest; do
	    if [ "x$address" = xdefault ]; then
		error_message "WARNING: default route ignored on interface $1"
	    else
		[ "$address" = "${address%/*}" ] && address="${address}/32"
		echo $address
	    fi
        done
}

#
# 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
    run_iptables -t mangle -N 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}"

	case $source in
	*.*.*|+*|!+*)
	    ;;
	*)
	    networks=$(get_routed_networks $networks)
	    [ -z "$networks" ] && fatal_error "Unable to determine the routes through interface \"$source\""
	    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)

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

		if [ $COMMAND != check ]; then
		    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=
		    else
			addnatrule $chain -j $newchain
		    fi
		else
		    networks=
		fi

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

		[ -n "$nomasq" ] && source="$source except $nomasq"
		;;
	    *)
		if [ -n "$nomasq" ]; then
		    if [ $COMMAND != check ]; 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
			else
			    for destnet in $(separate_list $destnets); do
				addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $newchain
			    done
			fi
		    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
		if [ $COMMAND != check ]; then
		    for destnet in $(separate_list $destnets); do
			addnatrule $chain $(both_ip_ranges $network $destnet) $proto $ports $policy -j $target $addrlist
		    done
		fi

		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
	else
	    if [ $COMMAND != check ]; then
		for destnet in $(separate_list $destnets); do
		    addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $target $addrlist
		done
	    fi

	    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
	echo "Masqueraded Networks and Hosts:"
	[ -n "$RETAIN_ALIASES" -o $COMMAND = check ] || save_progress_message "Restoring Masquerading/SNAT..."
    fi

    while read fullinterface networks addresses proto ports ipsec; do
	expandv fullinterface networks addresses proto ports ipsec
	[ -n "$NAT_ENABLED" ] && setup_one || \
	    error_message "WARNING: NAT disabled; masq rule ignored"
    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 [ "$COMMAND" != check ]; then
	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
    fi
}

#
# 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
	    ~*)
		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

	if [ "$COMMAND" = check ]; then
	    progress_message "   $addr" Verified
	else
	    progress_message "   $addr added to Black List"
	fi
    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
	echo "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
	echo "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

    echo "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
}

#
# Verify that kernel has netfilter support
#
verify_os_version() {

    osversion=$(uname -r)

    case $osversion in
    2.4.*|2.5.*|2.6.*)
	;;
    *)
	startup_error "Shorewall version $version does not work with kernel version $osversion"
	;;
    esac

    [ $COMMAND = start -a -n "$(lsmod 2> /dev/null | grep '^ipchains')" ] && \
	startup_error "Shorewall can't start with the ipchains kernel module loaded - see FAQ #8"
}

#
# Add IP Aliases
#
add_ip_aliases()
{
    local addresses external interface inet cidr rest val arping=$(mywhich arping)

    address_details()
    {
	#
	# Folks feel uneasy if they don't see all of the same
	# decoration on these IP addresses that they see when their
	# distro's net config tool adds them. In an attempt to reduce
	# the anxiety level, we have the following code which sets
	# the VLSM and BRD from an existing address in the same networks
	#
	# Get all of the lines that contain inet addresses with broadcast
	#
	ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | while read inet cidr rest ; do
	    case $cidr in
		*/*)
		    if in_network $external $cidr; then
			echo "/${cidr#*/} brd $(broadcastaddress $cidr)"
			break
		    fi
		    ;;
	    esac
	done
    }

    do_one()
    {
	val=$(address_details)

	if [ -n "$RETAIN_ALIASES" ]; then
	    run_ip addr add ${external}${val} dev $interface $label
	    save_command qt ip addr add ${external}${val} dev $interface $label
	else
	    ensure_and_save_command ip addr add ${external}${val} dev $interface $label
	fi

	[ -n "$arping" ] && run_and_save_command qt $arping -U -c 2 -I $interface $external

	echo "$external $interface" >> /var/lib/shorewall/nat
	[ -n "$label" ] && label="with $label"
	progress_message "   IP Address $external added to interface $interface $label"
    }

    set -- $ALIASES_TO_ADD

    save_progress_message "Restoring IP Addresses..."

    while [ $# -gt 0 ]; do
	external=$1
	interface=$2
	label=

	if [ "$interface" != "${interface%:*}" ]; then
	    label="${interface#*:}"
	    interface="${interface%:*}"
	    label="label $interface:$label"
	fi

	shift;shift

	list_search $external $(find_interface_addresses $interface) || do_one
    done
}

#
# Load kernel modules required for Shorewall
#
load_kernel_modules()
{
    save_modules_dir=$MODULESDIR

    [ -z "$MODULESDIR" ] && \
	MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv4/netfilter

    modules=$(find_file modules)

    if [ -f $modules -a -d $MODULESDIR ]; then
	progress_message "Loading Modules..."
	. $modules
    fi

    MODULESDIR=$save_modules_dir
}

save_load_kernel_modules()
{

    modules=$(find_file modules)

    save_progress_message "Loading kernel modules..."
    save_command "reload_kernel_modules <<__EOF__"

    while read command; do
	case "$command" in
	    loadmodule*)
		save_command $command
		;;
	esac
    done < $modules

    save_command __EOF__
    save_command ""

}

# Verify that the 'ip' program is installed

verify_ip() {
    qt ip link ls ||\
	startup_error "Shorewall $version requires the iproute package ('ip' utility)"
}

#
# 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 () {

    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"

    echo "Determining Zones..."

    determine_zones

    display_list "IPv4 Zones:" $IPV4_ZONES
    [ -n "$IPSEC_ZONES" ] && \
        display_list "IPSEC Zones:" $IPSEC_ZONES
    display_list "Firewall Zone:" $FW

    echo "Validating interfaces file..."

    validate_interfaces_file

    echo "Validating hosts file..."

    validate_hosts_file

    echo "Validating Policy file..."

    validate_policy

    echo "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

    echo "Pre-processing Actions..."
    process_actions1

    TERMINATOR=fatal_error

    deletechain shorewall

    [ -n "$NAT_ENABLED" ] && delete_nat

    delete_proxy_arp

    [ -n "$MANGLE_ENABLED" ] && \
	run_iptables -t mangle -F && \
	run_iptables -t mangle -X

    [ -n "$RAW_TABLE" ] && \
	run_iptables -t raw -F && \
	run_iptables -t raw -X

    [ -n "$CLEAR_TC" ] && delete_tc

    echo "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

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    else

	setpolicy INPUT DROP
	setpolicy OUTPUT DROP
	setpolicy FORWARD DROP

	deleteallchains

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    fi

    f=$(find_file ipsets)

    if [ -f $f ]; then
	echo "Processing $f ..."
	ipset -U :all: :all:
	run_ipset -F
	run_ipset -X
	run_ipset -R < $f
    fi

    run_user_exit continue

    f=$(find_file routestopped)

    echo "Processing $f ..."

    strip_file routestopped $f

    process_routestopped -A

    [ -n "$DISABLE_IPV6" ] && disable_ipv6

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

    if [ -n "$CLAMPMSS" ]; then
	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
    fi

    accounting_file=$(find_file accounting)

    [ -f $accounting_file ] && setup_accounting $accounting_file

    createchain reject	no
    createchain dynamic no
    createchain smurfs no

    if [ -f /var/lib/shorewall/save ]; then
	echo "Restoring dynamic rules..."

	if [ -f /var/lib/shorewall/save ]; then
	    while read target ignore1 ignore2 address rest; do
		case $target in
		    DROP|reject)
			run_iptables -A dynamic -s $address -j $target
			;;
		    *)
			;;
		esac
	    done < /var/lib/shorewall/save
	fi
    fi

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

    echo "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 address in $broadcasts ; do
	    run_iptables -A reject -d $address -j DROP
	done
    }

    #
    # Populate the smurf chain
    #
    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
    done
    #
    # Reject Rules -- Don't respond to broadcasts with an ICMP
    #
    if [ -n "$USEPKTTYPE" ]; then
	qt $IPTABLES -A reject -m pkttype --pkt-type broadcast -j DROP
	if ! qt $IPTABLES -A reject -m pkttype --pkt-type multicast -j DROP; then
            #
	    # No pkttype support -- do it the hard way
	    #
	    drop_broadcasts
	fi
    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
    #
    qt $IPTABLES  -A reject -p icmp -j REJECT --reject-with icmp-host-unreachable
    if ! qt $IPTABLES -A reject -j REJECT --reject-with icmp-host-prohibited; then
	#
	# In case the above doesn't work
	#
	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
    #
    setup_blacklist

    #
    # SMURFS
    #
    hosts=$(find_hosts_by_option nosmurfs)

    if [ -n "$hosts" ]; then

	echo "Adding Anti-smurf Rules"

	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

	echo "Adding 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" ] && \
		    $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
	echo "Enabling 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
	    #
	    run_iptables -t mangle -N man1918
	    run_iptables -t mangle -N 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
	echo "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 "Restoring ARP filtering..."

    for f in /proc/sys/net/ipv4/conf/*; do
	run_and_save_command "[ -f $f/arp_filter ] && echo 0 > $f/arp_filter"
	run_and_save_command "[ -f $f/arp_filter ] && echo 0 > $f/arp_ignore"
    done

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

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

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

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

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

	save_progress_message "Restoring Route Filtering..."

	for f in /proc/sys/net/ipv4/conf/*; do
	    run_and_save_command "[ -f $f/rp_filter ]    && echo 0 > $f/rp_filter"
	done

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

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

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

	run_and_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
	echo "Setting up Martian Logging..."

	save_progress_message "Restoring Martian Logging..."

	for f in /proc/sys/net/ipv4/conf/*; do
	    run_and_save_command "[ -f $f/log_martians ]    && echo 0 > $f/log_martians"
	done

	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/log_martians
	    if [ -f $file ]; then
		run_and_save_command "echo 1 > $file"
	    else
		error_message \
		    "WARNING: Cannot set Martian logging on $interface"
	    fi
	done

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

    fi

    #
    # Source Routing
    #
    save_progress_message "Restoring Accept Source Routing..."

    for f in /proc/sys/net/ipv4/conf/*; do
	run_and_save_command "[ -f $f/accept_source_route ] && echo 0 > $f/accept_source_route"
    done

    interfaces=$(find_interfaces_by_option sourceroute)

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

	for interface in $interfaces; do
	    file=/proc/sys/net/ipv4/conf/$interface/accept_source_route
	    if [ -f $file ]; then
		run_and_save_command "echo 1 > $file"
	    else
		error_message \
		    "WARNING: Cannot set Accept Source Routing on $interface"
	    fi
	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
	echo "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 ! 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

    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

    > /var/lib/shorewall/chains
    echo "$FW firewall" > /var/lib/shorewall/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 >> /var/lib/shorewall/zones

	if [ -n "$DYNAMIC_ZONES" ]; then
	    echo "$FW $zone $chain1" >> /var/lib/shorewall/chains
	    echo "$zone $FW $chain2" >> /var/lib/shorewall/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" >> /var/lib/shorewall/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
	#
        # Bridges under the 2.4 kernel have the wierd property that REJECTS have the physdev-in and physdev-out set to the input physdev.
        # To accomodate this feature/bug, we effectively set 'routeback' on bridge ports.
	#
	eval ports=\$$(chain_base $interface)_ports
	for port in $ports; do
	    run_iptables -A $(forward_chain $interface) -o $interface -m physdev --physdev-in $port --physdev-out $port -j ACCEPT
	done
    done

    chain=${FW}2${FW}

    if havechain $chain; then
	#
	# There is a fw->fw chain. Send loopback output through that chain
	#
	run_ip link ls | grep LOOPBACK | while read ordinal interface rest ; do
	    run_iptables -A OUTPUT -o ${interface%:*} -j $chain
	done
	#
	# 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
}

#
# Check for disabled startup
#
check_disabled_startup() {
    if [ -z "$STARTUP_ENABLED" ]; then
	echo "   Shorewall Startup is disabled -- to enable startup"
	echo "   after you have completed Shorewall configuration,"
	echo "   change the setting of STARTUP_ENABLED to Yes in"
        echo "   /etc/shorewall/shorewall.conf"

	[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	my_mutex_off
	exit 2
    fi
}

#
# Start/Restart the Firewall
#
define_firewall() # $1 = Command (Start or Restart)
{
    check_disabled_startup

    echo "${1}ing Shorewall..."

    set_state "${1}ing"

    verify_os_version
    verify_ip

    [ -d /var/lib/shorewall ] || { mkdir -p /var/lib/shorewall ; chmod 700 /var/lib/shorewall; }

    RESTOREBASE=$(mktempfile /var/lib/shorewall)

    [ -n "$RESTOREBASE" ] || startup_error "Cannot create temporary file in /var/lib/shorewall"

    echo '#bin/sh' >> $RESTOREBASE
    save_command "#"
    save_command "# Restore base file generated by Shorewall $version - $(date)"
    save_command "#"
    save_command ". /usr/share/shorewall/functions"

    f=$(find_file params)

    [ -f $f ] && \
	save_command ". $(resolve_file $f)"

    save_command "#"
    save_command "COMMAND=restore"
    save_command "MODULESDIR=\"$MODULESDIR\""
    save_command "MODULE_SUFFIX=\"$MODULE_SUFFIX\""

    save_load_kernel_modules

    echo "Initializing...";                    initialize_netfilter

    echo "Configuring Proxy ARP";              setup_proxy_arp
    #
    # [re]-Establish routing
    #
    setup_providers $(find_file providers)
    [ -n "$ROUTEMARK_INTERFACES" ] &&          setup_routes


    echo "Setting up NAT...";                  setup_nat
    echo "Setting up NETMAP...";               setup_netmap
    echo "Adding Common Rules";                add_common_rules

    setup_syn_flood_chains

    setup_ipsec

    maclist_hosts=$(find_hosts_by_option maclist)
    [ -n "$maclist_hosts" ] &&                 setup_mac_lists

    echo "Processing $(find_file rules)...";   process_rules

    tunnels=$(find_file tunnels)
    [ -f $tunnels ] && \
	echo "Processing $tunnels..." &&       setup_tunnels $tunnels

    echo "Processing Actions...";              process_actions2
                                               process_actions3
    echo "Processing $(find_file policy)...";  apply_policy_rules

    masq=$(find_file masq)
    [ -f $masq ] &&                            setup_masq $masq

    tos=$(find_file tos)
    [ -f $tos ] && [ -n "$MANGLE_ENABLED" ] && process_tos $tos

    ecn=$(find_file ecn)
    [ -f $ecn ] && [ -n "$MANGLE_ENABLED" ] && setup_ecn $ecn

    [ -n "$MANGLE_ENABLED" ]                && setup_tc

    echo "Activating Rules...";                activate_rules

    [ -n "$ALIASES_TO_ADD" ] && \
	echo "Adding IP Addresses..." &&       add_ip_aliases

    for file in chains nat proxyarp zones; do
	append_file $file
    done

    save_progress_message "Restoring Netfilter Configuration..."

    save_command 'iptables-restore << __EOF__'

    # 'shorewall save' appends the iptables-save output and '__EOF__'

    mv -f $RESTOREBASE /var/lib/shorewall/restore-base-$$

    > $RESTOREBASE

    save_command "#"
    save_command "# Restore tail file generated by Shorewall $version - $(date)"
    save_command "#"
    save_command "date > /var/lib/shorewall/restarted"

    run_user_exit start

    [ -n "$DELAYBLACKLISTLOAD" ] && refresh_blacklist

    createchain shorewall no

    date > /var/lib/shorewall/restarted

    run_and_save_command set_state "Started"

    report "Shorewall ${1}ed"

    run_user_exit started

    rm -rf $TMP_DIR

    mv -f /var/lib/shorewall/restore-base-$$ /var/lib/shorewall/restore-base
    mv -f $RESTOREBASE /var/lib/shorewall/restore-tail
}

#
# Refresh the firewall
#
refresh_firewall()
{
    echo "Refreshing Shorewall..."

    echo "Determining Zones and Interfaces..."

    determine_zones

    validate_interfaces_file

    determine_interfaces

    run_user_exit refresh

    #
    # Blacklist
    #
    refresh_blacklist

    ecn=$(find_file ecn)

    [ -f $ecn ] && [ -n "$MANGLE_ENABLED" ] && setup_ecn $ecn
    #
    # Refresh Traffic Control
    #
    [ -n "$MANGLE_ENABLED" ] && refresh_tc

    report "Shorewall Refreshed"

    rm -rf $TMP_DIR
}

#
# Add a host or networks to a zone
#
add_to_zone() # $1...${n-1} = <interface>[:<hosts>] $n = zone
{
    local interface host zone z h z1 z2 chain
    local dhcp_interfaces blacklist_interfaces maclist_interfaces
    local tcpflags_interfaces newhostlist=
    local rulenum source_chain dest_hosts iface hosts hostlist=

    nat_chain_exists() # $1 = chain name
    {
	qt $IPTABLES -t nat -L $1 -n
    }

    do_iptables() # $@ = command
    {
	[ -n "$BRIDGING" ]      && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange

	if ! $IPTABLES $@ ; then
	    error_message "Can't add $newhost to zone $zone"
	fi
    }

    #
    # Load $zones
    #
    determine_zones
    #
    # Validate Interfaces File
    #
    validate_interfaces_file
    #
    # Validate Hosts File
    #
    validate_hosts_file
    #
    # Validate IPSec File
    #
    f=$(find_file ipsec)

    [ -f $f ] && setup_ipsec $f
    #
    # Normalize host list
    #
    while [ $# -gt 1 ]; do
	interface=${1%%:*}
	host=${1#*:}
        #
        # Be sure that the interface was dynamic at last [re]start
        #
	if ! chain_exists $(input_chain $interface) ; then
	    startup_error "Unknown interface $interface"
	fi

	if ! chain_exists $(dynamic_in $interface) ; then
	    startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf"
	fi

	if [ -z "$host" ]; then
	    hostlist="$hostlist $interface:0.0.0.0/0"
	else
	    for h in $(separate_list $host); do
		hostlist="$hostlist $interface:$h"
	    done
	fi

	shift
    done
    #
    # Validate Zone
    #
    zone=$1

    validate_zone $zone || startup_error "Unknown zone: $zone"

    [ "$zone" = $FW ] && startup_error "Can't add $1 to firewall zone"

    #
    # Be sure that Shorewall has been restarted using a DZ-aware version of the code
    #
    [ -f /var/lib/shorewall/chains ] || startup_error "/var/lib/shorewall/chains -- file not found"
    [ -f /var/lib/shorewall/zones ]  || startup_error "/var/lib/shorewall/zones -- file not found"
    #
    # Check for duplicates and create a new zone state file
    #
    > /var/lib/shorewall/zones_$$

    while read z type hosts; do
	if [ "$z" = "$zone" ]; then
	    for h in $hostlist; do
		list_search $h $hosts
		if [ "$?" -gt 0 ]; then
		    newhostlist="$newhostlist $h"
		else
		    error_message "$h already in zone $zone"
		fi
	    done

	    [ -z "$hosts" ] && hosts=$newhostlist || hosts="$hosts $newhostlist"
	fi

	eval ${z}_hosts=\"$hosts\"

	echo "$z $type $hosts" >> /var/lib/shorewall/zones_$$
    done < /var/lib/shorewall/zones

    mv -f /var/lib/shorewall/zones_$$ /var/lib/shorewall/zones

    TERMINATOR=fatal_error
    #
    # Create a new Zone state file
    #
    for newhost in $newhostlist; do
        #
        # Isolate interface and host parts
        #
	interface=${newhost%%:*}
	host=${newhost#*:}
        #
        # If the zone passed in the command has a dnat chain then insert a rule in
        # the nat table PREROUTING chain to jump to that chain when the source
        # matches the new host(s)#
        #
	chain=${zone}_dnat

	if nat_chain_exists $chain; then
	    do_iptables -t nat -A $(dynamic_in $interface) $(source_ip_range $host) $(match_ipsec_in $zone $newhost) -j $chain
	fi
        #
        # Insert new rules into the filter table for the passed interface
        #
	while read z1 z2 chain; do
	    [ "$z1" = "$z2" ] && op="-I" || op="-A"
	    if [ "$z1" = "$zone" ]; then
		if [ "$z2" = "$FW" ]; then
		    do_iptables $op $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j $chain
		else
		    source_chain=$(dynamic_fwd $interface)
		    if is_ipsec_host $z1 $newhost ; then
			do_iptables $op $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd
		    else
			eval dest_hosts=\"\$${z2}_hosts\"

			for h in $dest_hosts; do
			    iface=${h%%:*}
			    hosts=${h#*:}

			    if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				do_iptables $op $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain
			    fi
			done
		    fi
		fi
	    elif [ "$z2" = "$zone" ]; then
		if [ "$z1" = "$FW" ]; then
		    #
		    # Add a rule to the dynamic out chain for the interface
		    #
		    do_iptables $op $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
		else
		    eval source_hosts=\"\$${z1}_hosts\"

		    for h in $source_hosts; do
			iface=${h%%:*}
			hosts=${h#*:}

			if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
			    if is_ipsec_host $z1 $h; then
				do_iptables $op ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
			    else
				do_iptables $op $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
			    fi
			fi
		    done
		fi
	    fi
	done < /var/lib/shorewall/chains

	progress_message "$newhost added to zone $zone"

    done

    rm -rf $TMP_DIR
}

#
# Delete a host or networks from a zone
#
delete_from_zone() # $1 = <interface>[:<hosts>] $2 = zone
{
    local interface host zone z h z1 z2 chain delhost
    local dhcp_interfaces blacklist_interfaces maclist_interfaces tcpflags_interfaces
    local rulenum source_chain dest_hosts iface hosts hostlist=

    #
    # Load $zones
    #
    determine_zones
    #
    # Validate Interfaces File
    #
    validate_interfaces_file
    #
    # Validate Hosts File
    #
    validate_hosts_file
    #
    # Validate IPSec File
    #
    f=$(find_file ipsec)

    [ -f $f ] && setup_ipsec $f

    #
    # Normalize host list
    #
    while [ $# -gt 1 ]; do
	interface=${1%%:*}
	host=${1#*:}
        #
        # Be sure that the interface was dynamic at last [re]start
        #
	if ! chain_exists $(input_chain $interface) ; then
	    startup_error "Unknown interface $interface"
	fi

	if ! chain_exists $(dynamic_in $interface) ; then
	    startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf"
	fi

	if [ -z "$host" ]; then
	    hostlist="$hostlist $interface:0.0.0.0/0"
	else
	    for h in $(separate_list $host); do
		hostlist="$hostlist $interface:$h"
	    done
	fi

	shift
    done
    #
    # Validate Zone
    #
    zone=$1

    validate_zone $zone || startup_error "Unknown zone: $zone"

    [ "$zone" = $FW ] && startup_error "Can't delete from the firewall zone"

    #
    # Be sure that Shorewall has been restarted using a DZ-aware version of the code
    #
    [ -f /var/lib/shorewall/chains ] || startup_error "/var/lib/shorewall/chains -- file not found"
    [ -f /var/lib/shorewall/zones ]  || startup_error "/var/lib/shorewall/zones -- file not found"
    #
    # Delete the passed hosts from the zone state file
    #
    > /var/lib/shorewall/zones_$$

    while read z hosts; do
	if [ "$z" = "$zone" ]; then
	    temp=$hosts
	    hosts=

	    for host in $hostlist; do
		found=
		for h in $temp; do
		    if [ "$h" = "$host" ]; then
			found=Yes
			break
		    fi
		done

		[ -n "$found" ] || error_message "Warning: $host does not appear to be in zone $zone"
	    done

	    for h in $temp; do
		found=
		for host in $hostlist; do
		    if [ "$h" = "$host" ]; then
			found=Yes
			break
		    fi
		done

		[ -n "$found" ] || hosts="$hosts $h"
	    done
	fi

	eval ${z}_hosts=\"$hosts\"

	echo "$z $hosts" >> /var/lib/shorewall/zones_$$
    done < /var/lib/shorewall/zones

    mv -f /var/lib/shorewall/zones_$$ /var/lib/shorewall/zones

    TERMINATOR=fatal_error

    for delhost in $hostlist; do
	interface=${delhost%%:*}
	host=${delhost#*:}
        #
        # Delete any nat table entries for the host(s)
        #
	qt_iptables -t nat -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $zone $delhost) -j ${zone}_dnat
        #
        # Delete rules rules the input chains for the passed interface
        #
	while read z1 z2 chain; do
	    if [ "$z1" = "$zone" ]; then
		if [ "$z2" = "$FW" ]; then
		    qt_iptables -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $delhost) -j $chain
		else
		    source_chain=$(dynamic_fwd $interface)
		    if is_ipsec_host $z1 $delhost ; then
			qt_iptables -D $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd
		    else
			eval dest_hosts=\"\$${z2}_hosts\"

			[ "$z2" = "$zone" ] && dest_hosts="$dest_hosts $hostlist"

			for h in $dest_hosts; do
			    iface=${h%%:*}
			    hosts=${h#*:}

			    if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				qt_iptables -D $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain
			    fi
			done
		    fi
		fi
	    elif [ "$z2" = "$zone" ]; then
		if [ "$z1" = "$FW" ]; then
		    qt_iptables -D $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain
		else
		    eval source_hosts=\"\$${z1}_hosts\"

		    for h in $source_hosts; do
			iface=${h%%:*}
			hosts=${h#*:}

			if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				if is_ipsec_host $z1 $h; then
				    qt_iptables -D ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain
				else
				    qt_iptables -D $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host)  $(match_ipsec_out $z2 $delhost) -j $chain
				fi
			fi
		    done
		fi
	    fi
	done < /var/lib/shorewall/chains

	progress_message "$delhost removed from zone $zone"

    done

    rm -rf $TMP_DIR
}

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

    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=

    FUNCTIONS=$SHARED_DIR/functions

    if [ -f $FUNCTIONS ]; then
	[ -n "$QUIET" ] || 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 "rm -rf $TMP_DIR; my_mutex_off; 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
	    [ -n "$QUIET" ] || echo "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.
    #

    [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ]
    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

    PKTTYPE=$(added_param_value_no PKTTYPE $PKTTYPE)

    determine_capabilities

    [ -d /var/lib/shorewall ] || mkdir -p /var/lib/shorewall

    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)"
    TC_ENABLED="$(added_param_value_yes TC_ENABLED $TC_ENABLED)"

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

    [ "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] {start|stop|reset|restart|refresh|clear|{add|delete} <interface>[:hosts] zone}}"
    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 "my_mutex_off; exit 2" 1 2 3 4 5 6 9

COMMAND="$1"

case "$COMMAND" in
    stop)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	#
	# Don't want to do a 'stop' when startup is disabled
	#
	check_disabled_startup
	echo -n "Stopping Shorewall..."
	stop_firewall
	[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
	echo "done."
	my_mutex_off
	;;

    start)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	if shorewall_is_started ; then
	    [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
	    echo "Shorewall Already Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 0;
	fi
	define_firewall "Start" && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
	my_mutex_off
	;;

    restart)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	if shorewall_is_started; then
	    define_firewall "Restart"
	else
	    echo "Shorewall Not Currently Running"
	    define_firewall "Start"
	fi

	[ $? -eq 0 ] && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
	my_mutex_off
	;;

    reset)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	$IPTABLES -Z
	$IPTABLES -t nat -Z
	$IPTABLES -t mangle -Z
	report "Shorewall Counters Reset"
	date > /var/lib/shorewall/restarted
	my_mutex_off
	;;

    refresh)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	refresh_firewall;
	my_mutex_off
	;;

    clear)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	echo -n "Clearing Shorewall..."
	clear_firewall
	[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
	echo "done."
	my_mutex_off
	;;

    check)
	[ $# -ne 1 ] && usage
	do_initialize
	check_config
	;;

    add)
	[ $# -lt 3 ] && usage
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	shift
	add_to_zone $@
	my_mutex_off
	;;

    delete)
	[ $# -lt 3 ] && usage
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	shift
	delete_from_zone $@
	my_mutex_off
	;;

    call)
	#
	# Undocumented way to call functions in /usr/share/shorewall/firewall directly
	#
	shift;
	do_initialize
	EMPTY=
	$@
	;;

    *)
	usage
	;;

esac