#!/bin/sh
#
#     The Shoreline Firewall (Shorewall) Packet Filtering Firewall Compiler - V4.0
#
#     This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
#
#     (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net)
#
#	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.,
#	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
#	If an error occurs while starting or restarting the firewall, the
#	firewall is automatically stopped.
#
#	Commands are:
#
#          compile check                        Verify the configuration files.
#	   compile compile <path name>          Compile into <path name>
#
#	Environmental Variables:
#
#	    EXPORT=Yes                          -e option specified to /sbin/shorewall
#	    SHOREWALL_DIR                       A directory name was passed to /sbin/shorewall
#	    VERBOSE                             Standard Shorewall verbosity control.

BASE_VERSION=40000
BASE_VERSION_PRINTABLE=4.0.0
CONFIG_VERSION=40000
CONFIG_VERSION_PRINTABLE=4.0.0

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

#
# We include this for compatibility with the 'firewall' script. That script
# distinguishes between Fatal Errors (stop or restore required) and Startup
# Errors (errors detected before the firewall state has been changed. This
# allows us to use common parsing routines in both programs.
#
startup_error()
{
    echo "   ERROR: $@" >&2
    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    [ -n "$OUTPUT" ] && rm -f $OUTPUT
    kill $$
    exit 2
}

#
# Write the passed args to the compiler output file.
#
save_command()
{
    [ $# -gt 0 ] && echo "${INDENT}${@}" >&3 || echo >&3
}

save_command_unindented()
{
    echo "${@}" >&3
}

#
# Write a progress_message2 command to the output file.
#
save_progress_message()
{
    echo                                     >&3
    echo "${INDENT}progress_message2 \"$@\"" >&3
    echo                                     >&3
}

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

progress_message_and_save()
{
    progress_message "$@"
    echo "${INDENT}progress_message \"$@\"" >&3
}

#
# Echo the contents of the passed file indented by $INDENT
#
indent() {
    if [ -n "$INDENT" ]; then
	eval sed \'s\/^/"$INDENT"\/\' $1
    else
	cat $1
    fi
}

#
# Echo the contents of the passed file indented by $INDENT while handling line
# continuation
#
indent1() {
    if [ -n "$INDENT" ]; then
	if [ -n "$HAVEAWK" ]; then
	    eval awk \''BEGIN { indent=1; }; /^[[:space:]]*$/ { print ""; indent=1; next; }; { if (indent == 1) print "'"$INDENT"'" $0; else print; }; { indent=1; }; /\\$/ { indent=0; };'\' $1
	else
	    eval sed \'s\/^/"$INDENT"\/\' $1
	fi
    else
	cat $1
    fi
}

#
# Append a file to the compiler's output with indentation.
#
append_file() # $1 = File Name
{
    local user_exit
    user_exit=$(find_file $1)

    case $user_exit in
	$SHAREDIR/*)
	    #
	    # Don't copy files from /usr/share/shorewall into the compiled script
	    #
	    ;;
	*)
	    if [ -f $user_exit ]; then
		save_progress_message "Processing $user_exit ..."
		indent1 $user_exit >&3
		save_command
	    fi
	    ;;
    esac
}

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

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

    save_command "$IPTABLES_COMMAND $@"
}

#
# Version of 'run_iptables' that inserts white space after "!" in the arg list
#
run_iptables2() {
    if [ -z "$KLUDGEFREE" ]; then
	#
	# Purge the temporary files that we use to prevent duplicate '-m' specifications
	#
	[ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange
    fi

    save_command run_iptables $(fix_bang $@)
}

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

    save_command qt \$IPTABLES $@
}

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

#
# Add the implicit ACCEPT rules at the end of a rules file section
#
finish_chain_section() # $1 = canonical chain $2 = state list
{
    local policy
    local 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
    local zone1
    local chain

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

#
# Create a filter chain
#
# If the chain isn't one of the common chains then add a rule to the chain
# allowing packets that are part of an established connection. Create a
# variable exists_${1} and set its value to Yes to indicate that the chain now
# exists.
#
createchain() # $1 = chain name, $2 = If "yes", do section-end processing
{
    local c
    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
}

#
# This version creates the chain if it doesn't already exist
#
createchain2() # $1 = chain name, $2 = If "yes", create default rules
{
    local c
    c=$(chain_base $1)

    ensurechain $1

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

    eval exists_${c}=Yes
}

#
# Determine if a chain exists
#
# When we create a chain "x", we create a variable named exists_x and
# set its value to Yes. This function tests for the "exists_" variable
# corresponding to the passed chain having the value of "Yes".
#
havechain() # $1 = name of chain
{
    local c
    c=$(chain_base $1)

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

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

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

#
# Add a rule to a chain creating the chain if necessary
#
addrule() # $1 = chain name, remainder of arguments specify the rule
{
    ensurechain $1
    run_iptables -A $@
}

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

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

    eval exists_mangle_${1}=Yes
}

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

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

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

    eval exists_nat_${1}=Yes
}

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

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

#
# Add a rule to a nat chain creating the chain if necessary

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

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

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

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

    for var in DROP_DEFAULT REJECT_DEFAULT ACCEPT_DEFAULT QUEUE_DEFAULT; do
	eval default=\$$var

	case $default in
	    none)
		;;
	    *)
		if ! list_search $default $USEDACTIONS; then
		    if ! list_search $default $DEFAULT_MACROS; then
			if [ ! -f $(find_file macro.$default) ]; then
			    fatal_error "Default Action/Macro $var=$default not found"
			fi
			DEFAULT_MACROS="$DEFAULT_MACROS $default"
		    fi
		fi
	esac
    done

    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"

	if [ -n "$IMPLICIT_CONTINUE" ]; then
	    eval parents=\$${zone}_parents
	    if [ -n "$parents" ]; then
		for zone1 in $ZONES $FW; do 
		    if [ $zone != $zone1 ]; then
			chain=${zone}2${zone1}
			eval ${chain}_is_policy=Yes
			eval ${chain}_is_optional=Yes
			eval ${chain}_policy=CONTINUE
			eval ${chain}_policychain=$chain
			ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain"
			chain=${zone1}2${zone}
			eval ${chain}_is_policy=Yes
			eval ${chain}_is_optional=Yes
			eval ${chain}_policy=CONTINUE
			eval ${chain}_policychain=$chain
			ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain"
		    fi
		done
	    fi
	fi
    done

    while read client server policy loglevel synparams; do
	clientwild=
	serverwild=

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

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

	default=

	case $policy in
	    *:None|*:none)
		default=none
		;;
	    *:*)
		default=${policy#*:}
		if list_search $default $ACTIONS; then
		    if ! list_search $default $USEDACTIONS; then
			USEDACTIONS="$USEDACTIONS $default"
		    fi
		elif ! list_search $default $DEFAULT_MACROS; then
		    [ -f $(find_file macro.${default}) ] || fatal_error "$client $server $policy $loglevel $synparams: Default Macro $default not found"
		    DEFAULT_MACROS="$DEFAULT_MACROS $default"
		fi
		;;
	    *)
		;;
	esac

	case ${policy%:*} in
	    DROP)
		[ -n "${default:=$DROP_DEFAULT}" ]
		;;
	    REJECT)
		[ -n "${default:=$REJECT_DEFAULT}" ]
		;;
	    ACCEPT)
		[ -n "${default:=$ACCEPT_DEFAULT}" ]
		;;
	    CONTINUE)
		;;
	    QUEUE)
		[ -n "${default:=$QUEUE_DEFAULT}" ]
		;;
	    NONE)
		[ "$client" = "$FW" -o "$server" = "$FW" ] && \
		    fatal_error " $client $server $policy $loglevel $synparams: NONE policy not allowed to/from the $FW zone"

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

	chain=${client}2${server}

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

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

	policy=${policy%:*}

	[ $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
	eval ${chain}_default=$default

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

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

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

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

    done < $TMP_DIR/policy
}

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

#
# Find interfaces with BROADCAST=detect
#
find_bcastdetect_interfaces() {
    for interface in $ALL_INTERFACES; do
	eval bcast=\$$(chain_base $interface)_broadcast
	[ "x$bcast" = "xdetect" ] && echo $interface
    done
}

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

    progress_message2 "Compiling IP Forwarding..."

    case "$IP_FORWARDING" in
	On|on|ON|Yes|yes|YES)
	    save_progress_message "IP Forwarding Enabled"
	    save_command "echo 1 > /proc/sys/net/ipv4/ip_forward"
	    save_command ""
	    ;;
	Off|off|OFF|No|no|NO)
	    save_progress_message "IP Forwarding Disabled!"
	    save_command "echo 0 > /proc/sys/net/ipv4/ip_forward"
	    save_command ""
	    ;;
    esac
}

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

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

#
# Logging Rules
#
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
    level=$1
    local chain
    chain=$2
    local displayChain
    displayChain=$3
    local disposition
    disposition=$4
    local rulenum
    rulenum=
    local limit
    limit=
    local tag
    tag=$6
    local command
    command=${7:--A}
    local prefix
    local base
    base=$(chain_base $displayChain)

    limit="${5:-$LOGLIMIT}" # Do this here rather than in the declaration above to appease /bin/ash.

    shift 7

    save_command "do_log_rule_limit \"$level\" \"$chain\" \"$displayChain\" \"$disposition\" \"$limit\" \"$tag\" \"$command\" $(fix_bang $@)"
}

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

    shift 3

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

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

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

    if ! havechain $chain ; then
	createchain $chain no
	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
    fi
}

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() {
    indent >&3 << __EOF__
if [ -s \${VARDIR}/proxyarp ]; then
    while read address interface external haveroute; do
        qt arp -i \$external -d \$address pub
        [ -z "\$haveroute" -a -z "\$NOROUTE" ] && qt ip route del \$address dev \$interface
    done < \${VARDIR}/proxyarp

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

rm -f \${VARDIR}/proxyarp

__EOF__

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

 }

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

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

    indent >&3 << __EOF__

if [ -f \${VARDIR}/nat ]; then
   while read external interface; do
       del_ip_addr \$external \$interface
    done < \${VARDIR}/nat

    rm -f \${VARDIR}/nat
fi

__EOF__
}

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

    if [ -s ${TMP_DIR}/ecn ]; then
	save_progress_message "Setting up ECN..."

	progress_message2 "$DOING $1..."

	while read interface host; do
	    list_search  $interface $ALL_INTERFACES || \
		fatal_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 "$DOING ECN control on${interfaces}..."

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

	    for host in $hosts; do
		interface=${host%:*}
		h=${host#*:}
		run_iptables -t mangle -A $(ecn_chain $interface) -p tcp $(dest_ip_range $h) -j ECN --ecn-tcp-remove
		    progress_message_and_save "   ECN Disabled to $h through $interface"
	    done
	fi
    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
    c=excl_${EXCLUSION_SEQ}
    local 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
}

#
# Setup queuing and classes
#
setup_tc1() {
    local mark_part
    mark_part=
    local comment
    comment=
    #
    # Create the TC mangle chains
    #

    createmanglechain tcpre

    if [ -n "$MANGLE_FORWARD" ]; then
	createmanglechain tcfor
	createmanglechain tcpost
    fi

    createmanglechain tcout
    #
    # Process the TC Rules File
    #
    if [ -s $TMP_DIR/tcrules ]; then
	save_progress_message "Setting up TC Rules..."
	save_command setup_tc_rules
	save_command

    fi
    #
    # Just in case the file ended with a comment
    #
    if [ -n "$COMMENTS" ]; then
	save_command
	save_command COMMENT=
	save_command
    fi
    #
    # Link to the TC mangle chains from the main chains
    #

    #
    # 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.
    #
    if [ -n "$ROUTEMARK_INTERFACES" -a -z "$TC_EXPERT" ]; then
	[ -n "$HIGH_ROUTE_MARKS" ] && mark_part="-m mark --mark 0/0xFF00" || mark_part="-m mark --mark 0/0xFF"
	#
	# But let marks in tcpre override those assigned by 'track'
	#
	for interface in $ROUTEMARK_INTERFACES; do
	    run_iptables -t mangle -A PREROUTING -i $interface -j tcpre
	done
    fi

    run_iptables -t mangle -A PREROUTING $mark_part -j tcpre
    run_iptables -t mangle -A OUTPUT     $mark_part -j tcout

    if [ -n "$MANGLE_FORWARD" ]; then
	run_iptables -t mangle -A FORWARD -j tcfor
	run_iptables -t mangle -A POSTROUTING -j tcpost
    fi

    if [ -n "$HIGH_ROUTE_MARKS" ]; then
	for chain in INPUT FORWARD POSTROUTING; do
	    run_iptables -t mangle -I $chain -j MARK --and-mark 0xFF
	done
    fi

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

setup_tc() {

    progress_message2 "$DOING Traffic Control Rules..."

    setup_tc1
}

#
# Clear Traffic Shaping
#
delete_tc()
{
    save_progress_message "Clearing Traffic Control/QOS"

    append_file tcclear

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

#
# Refresh queuing and classes
#
refresh_tc() {

    local comment
    comment=

    if [ -n "$CLEAR_TC" ]; then
	delete_tc
	save_command
    fi

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

    #
    # Flush the TC mangle chains
    #

    if [ -n "$MANGLE_FORWARD" ]; then
	run_iptables -t mangle -F tcfor
	run_iptables -t mangle -F tcpost
    fi

    run_iptables -t mangle -F tcpre
    run_iptables -t mangle -F tcout
    #
    # Remove all exclusion chains from the mangle table
    #
    indent >&3 << __EOF__

\$IPTABLES -t mangle -L -n | grep '^Chain excl_' | while read junk chain rest; do
    run_iptables -t mangle -F \$chain
    run_iptables -t mangle -X \$chain
done

__EOF__
    #
    # Process the TC Rules File
    #
    if [ -s $TMP_DIR/tcrules ]; then
	save_progress_message "Refreshing Traffic Control Rules..."

	save_command setup_tc_rules
	save_command
    fi
    #
    # Just in case the file ended with a comment
    #
    if [ -n "$COMMENTS" ]; then
	save_command
	save_command COMMENT=
	save_command
    fi

    if [ -n "$TC_SCRIPT" ]; then
	save_progress_message "Refreshing Traffic Shaping"
	run_user_exit $TC_SCRIPT
    elif [ "$TC_ENABLED" = Internal -a -n "$LIB_tc_LOADED" ]; then
	save_command
	save_command setup_traffic_shaping
	save_command
    fi
}

#
# Compile refresh of the firewall
#
compile_refresh_firewall()
{
    local INDENT
    INDENT=""
    local DOING
    DOING="Compiling Refresh of"
    local DONE
    DONE="Compiled"
    local indent

    save_command "refresh_firewall()"
    save_command "{"
    INDENT="    "

    append_file refresh

    #
    # Blacklist
    #    
    save_command "if chain_exists blacklst; then"
    indent="$INDENT"
    INDENT="$INDENT    "
    
    save_command progress_message2 \"Refreshing Black List...\"
    run_iptables -F blacklst

    [ -s ${TMP_DIR}/blacklist ] && save_command load_blacklist
    
    INDENT="$indent"
    save_command "fi"

    ecn=$(find_file ecn)

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

    append_file refreshed

    save_command "cp -f \$(my_pathname) \${VARDIR}/.restore"

    INDENT=""

    save_command "}"
    save_command

}

#
# Source the extension script for an action, if any
#
process_action_file() # $1 = File Name
{
    if ! list_search $1 $BUILTIN_ACTIONS; then
	local user_exit
	user_exit=$(find_file $1)

	if [ -f $user_exit ]; then
	    progress_message "Processing $user_exit ..."
	    
	    . $user_exit
	fi
    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
    actchain=
    local action
    action=$1
    local level
    level=$2

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

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

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

    CHAIN=%${CHAIN}${actchain}

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

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

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

    process_action_file $1

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

}

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

createactionchain() # $1 = Action, including log level and tag if any
{
    create_simple_chain() 
    {
	CHAIN=$1
	LEVEL=
	TAG=
	createchain $CHAIN no
	
	process_action_file $CHAIN
    }

    case $1 in
	*::*)
	    fatal_error "Invalid ACTION $1"
	    ;;
	*:*:*)
	    set -- $(split $1)
	    createlogactionchain $1 $2:$3
	    ;;
	*:)
	    create_simple_chain ${1%:*}
	    ;;
	*:*)
	    set -- $(split $1)

	    if [ "x$2" = xnone ]; then
		create_simple_chain $1
	    else
		createlogactionchain $1 $2
	    fi
	    ;;
	*)
	    create_simple_chain $1
	    ;;
    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
    fullaction=$1
    local action
    action=${1%%:*}
    local level
    level=
    local chains
    chains=

    find_simpleaction() {
	havechain $action ||  fatal_error "Fatal error in find_logactionchain"
	echo $action
    }

    case $fullaction in
	*:)
	    find_simpleaction
	    return
	    ;;
	*:*)
	    level=${fullaction#*:}
	    if [ "x$level" = xnone ]; then
		find_simpleaction
		return
	    fi
	    ;;
	*)
	    find_simpleaction
	    return
	    ;;
    esac

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

    set -- $chains

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

    fatal_error "Fatal error in find_logactionchain"

}

#
# This function determines the logging for a subordinate action or a rule within a subordinate action
#
merge_levels() # $1=level at which superior action is called, $2=level at which the subordinate rule is called
{
    local superior
    superior=$1
    local subordinate
    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
}

#
# Define the builtin actions. They are available even when USE_ACTIONS=No
#
define_builtin_actions() {
    ACTIONS="dropBcast allowBcast dropNotSyn rejNotSyn dropInvalid allowInvalid allowinUPnP allowoutUPnP forwardUPnP Limit"
    BUILTIN_ACTIONS="$ACTIONS"
    USEDACTIONS=
}

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

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

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

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

    echo $1
}

# 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
    logpart=${2#*:}

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

#
# Third phase of action processing. It needs to be here in the compiler because
#                                   it handles builtin actions.
#
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

	case $xlevel in
	    none|none'!')
		ylevel=
		;;
	    *)
		ylevel=$xlevel;
		;;
	esac

	save_progress_message "Creating action chain $xaction1"

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

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

ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do
__EOF__
			[ -n "$ylevel" ] && indent >&3 << __EOF__
   log_rule_limit ${ylevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d \$address
__EOF__
			    indent >&3 << __EOF__
    run_iptables -A $xchain -d \$address -j DROP
done

__EOF__
		    done

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

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

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

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

			indent >&3 << __EOF__
    run_iptables -A $xchain -d \$address -j ACCEPT
done

__EOF__
		    done

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

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

		run_iptables -A $xchain -p udp --dport 1900  -j ACCEPT
		run_iptables -A $xchain -p tcp --dport 49152 -j ACCEPT
		;;
	    allowoutUPnP)
		[ -n "$ylevel" ] && \
		    log_rule_limit ${ylevel%\!} $xchain allowoutUPnP ACCEPT "" "$xtag" -A -m owner --owner-cmd upnpd
		run_iptables -A $xchain -m owner --cmd-owner upnpd -j ACCEPT
		;;
	    Limit)
		set -- $(separate_list $xtag)

		[ $# -eq 3 ] || fatal_error "Limit rules must include <set name>,<max connections>,<interval> as the log tag"

		run_iptables -A $xchain -m recent --name $1 --set

		if [ -n "$ylevel" ]; then
		    run_iptables -N $xchain%
		    log_rule_limit $ylevel $xchain% $1 DROP "" "" -A
		    run_iptables -A $xchain% -j DROP
		    run_iptables -A $xchain -m recent --name $1 --update --seconds $3 --hitcount $(( $2 + 1 )) -j $xchain%
		else
		    run_iptables -A $xchain -m recent --update --name $1 --seconds $3 --hitcount $(( $2 + 1 )) -j DROP
		fi

		run_iptables -A $xchain -j ACCEPT
		;;
	    *)
		#
		# Not a builtin
		#
		process_action3
		;;
	esac
    done
}

#
# Add one Filter Rule
#
# The caller has established the following variables:
#	 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
#	 mark		= Packet mark
#	 logtag		= Log tag
#	 policy		= Applicable Policy
#
add_a_rule() {
    local natrule
    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 $multiport $sports $dports) $user -m conntrack --ctorigdst $adr -j $chain
	    done
	    addr=
	else
	    run_iptables -A $logchain $state $(fix_bang $cli $proto $multiport $sports $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"
    mrk="$mark"

    # Restore $chain to the canonical chain.

    chain=$logchain

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

    case $proto in
	tcp|TCP|6)
	    do_ports
	    ;;
	tcp:syn)
	    proto="tcp --syn"
	    do_ports
	    ;;
	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 [ "$SECTION" != DONE ]; then
		#
		# This function is called from process_default_macro() after rules are DONE
		#
		if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" -a -z "$user" -a -z "$excludesource" -a -z "$excludedest" -a -z "$mark" ] ; then
		    error_message "WARNING -- Rule \"$rule\" is a POLICY"
		    error_message "	   -- and should be moved to the policy file"
		fi
	    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)
	    [ -n "$FASTACCEPT" ] && fatal_error "Entries in the $SECTION SECTION of the rules file not permitted with FASTACCEPT=Yes"
	    state="-m state --state $SECTION"
	    ;;
	*)
	    state=
	    ;;
    esac

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

        # A specific server or server port given

	if [ -n "$natrule" ]; then
	    lib_load nat "$logtarget Rules"
	    add_nat_rule
	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
			srv=$(dest_ip_range $srv)
			if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then
			    if [ "$addr" = detect ]; then
				indent >&3 << __EOF__
    run_iptables -A $chain $state $proto $ratelimit $multiport $cli $sports $srv $dports -m conntrack --ctorigdst \$adr $user $mrk -j $target
done

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

				    if [ "$logtarget" != LOG ]; then
					run_iptables2 -A $chain $state $proto $ratelimit $multiport $cli $sports \
					    $srv $dports -m conntrack --ctorigdst $adr $user $mrk -j $target
				    fi
				done
			    fi
			else
			    if [ "$addr" = detect ]; then
				save_command 'done'
				save_command ''
			    fi

			    if [ -n "$loglevel" -a -z "$natrule" ]; then
				log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \
				    $state $(fix_bang $proto $multiport $sports $cli $srv $dports)
			    fi

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

			    if [ "$logtarget" != NONAT -a "$logtarget" != LOG ]; then
				run_iptables2 -A $chain $state $proto $multiport $cli $sports \
				    $srv $dports $ratelimit $user $mrk -j $target
			    fi
			fi
		    done
		done
	    else
		if [ "$addr" = detect ]; then
		    save_command 'done'
		    save_command ''
		fi

		if [ -n "$loglevel" -a -z "$natrule" ]; then
		    log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \
			$state $(fix_bang $proto $multiport $sports $cli $dports)
		fi

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

		[ "$logtarget" != NONAT  -a "$logtarget" != LOG ] && \
		    run_iptables2 -A $chain $state $proto $multiport $cli $sports \
		    $dports $ratelimit $user $mrk -j $target
	    fi
	elif [ -n "$serv" -a "$addr" = detect ]; then
	    save_command 'done'
	    save_command ''
	fi
    else

    # Destination is a simple zone

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

	if [ -n "$addr" ]; then
	    for adr in $(separate_list $addr); do
		if [ -n "$loglevel" ]; then
		    log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \
			$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 $mrk  -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 $mrk  -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 $mrk \
		    $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 $mrk -j RETURN
		fi

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

    if [ "$logtarget" = LOG -a -z "$KLUDGEFREE" ]; then
	#
	# Purge the temporary files that we use to prevent duplicate '-m' specifications
	#
	[ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange
    fi

}

#
# Process the contents of the USER/GROUP column
#
process_userspec()
{
    [ "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
}

#
# Process the RATE/LIMIT column contents
#
process_ratelimit() {
    [ "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
}

#
# Process the MARK column contents
#
process_mark() {
    [ "x$mark" = "x-" ] && mark=

    if [ -n "$mark" ]; then
        if [ "$mark" = "${mark%!*}" ]; then
            mark="-m mark --mark $mark"
        else
            mark="-m mark ! --mark ${mark#*!}"
        fi
    fi
}

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

#
# Process a record from the rules file
#
process_rule() # $1 = target
               # $2 = clients
               # $3 = servers
               # $4 = protocol
               # $5 = ports
               # $6 = cports
               # $7 = address
               # $8 = ratelimit
               # $9 = userspec
               # $10= mark
{
    local target
    target="$1"
    local clients
    clients="$2"
    local servers
    servers="$3"
    local protocol
    protocol="$4"
    local ports
    ports="$5"
    local cports
    cports="$6"
    local address
    address="$7"
    local ratelimit
    ratelimit="$8"
    local userspec
    userspec="$9"
    local mark
    mark="${10}"
    local userandgroup
    userandgroup=
    local logtag
    logtag=
    local nonat
    nonat=

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

    process_mark

    process_ratelimit

    # Isolate log level

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

	case $loglevel in
	    none*)
		loglevel=
		[ $target = LOG ] && return
		;;
	    *)
		[ -n "$loglevel" ] || target=${target%:*}
		;;
	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$address" = "x-" ] && address=

    process_userspec

    case $target in
	*!)
	    target=${target%!}
	    ;;
    esac

    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)
	    [ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with 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

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

    source=$clientzone

    if [ $source != $FW -a -n "$userspec" ]; then
	fatal_error "Invalid use of a user-qualification: rule \"$rule\""
    fi

    # Parse and validate destination

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

    excludedest=

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

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

    dest=$serverzone

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

    chain=${source}2${dest}

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

    logchain=$chain

    eval policy=\$${chain}_policy

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

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

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

    ensurechain $chain

    # Generate Netfilter rule(s)

    case $logtarget in
	DNAT*|SAME)

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

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

#
# Process a macro invocation in the rules file
#

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

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

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

	[ $mtarget = COMMENT ] && continue

	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!|ACCEPT+|NONAT|DROP|DROP!|REJECT|REJECT!|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|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
		-|SOURCE)
		    mclients=${iclients}
		    ;;
		DEST)
		    mclients=${iservers}
		    ;;
		*)
		    mclients=$(merge_macro_source_dest $mclients $iclients)
		    ;;
	    esac
	else
	    mclients=${iclients}
	fi

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

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

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

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

    progress_message "..End Macro"

}

#
# Process the rules file
#
process_rules()
{
    local comment
    comment=
    local optimize
    #
    # 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
	local yservers
	local ysourcezone
	local ydestzone
	local 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 [ $optimize -gt 0 ]; then
			    eval yloglevel=\$${ysourcezone}2${ydestzone}_loglevel
			    if [ -n "$yloglevel" ]; then
				if [ x$ypolicy:$yloglevel = x$xtarget ]; then
				    continue
				fi
			    elif [ x$ypolicy = x$xtarget ]; then
				continue
			    fi
			fi
			if [ "$1" = Yes ]; then
			    process_macro $xtarget "$xparam" $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark
			else
			    rule="$xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark"
			    process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark
			fi
		    fi
		fi
	    done
	done
    }

    do_it() # $1 = "Yes" if the target is a macro.
    {
	local intrazone
	intrazone=

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

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

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

	case $xclients in
	    all|all-)
		[ $xclients = all ] && xclients="$ZONES $FW" || xclients="$ZONES"

		if [ "x$xservers" = xall ]; then
		    xservers="$ZONES $FW"
		elif [ "x$xservers" = xall- ]; then
		    xservers="$ZONES"
		fi

		process_wildcard_rule "$1" $intrazone
		return
		;;
	esac

	case $xservers in
	    all|all-)
		xservers="$ZONES $FW"
		process_wildcard_rule "$1" $intrazone
		return
		;;
	esac

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

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

	optimize=$OPTIMIZE;

	case "${xtarget%%:*}" in
	    ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-)
		do_it No
		;;
	    ACCEPT!|DROP!|REJECT!|QUEUE!|CONTINUE!)
		optimize=0
		do_it No
		;;
	    COMMENT)
		if [ -n "$COMMENTS" ]; then
		    comment=$(echo $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark)
		    save_command COMMENT=\"$comment\"
		else
		    error_message "COMMENT ignored --  requires comment support in iptables/Netfilter"
		fi
		continue
		;;
	    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 $xmark"
			    fatal_error "Invalid Action in rule \"$rule\""
			fi
		    fi
		fi
		;;

	esac

    done < $TMP_DIR/rules
    #
    # Just in case the file ended with a comment
    #
    if [ -n "$COMMENTS" ]; then
	save_command
	save_command COMMENT=
    fi
    
    case $SECTION in
	ESTABLISHED)
	    finish_section ESTABLISHED,RELATED
	    ;;
	RELATED)
	    finish_section RELATED
	    ;;
    esac

    SECTION=DONE
}

#
# Process a default macro
#
process_default_macro() # $1 = macro name
{
    local macro
    macro=$1
    local address
    address=
    local multioption
    multioption=
    local servport
    servport=
    local chain
    chain=$1
    local logchain
    logchain=$1
    local userandgroup
    userandgroup=
    local logtag
    logtag=
    local excludesource
    excludesource=
    local target
    local client
    local server
    local protocol
    local port
    local cport
    local ratelimit
    local userspec
    local rule
    local f
    f=$(find_file macro.${macro})

    havechain $macro && fatal_error "Illegal duplicate default macro name: $macro"

    createchain $macro no
    strip_file macro.$macro $f
    progress_message "..Expanding Default Macro $f into chain $macro..."

    while read target client server protocol port cport ratelimit userspec; do
	rule="$target ${client:--} ${server:--} ${protocol:--} ${port:--} ${cport:--} ${ratelimit:--} ${userspec:--}"

	case $target in
	    PARAM|PARAM:*)
		fatal_error "Invalid target ($target) in default macro $macro"
		;;
	esac

	case ${target} in
	    ACCEPT|DROP|REJECT)
		;;
	    *)
		if ! list_search $target $USEDACTIONS; then
		    if list_search $target $ACTIONS; then
			createactionchain $target
			USEDACTIONS="$USEDACTIONS $target"
		    else
			fatal_error "Invalid target ($target) in default macro $macro"
		    fi
		fi
		;;
	esac

	if [ $(list_count ${port}${cport}) -gt 1 ]; then
	    multioption="-m multiport"
	fi

	if [ -n "$client" ]; then
	    case $client in
		-|SOURCE)
		    client=
		    ;;
	    esac
	fi

	if [ -n "$server" ]; then
	    case $server in
		-|DEST)
		    server=
		    ;;
		*)
		    ;;
	    esac
	fi

	process_userspec

	process_ratelimit

	add_a_rule
	progress_message "Rule \"$target $protocol $port $cport $ratelimit $userspec\" $DONE"

    done < $TMP_DIR/macro.$macro

    progress_message "..End Macro"

}

#
# 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". The caller
# has also set the variable 'chain' to contain the name of the mangle table
# chain where forward rules are to be in placed in.
#
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}"

    [ "x$mark" = x- ] && mark=
    if [ -n "$mark" ]; then
        if [ "$mark" = "${mark%!*}" ]; then
            mark="-m mark --mark $mark"
        else
            mark="-m mark ! --mark ${mark#*!}"
        fi
    fi

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

    case "$dstzone" in
	all|ALL)
	    dst=0.0.0.0/0
	    ;;
	*)
	    if [ -z "$MANGLE_FORWARD" ]; then
		error_message "WARNING: A zone name in the DEST column requires Mangle FORWARD Chain support in your kernel and iptables: rule \"$rule\" ignored"
		return
	    fi

	    [ -z "$dst" ] && eval dst=\$${dstzone}_hosts
	    ;;
    esac

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

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

		for host in $hosts; do
		    run_iptables2 -t mangle -A $chain $(match_source $host) \
			$protocol $dest $dports $sports $mark $tos
		done
	    fi
	    ;;
	esac
    done

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

#
# Process the tos file
#
process_tos() # $1 = name of tos file
{
    local chain
    chain=pretos
    local stdchain
    stdchain=PREROUTING

    if [ -n "$MANGLE_FORWARD" ]; then
	chain=fortos
	stdchain=FORWARD
    fi

    if [ -s $TMP_DIR/tos ] ; then

	save_progress_message "Setting up TOS..."

	progress_message2 "$DOING $1..."

	createmanglechain $chain
	createmanglechain outtos

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

	run_iptables -t mangle -A $stdchain  -j $chain
	run_iptables -t mangle -A OUTPUT     -j outtos
    fi
}

policy_rules() # $1 = chain to add rules to
	       # $2 = policy
	       # $3 = loglevel
               # $4 = Default Action/Macro
{
    local target
    target="$2"
    local default
    default="$4"

    if [ -n "$default" ]; then
	[ "$default" = none ] || run_iptables -A $1 -j $default
    fi


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

    if [ -n "$target" ]; then
	case $target in
	    REJECT)
		run_iptables -A $1 -j reject
		;;
	    CONTINUE)
		;;
	    *)
		run_iptables -A $1 -j $target
		;;
	esac
    fi
}

#
# 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
    chain="${1}2${2}"
    local policy
    policy=
    local loglevel
    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 for the policy chain
	#
	eval policy=\$${chain1}_policy
	eval loglevel=\$${chain1}_loglevel
	eval synparams=\$${chain1}_synparams
	eval default=\$${chain1}_default
	#
	# 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:--}" $default
	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:--}" $default
		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:--}" $default
		;;
	    *)
		#
		# 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
    policy=
    local loglevel
    loglevel=
    local policychain
    policychain=
    local default
    default=

    run_user_exit $1

    [ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT

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

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

	policy_rules $1 $policy "${loglevel:--}" $default
    else
	policy_rules $1 DROP info $DROP_DEFAULT
    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
    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
}

#
# Add a record to the blacklst chain
#
#   $source      = address match
#   $proto       = protocol selector
#   $dport       = destination port selector
#
add_blacklist_rule() {
    run_iptables2 -A blacklst $source $proto $dport -j $target
}

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

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

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

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

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

	progress_message_and_save "   $addr added to Black List"
    done
}

process_blacklist()
{
    local disposition
    disposition=$BLACKLIST_DISPOSITION
    local f
    f=$(find_file blacklist)
    local target

    if [ -s $TMP_DIR/blacklist ]; then

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

	[ -n "$BLACKLIST_LOGLEVEL" ] && target=blacklog || target=$disposition

	progress_message2 "Compiling $f..."

	cat >&3 << __EOF__
#
# Load the blacklist
#
load_blacklist() 
{
__EOF__
	INDENT="    "

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

	INDENT=
	save_command "}"
	save_command
    fi
}

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

    if [ -n "$hosts" -a -s ${TMP_DIR}/blacklist ]; then
	progress_message2 "$DOING Blacklisting..."

	createchain blacklst no

	if [ -n "$BLACKLIST_LOGLEVEL" ]; then
	    createchain blacklog no
	    log_rule_limit $BLACKLIST_LOGLEVEL blacklog blacklst $BLACKLIST_DISPOSITION "$LOGLIMIT" "" -A
	    
	    if [ $BLACKLIST_DISPOSITION = REJECT ]; then
		run_iptables -A blacklog -j reject
	    else
		run_iptables -A blacklog -j $BLACKLIST_DISPOSITION
	    fi
	fi

	[ -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_and_save "   Blacklisting enabled on ${interface}${network}"
	done

	if [ -z "$DELAYBLACKLISTLOAD" -a -s ${TMP_DIR}/blacklist ]; then
	    save_command load_blacklist
	fi
    fi
}

# Construct zone-independent rules
#
add_common_rules() {
    local savelogparms
    savelogparms="$LOGPARMS"
    local broadcasts
    broadcasts="$(find_broadcasts) 255.255.255.255 224.0.0.0/4"
    #
    # Populate the smurf chain
    #
    save_progress_message "Setting up SMURF control..."

    for interface in $(find_bcastdetect_interfaces); do
	indent >&3 << __EOF__

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

__EOF__
    done

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

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

__EOF__
	done

	for address in $broadcasts ; do
	    run_iptables -A reject -d $address -j DROP
	done
    fi
    #
    # Don't feed the smurfs
    #
    for address in $broadcasts ; do
	run_iptables -A reject -s $address -j DROP
    done

    #
    # Don't respond to IGMP with an ICMP
    #
    run_iptables -A reject -p 2 -j DROP

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

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

    append_file initdone

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

    setup_blacklist

    #
    # SMURFS
    #
    hosts=$(find_hosts_by_option nosmurfs)

    if [ -n "$hosts" ]; then

	progress_message2 "Adding Anti-smurf Rules"

	save_progress_message "Adding Anti-smurf Jumps..."

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

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

    if [ -n "$interfaces" ]; then

	progress_message2 "Adding rules for DHCP"

	save_progress_message "Setting up rules for DHCP..."

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

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

	save_progress_message "Setting up RFC1918 Filtering..."

	createchain norfc1918 no

	createchain rfc1918 no

	log_rule $RFC1918_LOG_LEVEL rfc1918 DROP

	run_iptables -A rfc1918 -j DROP

	chain=norfc1918

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

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

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

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

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

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

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

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

    hosts=$(find_hosts_by_option tcpflags)

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

	save_progress_message "Setting up TCP Flags checking..."

	createchain tcpflags no

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

	    savelogparms="$LOGPARMS"

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

	    log_rule $TCP_FLAGS_LOG_LEVEL logflags $TCP_FLAGS_DISPOSITION

	    LOGPARMS="$savelogparms"

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

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

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

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

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

    indent >&3 << __EOF__
for f in /proc/sys/net/ipv4/conf/*; do
    [ -f \$f/arp_filter ] && echo 0 > \$f/arp_filter
    [ -f \$f/arp_ignore ] && echo 0 > \$f/arp_ignore
done

__EOF__

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

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

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

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

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

	save_progress_message "Setting up Route Filtering..."

	if [ "$ROUTE_FILTER" = no ]; then
	    indent >&3 << __EOF__

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

__EOF__
	fi

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

	    indent >&3 << __EOF__
if [ -f $file ]; then
    echo 1 > $file
else
    error_message "WARNING: Cannot set route filtering on $interface"
fi
__EOF__
	done

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

	if [ "$ROUTE_FILTER" = yes ]; then
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter"
	elif [ "$ROUTE_FILTER" = no ]; then
	    save_command "echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter"
	fi

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

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

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

	save_progress_message "Setting up Martian Logging..."

	if [ "$LOG_MARTIANS" = no ]; then
	    indent >&3 << __EOF__

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

__EOF__
	fi

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

	    indent >&3 << __EOF__
if [ -f $file ]; then
    echo 1 > $file
else
    error_message "WARNING: Cannot set Martian logging on $interface"
fi

__EOF__
	    done
	
	if [ "$LOG_MARTIANS" = yes ]; then
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/all/log_martians"
	    save_command "echo 1 > /proc/sys/net/ipv4/conf/default/log_martians"
	elif [ "$LOG_MARTIANS" = no ]; then
	    save_command "echo 0 > /proc/sys/net/ipv4/conf/all/log_martians"
	    save_command "echo 0 > /proc/sys/net/ipv4/conf/default/log_martians"
	fi

    fi

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

    indent >&3 << __EOF__
for f in /proc/sys/net/ipv4/conf/*; do
    [ -f \$f/accept_source_route ] && echo 0 > \$f/accept_source_route
done

__EOF__

    interfaces=$(find_interfaces_by_option sourceroute)

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

	save_progress_message "Setting up Source Routing..."

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

	    indent >&3 << __EOF__
if [ -f $file ]; then
    echo 1 > $file
else
    error_message "WARNING: Cannot set Accept Source Routing on $interface"
fi
__EOF__
	done
    fi

    if [ -n "$DYNAMIC_ZONES" ]; then
	progress_message "$DOING 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 $(out_chain $interface)     -j $(dynamic_out $interface)
	done
    fi
    #
    # UPnP
    #
    interfaces=$(find_interfaces_by_option upnp)

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

	save_progress_message "Setting up UPnP..."

	createnatchain UPnP

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

#
# 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
	eval default=\$${chain}_default

	if [ "$policy" != NONE ]; then
	    if ! havechain $chain && [ -z "$optional" -a "$policy" != CONTINUE ]; then
		#
		# The chain doesn't exist. Create the chain and add policy
		# rules
		#
		createchain $chain yes
		#
		# If either client or server is 'all' then this MUST be
		# a policy chain and we must apply the appropriate policy rules
		#
		# Otherwise, this is a canonical chain which will be handled in
		# the for loop below
		#
		case $chain in
		    all2*|*2all)
			run_user_exit $chain
			policy_rules $chain $policy "${loglevel:--}" $default
			;;
		esac
	    fi
	fi
    done

    #
    # Add policy rules to canonical chains
    #
    for zone in $FW $ZONES; do
	for zone1 in $FW $ZONES; do
	    chain=${zone}2${zone1}
	    if havechain $chain; then
		run_user_exit $chain
		default_policy $zone $zone1
	    fi
	done
    done
}

#
# Activate the rules
#
activate_rules()
{
    local PREROUTING_rule
    PREROUTING_rule=1
    local POSTROUTING_rule
    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
	sourcechain=$1
        local destchain
        destchain=$2
	shift
	shift

	if havenatchain $destchain ; then
	    run_iptables2 -t nat -A $sourcechain $@ -j $destchain
	elif [ -z "$KLUDGEFREE" ]; then
	    [ -n "$PHYSDEV_MATCH" -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 inserted before jumps to one-to-one NAT chains.
    #
    addrulejump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments
    {
	local sourcechain
	sourcechain=$1
	local destchain
	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\)\)
	elif [ -z "$KLUDGEFREE" ]; then
	    [ -n "$PHYSDEV_MATCH" -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
    }
    #
    # Insert a set of exclusions at the front of a chain
    #
    insert_exclusions() # $1 = table $2 = chain name, $3 - $n = exclusions
    {
	local t
	t=$1
	local c
	c=$2
	local num
	num=0
	local host1
	local interface1
	local networks1

	shift 2

	for host1 in $*; do
	    interface1=${host1%%:*}
	    networks1=${host1#*:}
	    num=$(($num + 1))
	    run_iptables -t $t -I $c $num -o $interface1 -d $networks1 -j RETURN
	done
    }
    #
    # Add a set of exclusions to the end of a chain
    #
    add_exclusions() # $1 = table $2 = chain name, $3 - $n = exclusions
    {
	local t
	t=$1
	local c
	c=$2
	local host1
	local interface1
	local networks1

	shift 2

	for host1 in $*; do
	    interface1=${host1%%:*}
	    networks1=${host1#*:}
	    run_iptables -t $t -A $c -o $interface1 -d $networks1 -j RETURN
	done
    }
    #
    #                E x e c u t i o n   S t a r t s   H e r e
    #
    # Add jumps to early SNAT chains
    #
    for interface in $ALL_INTERFACES; do
	addnatjump POSTROUTING $(snat_chain $interface) -o $interface
    done
    #
    # Add jumps for dynamic nat chains
    #
    [ -n "$DYNAMIC_ZONES" ] && for interface in $ALL_INTERFACES ; do
	addrulejump PREROUTING $(dynamic_in $interface) -i $interface
    done
    #
    # Add jumps from the builtin chains to the nat chains
    #
    addnatjump PREROUTING  nat_in
    addnatjump POSTROUTING nat_out

    for interface in $ALL_INTERFACES; do
	addnatjump PREROUTING  $(input_chain $interface)  -i $interface
	addnatjump POSTROUTING $(output_chain $interface) -o $interface
    done

    > $STATEDIR/chains
    echo "$FW firewall" > $STATEDIR/zones
    #
    # Create forwarding chains for complex zones and generate jumps for IPSEC source hosts to that chain.
    #
    for zone in $ZONES; do
	if eval test -n \"\$${zone}_is_complex\" ; then
	    frwd_chain=${zone}_frwd
	    createchain $frwd_chain No

	    eval exclusions=\"\$${zone}_exclusions\"

	    if [ -n "$exclusions" ]; then
		local num
		num=1
		in_chain=${zone}_input
		out_chain=${zone}_output
		createchain $in_chain No
		createchain $out_chain No

		if [ "$(rules_chain $zone $zone)" = ACCEPT ]; then
		    createchain ${zone}2${zone} yes
		    run_iptables -A ${zone}2${zone} -j ACCEPT
		fi

		for host in $exclusions; do
		    interface=${host%%:*}
		    address=${host#*:}
		    run_iptables -A $frwd_chain -i $interface -s $address -j RETURN
		    run_iptables -A $in_chain   -i $interface -s $address -j RETURN
		    run_iptables -A $out_chain  -i $interface -s $address -j RETURN
		done
	    fi

	    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
    #
    # Main source zone rule-activation loop
    #
    for zone in $ZONES; do
	eval source_hosts=\$${zone}_hosts

	chain1=$(rules_chain $FW $zone)
	chain2=$(rules_chain $zone $FW)
	chain3=$(rules_chain $zone $zone)

	eval complex=\$${zone}_is_complex
	eval type=\$${zone}_type
	eval exclusions=\"\$${zone}_exclusions\"

	if [ -n "$exclusions" ]; then
	    echo "$zone $type $source_hosts exclude $exclusions" >> $STATEDIR/zones
	else
	    echo "$zone $type $source_hosts" >> $STATEDIR/zones
	fi

	if [ -n "$DYNAMIC_ZONES" ]; then
	    echo "$FW $zone $chain1" >> $STATEDIR/chains
	    echo "$zone $FW $chain2" >> $STATEDIR/chains
	fi

	need_broadcast=

	if [ -n "$complex" ]; then
	    frwd_chain=${zone}_frwd
	    chain=$(dnat_chain $zone)
	    if havenatchain $chain; then
		insert_exclusions nat $chain $exclusions
	    fi
	fi
	#
	# Take care of PREROUTING, INPUT and OUTPUT jumps
	#
	for host in $source_hosts; do
	    interface=${host%%:*}
	    networks=${host#*:}

	    if [ -n "$chain1" ]; then
		if [ -n "$exclusions" ]; then
		    run_iptables2 -A $(out_chain $interface) $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j ${zone}_output
		    run_iptables -A ${zone}_output -j $chain1
		else
		    run_iptables2 -A $(out_chain $interface) $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j $chain1
		fi
	    fi
	    #
	    # 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)

	    if [ -n "$chain2" ]; then
		if [ -n "$exclusions" ]; then
		    run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j ${zone}_input
		    run_iptables -A ${zone}_input -j $chain2
		else
		    run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $chain2
		fi
	    fi

	    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
			    if interface_has_option $interface detectnets; then
				need_broadcast="$need_broadcast $interface"
				iface=$(chain_base $interface)
				eval need_bcast_$iface=\"$(match_source_hosts $networks)\"
			    fi
			fi
		    fi
		    ;;
	    esac
	done

	if [ -n "$chain1" ]; then
	    for interface in $need_broadcast ; do
		run_iptables -A $(out_chain $interface) -d 255.255.255.255 -j $chain1
		run_iptables -A $(out_chain $interface) -d 224.0.0.0/4     -j $chain1
	    done
	fi
	#
	#                           F O R W A R D I N G
	#
	temp_zones=
	last_chain=

	if [ $OPTIMIZE -gt 0 ]; then

	    dest_zones=
	    #
	    # The following loop attempts to eliminate redundant sequences of jumps to
	    # all2all or <source zone>2all. It does so by combining all trailing
	    # jumps to the same policy-only chain.
	    #
	    for zone1 in $ZONES; do

		eval policy=\$${zone}2${zone1}_policy

		[ "$policy" = NONE ] && continue

		chain="$(rules_chain $zone $zone1)"

		[ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain.

		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" -a -z "$exclusions" ] ; then
			continue
		    fi
		fi

		case $chain in
		    *2all)
		        #
		        # Rules chain is a policy-only chain that could be used more than once (all2all or ${zone}2all
		        #
			if [ -n "$last_chain" ]; then
			    #
			    # And the last rules chain was a policy-only chain
			    #
			    if [ "$chain" != "$last_chain" ]; then
			        #
			        # But it was a different one -- back to square 1
			        #
				last_chain=$chain
				dest_zones="$dest_zones $temp_zones"
				temp_zones=$zone1
			    else
			        #
			        # Same chain -- add this dest zone to the running list of
			        #               zones using the same rules chain
			        #
			        temp_zones="$temp_zones $zone1"
			    fi
		        elif [ $policy = ACCEPT ]; then
			    #
			    # We don't wild-card ACCEPT policies -- could open up security holes through interfaces
			    # that aren't described in /etc/shorewall/interfaces
			    #
			    dest_zones="$dest_zones $zone1"
			else
			    #
			    # First in a potential run of rules using this chain
		            #
			    last_chain=$chain
			    temp_zones=$zone1
			fi
			;;
		    *)
		        #
		        # Not a policy-only chain -- add accumulated sequence of dest zones to those needing processing
		        #
			dest_zones="$dest_zones $temp_zones $zone1"
			temp_zones=
			last_chain=
			;;
		esac
	    done
	    #
	    # If there is no reduction in the number of rules then don't bother with the optimization
	    #
	    if [ -n "$last_chain" -a $(list_count1 $temp_zones) -eq 1 ]; then
		dest_zones="$dest_zones $temp_zones"
		last_chain=
	    fi
	else
	    dest_zones=$ZONES
	fi
	#
	# We now loop through the destination zones creating jumps to the rules chain for each source/dest combination.
	# $dest_zones is the list of destination zones that we need to handle from this source zone
	#
	for zone1 in $dest_zones; do

	    eval policy=\$${zone}2${zone1}_policy

	    [ "$policy" = NONE ] && continue

	    eval dest_hosts=\$${zone1}_hosts
	    eval exclusions1=\"\$${zone1}_exclusions\"

	    chain="$(rules_chain $zone $zone1)"

	    [ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain.

	    [ -n "$DYNAMIC_ZONES" ] && echo "$zone $zone1 $chain" >> $STATEDIR/chains

	    if [ $zone = $zone1 ]; then
		eval routeback=\"\$${zone}_routeback\"
		eval interfaces=\"\$${zone}_interfaces\"
		eval ports="\$${zone}_ports"

		num_ifaces=$(list_count1 $interfaces)

		[ $num_ifaces -eq 1 -a -n "$ports" ] && num_ifaces=$(list_count1 $ports)

		if [  $num_ifaces -lt 2 -a -z "$routeback" -a -z "$exclusions" ] ; then
		    continue
		fi
		
		if [ -n "$chain3" ]; then
		    for interface in $need_broadcast ; do
			if interface_has_option $interface routeback; then
			    iface=$(chain_base $interface)
			    eval source=\"\$need_bcast_$iface\"
			    run_iptables -A $(forward_chain $interface) $source $(match_dest_dev $interface) -d 255.255.255.255 -j $chain3;
			    run_iptables -A $(forward_chain $interface) $source $(match_dest_dev $interface) -d 224.0.0.0/4 -j $chain3;
			fi
		    done
		fi
	    else
		routeback=
		num_ifaces=0
	    fi

	    if [ -n "$exclusions1" ]; then
		#
		# We handle exclusions in the dest zone by inserting RETURN rules at the front of
		# each rules chain where the zone is the destination
		#
		case $chain in
		    all2$zone1)
			#
			# We only want to add the exclusions once
			#
		        if eval test -z \"\$${chain}_exclusions\"; then
			    eval ${chain}_exclusions=Yes
			    insert_exclusions filter $chain $exclusions1
			fi
			;;
		    *2all)
			#
			# A policy-only chain -- we create one exclusion chain for this
			#                        dest zone/chain combination, and re-use
			#                        it if the occasion presents itself
			#
			eval chain1=\$${chain}_${zone1}_ex

			if [ -z "$chain1" ]; then
			    #
			    # Must create the chain
			    #
			    chain1=excl_${EXCLUSION_SEQ}
			    EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 ))
			    eval ${chain}_${zone1}_ex=$chain1
			    createchain $chain1 no
			    add_exclusions filter $chain1 $exclusions1
			    run_iptables -A $chain1 -j $chain
			fi
			#
			# We must jump to the exclusion chain rather than to the policy chain
			#
			chain=$chain1
			;;
		    *)
			insert_exclusions filter $chain $exclusions1
			;;
		esac
	    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#*:}

		    chain4=$(forward_chain $interface)

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

			if [ "$host" != "$host1" ] || list_search $host $routeback; then
			    run_iptables2 -A $chain4 $(match_source_hosts $networks) -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain
			fi
		    done
		done
	    fi
	done
	#
	#                                      E N D   F O R W A R D I N G
	#
	# Now add (an) unconditional jump(s) to the last unique policy-only chain determined above, if any
	#
	if [ -n "$last_chain" ]; then
	    if [ -n "$complex" ]; then
		run_iptables -A $frwd_chain -j $last_chain
	    else
		for host in $source_hosts; do
		    interface=${host%%:*}
		    networks=${host#*:}

		    chain=$(forward_chain $interface)

		    run_iptables -A $chain $(match_source_hosts $networks) -j $last_chain
		done
	    fi
	fi
    done
    #
    # Now add the jumps to the interface chains from FORWARD, INPUT, OUTPUT and POSTROUTING
    #
    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)
	run_iptables -A OUTPUT  -o $interface -j $(out_chain $interface)
	addnatjump POSTROUTING $(masq_chain $interface) -o $interface
    done
    #
    # Handle fw->fw
    #
    chain=${FW}2${FW}

    if havechain $chain; then
	#
	# There is a fw->fw chain. Send loopback output through that chain
	#
	run_iptables -A OUTPUT -o lo -j $chain
	#
	# And delete the unconditional ACCEPT rule
	#
	run_iptables -D OUTPUT -o lo -j ACCEPT
    fi
    #
    # Add policy enforcement to the builtin filter chains to catch underfined hosts
    #
    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)
		    [ -n "$MANGLE_FORWARD" ] && chains="PREROUTING INPUT FORWARD POSTROUTING" || chains="PREROUTING INPUT"
		    ;;
		nat)
		    chains="PREROUTING POSTROUTING OUTPUT"
		    ;;
		*)
		    chains="INPUT FORWARD OUTPUT"
		    ;;
	    esac

	    for chain in $chains; do
		log_rule_limit $LOGALLNEW $chain $table $chain "" "" -I -m state --state NEW -t $table
	    done
	done
    fi
}

#
# Compile a script that will stop the firewall
#
# This function is called by compile_firewall() so all of the overloaded functions
# from that script are available here
#
compile_stop_firewall() {
    local IPTABLES_COMMAND
    IPTABLES_COMMAND="\$IPTABLES"
    local INDENT
    INDENT="    "

    cat >&3 << __EOF__

#
# Stop/restore the firewall after an error or because of a "stop" or "clear" command
#
stop_firewall() {

    deletechain() {
	qt \$IPTABLES -L \$1 -n && qt \$IPTABLES -F \$1 && qt \$IPTABLES -X \$1
    }

    deleteallchains() {
	\$IPTABLES -F
	\$IPTABLES -X
    }

    setcontinue() {
	\$IPTABLES -A \$1 -m state --state ESTABLISHED,RELATED -j ACCEPT
    }

    delete_nat() {
	\$IPTABLES -t nat -F
	\$IPTABLES -t nat -X

	if [ -f \${VARDIR}/nat ]; then
	    while read external interface; do
		del_ip_addr \$external \$interface
	    done < \${VARDIR}/nat

	    rm -f \${VARDIR}/nat
	fi
    }

    case \$COMMAND in
	stop|clear|restore)
	    ;;
	*)
	    set +x

            case \$COMMAND in
	        start)
	            logger -p kern.err "ERROR:\$PRODUCT start failed"
	            ;;
	        restart)
	            logger -p kern.err "ERROR:\$PRODUCT restart failed"
	            ;;
	        restore)
	            logger -p kern.err "ERROR:\$PRODUCT restore failed"
	            ;;
            esac
            
            if [ "\$RESTOREFILE" = NONE ]; then
                COMMAND=clear
                clear_firewall
                echo "\$PRODUCT Cleared"

	        kill \$\$
	        exit 2
            else
	        RESTOREPATH=\${VARDIR}/\$RESTOREFILE

	        if [ -x \$RESTOREPATH ]; then

		    if [ -x \${RESTOREPATH}-ipsets ]; then
		        progress_message2 Restoring Ipsets...
		        #
		        # We must purge iptables to be sure that there are no
		        # references to ipsets
		        #
		        for table in mangle nat filter; do
			    \$IPTABLES -t \$table -F
			    \$IPTABLES -t \$table -X
		        done

		        \${RESTOREPATH}-ipsets
		    fi

		    echo Restoring \${PRODUCT:=Shorewall}...

		    if \$RESTOREPATH restore; then
		        echo "\$PRODUCT restored from \$RESTOREPATH"
		        set_state "Started"
		    else
		        set_state "Unknown"
		    fi

	            kill \$\$
	            exit 2
	        fi
            fi
	    ;;
    esac

    set_state "Stopping"

    STOPPING="Yes"

    TERMINATOR=

    deletechain shorewall

    determine_capabilities

__EOF__

    append_file stop

    cat >&3 << __EOF__

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

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

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

    if [ -f \${VARDIR}/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 < \${VARDIR}/proxyarp

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

    rm -f \${VARDIR}/proxyarp

__EOF__
    [ -n "$CLEAR_TC" ] && save_command "delete_tc1"

    [ -n "$DISABLE_IPV6" ] && save_command "disable_ipv6"

    save_command "undo_routing"

    save_command "restore_default_route"

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then
	if [ -z "$ADMINISABSENTMINDED" ]; then
	    cat >&3 << __EOF__

    for chain in INPUT OUTPUT; do
	setpolicy \$chain ACCEPT
    done

    setpolicy FORWARD DROP

    deleteallchains

__EOF__

	    for host in $CRITICALHOSTS; do
		interface=${host%:*}
		networks=${host#*:}
		do_iptables -A INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
		do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
	    done
    
	    cat >&3 << __EOF__

    for chain in INPUT OUTPUT; do
	setpolicy \$chain DROP
    done

__EOF__
	else
	    cat >&3 << __EOF__

    for chain in INPUT OUTPUT; do
	setpolicy \$chain ACCEPT
    done

    setpolicy FORWARD DROP

    deleteallchains

__EOF__
	    for host in $CRITICALHOSTS; do
		interface=${host%:*}
		networks=${host#*:}
		do_iptables -A INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	        do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
	    done

	    cat >&3 << __EOF__

    setpolicy INPUT DROP

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

__EOF__
	fi
    elif [ -z "$ADMINISABSENTMINDED" ]; then
	cat >&3 << __EOF__

    for chain in INPUT OUTPUT FORWARD; do
	setpolicy \$chain DROP
    done

    deleteallchains

__EOF__
    else
	cat >&3 << __EOF__

    for chain in INPUT FORWARD; do
	setpolicy \$chain DROP
    done

    setpolicy OUTPUT ACCEPT

    deleteallchains

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

__EOF__
    fi

    process_routestopped -A

    save_command "\$IPTABLES -A INPUT  -i lo -j ACCEPT"

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

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

    save_command

    case "$IP_FORWARDING" in
	On|on|ON)
	    save_command "echo 1 > /proc/sys/net/ipv4/ip_forward"
	    save_command "progress_message2 IP Forwarding Enabled"
	    ;;
	Off|off|OFF)
	    save_command "echo 0 > /proc/sys/net/ipv4/ip_forward"
	    save_command "progress_message2 IP Forwarding Disabled!"
	    ;;
    esac

    append_file stopped

    cat >&3 << __EOF__

    set_state "Stopped"

    logger -p kern.info "\$PRODUCT Stopped"

    case \$COMMAND in
    stop|clear)
	;;
    *)
	#
	# The firewall is being stopped when we were trying to do something
	# else. Remove the lock file and Kill the shell in case we're in a
	# subshell
	#
	kill \$\$
	;;
    esac
}
__EOF__
}

#
# Conditionally add an option to .conf file (FD 3)
#
conditionally_add_option() { # $1 = option name
    local value

    eval value=\"\$$1\"

    if [ -n "$value" ]; then
	    cat >&3 << __EOF__
[ -n "\${$1:=$value}" ]
__EOF__
    fi
}

conditionally_add_option1() { # $1 = option name
    local value

    eval value=\"\$$1\"

    if [ -n "$value" ]; then
	    cat >&3 << __EOF__
$1="$value"
__EOF__
    fi
}

#
# Post-process generated script to:
#
#       - Suppress redundant blank lines
#       - Replace leading spaces with tabs
#
mycat() 
{
    if [ -n "$HAVEAWK" ]; then
	awk 'BEGIN {blnk=0;}; /^[[:space:]]*$/ {blnk=1; next; }; { while (/^\t*        /) sub(/        /, "\t"   ); if (blnk == 1 ) { print ""; blnk=0; }; print; }' $*
    else
	cat $*
    fi
}

#
# Compile a Firewall Script
#
compile_firewall() # $1 = File Name
{
    local IPTABLES_COMMAND
    IPTABLES_COMMAND=run_iptables
    local INDENT
    INDENT=""
    local checking
    checking=
    local outfile
    outfile=$1
    local dir
    dir=
    local match
    match=

    setup_mss()
    {
	case $CLAMPMSS in
	    Yes)
		option="--clamp-mss-to-pmtu"
		;;
	    *)
		option="--set-mss $CLAMPMSS"
		[ "$TCPMSS_MATCH" ] && match="-m tcpmss --mss $CLAMPMSS: "
		;;
	esac

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

    progress_message2 "Initializing..."

    #
    # So that mktempdir doesn't have to jump through hoops when there isn't a working 'mktemp',
    # we create the compiler's temporary directory in TMP_DIR
    #
    STATEDIR=$TMP_DIR/compiler_state/

    mkdir $STATEDIR || fatal_error "Cannot create temporary directory in $TMP_DIR"

    if [ $COMMAND = compile ]; then
	dir=$(dirname $1)
	[ -d $dir ] || fatal_error "Directory $dir does not exist"
	[ -h $dir ] && fatal_error "$dir is a Symbolic Link"
	[ -d $outfile ] && fatal_error "$outfile is a Directory"
	[ -h $outfile ] && fatal_error "$outfile is a Symbolic Link"
	[ -f $outfile -a ! -x $outfile ] && fatal_error "$outfile exists and is not a restore file"

	DOING=Compiling
	DONE=compiled

	OUTPUT=$(mktempfile $STATEDIR)

	[ -n "$OUTPUT" ] || fatal_error "Cannot create temporary file in /tmp"

	exec 3>>$OUTPUT
    else
	DOING=Checking
	DONE=checked
	checking=Yes
	COMMAND=compile

	exec 3>/dev/null
    fi

    cat >&3 << __EOF__
#
# Compiled firewall script generated by Shorewall-shell $VERSION - $(date)"
#
__EOF__

    if [ -n "$EXPORT" ]; then
	cat >&3 << __EOF__
SHAREDIR=/usr/share/shorewall-lite
CONFDIR=/etc/shorewall-lite

[ -f \${CONFDIR}/vardir ] && . \${CONFDIR}/vardir

[ -n "\${VARDIR:=/var/lib/shorewall-lite}" ]

__EOF__

	cat ${SHAREDIR}/lib.base >&3

	cat >&3 << __EOF__

################################################################################
#                        End of ${SHAREDIR}/lib.base
################################################################################

__EOF__
    else
	cat >&3 << __EOF__
SHAREDIR=/usr/share/shorewall
CONFDIR=/etc/shorewall

[ -f \${CONFDIR}/vardir ] && . \${CONFDIR}/vardir

[ -n "\${VARDIR:=/var/lib/shorewall}" ]

. \${SHAREDIR}/lib.base
__EOF__
    fi

    cat >&3 << __EOF__
#
# Set policy of chain \$1 to \$2
#
setpolicy() {
    \$IPTABLES -P \$1 \$2
}
__EOF__

    compile_stop_firewall

    cat >&3 << __EOF__

#
# Remove all Shorewall-added rules
#
clear_firewall() {
    stop_firewall

    setpolicy INPUT ACCEPT
    setpolicy FORWARD ACCEPT
    setpolicy OUTPUT ACCEPT

    run_iptables -F

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

__EOF__
    if [ -n "$DISABLE_IPV6" ]; then
	cat >&3 << __EOF__
    if qt mywhich ip6tables; then
	ip6tables -P INPUT   ACCEPT 2> /dev/null
	ip6tables -P OUTPUT  ACCEPT 2> /dev/null
	ip6tables -P FORWARD ACCEPT 2> /dev/null
    fi

__EOF__
    fi

    append_file clear

    cat >&3 << __EOF__

    set_state "Cleared"

    logger -p kern.info "\$PRODUCT Cleared"
}

#
# Issue a message and stop/restore the firewall
#
fatal_error()
{
    echo "   ERROR: \$@" >&2
    stop_firewall
    exit 2
}

#
# Issue a message and stop
#
startup_error() # \$* = Error Message
{
    echo "   ERROR: \$@" >&2
    case \$COMMAND in
        start)
	    logger -p kern.err "ERROR:\$PRODUCT start failed"
	    ;;
	restart)
	    logger -p kern.err "ERROR:\$PRODUCT restart failed"
	    ;;
	restore)
	    logger -p kern.err "ERROR:\$PRODUCT restore failed"
	    ;;
    esac
            
    kill \$\$
    exit 2
}

#
# Run iptables and if an error occurs, stop/restore the firewall
#
run_iptables()
{
    if [ -n "\$COMMENT" ]; then
        \$IPTABLES \$@ -m comment --comment "\$COMMENT"
    else
        \$IPTABLES \$@
    fi

    if [ \$? -ne 0 ]; then
        error_message "ERROR: Command \"\$IPTABLES \$@\" Failed"
	stop_firewall
        exit 2
    fi
}

#
# Run iptables and if an error occurs, stop/restore the firewall
#
run_ip()
{
    if ! ip \$@; then
	error_message "ERROR: Command \"ip \$@\" Failed"
	stop_firewall
	exit 2
    fi
}

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

#
# Functions to appease unconverted extension scripts
#
save_command()
{
    return 0
}

run_and_save_command() {
    eval \$@
}

ensure_and_save_command() {
    eval \$@ || fatal_error "Command \"\$@\" failed"
}

#
# Initialize environment
#
initialize() {
__EOF__
    INDENT="    "

    if [ -n "$EXPORT" ]; then
	cat >&3 << __EOF__
    #
    # These variables are required by the library functions called in this script
    #
    CONFIG_PATH="/etc/shorewall-lite:/usr/share/shorewall-lite"
__EOF__
    else
	cat >&3 << __EOF__
    if [ ! -f \${SHAREDIR}/version ]; then
	fatal_error "This script requires Shorewall which do not appear to be installed on this system (did you forget "-e" when you compiled?)"
    fi

    local version
    version=\$(cat \${SHAREDIR}/version)

    if [ \${SHOREWALL_LIBVERSION:-0} -lt 30203 ]; then
	fatal_error "This script requires Shorewall version 3.3.3 or later; current version is \$version"
    fi
    #
    # These variables are required by the library functions called in this script
    #
    CONFIG_PATH="$CONFIG_PATH"
__EOF__
    fi

    cat >&3 << __EOF__
    [ -n "\${COMMAND:=restart}" ]
    [ -n "\${VERBOSE:=0}" ]
    [ -n "\${RESTOREFILE:=$RESTOREFILE}" ]
    MODULESDIR="$MODULESDIR"
    MODULE_SUFFIX="$MODULE_SUFFIX"
    LOGLIMIT="$LOGLIMIT"
    LOGTAGONLY="$LOGTAGONLY"
    LOGRULENUMBERS="$LOGRULENUMBERS"
__EOF__

    if [ -n "$LOGFORMAT" ]; then
	cat >&3 << __EOF__
    LOGFORMAT="$LOGFORMAT"
__EOF__
    else
	cat >&3 << __EOF__
    [ -n "\$LOGFORMAT\" ] || LOGFORMAT="Shorewall:%s:%s:"
__EOF__
    fi

    cat >&3 << __EOF__
    VERSION="$VERSION"
    SUBSYSLOCK="$SUBSYSLOCK"
    LOCKFILE="$LOCKFILE"
    PATH="$PATH"
    TERMINATOR=fatal_error
    DONT_LOAD="$DONT_LOAD"

__EOF__
    if [ -n "$IPTABLES" ]; then
	cat >&3 << __EOF__
    IPTABLES="$IPTABLES"

    [ -x "$IPTABLES" ] || startup_error "IPTABLES=$IPTABLES does not exist or is not executable"
__EOF__
   else
	cat >&3 << __EOF__
    [ -z "\$IPTABLES" ] && IPTABLES=\$(mywhich iptables 2> /dev/null)

    [ -n "\$IPTABLES" -a -x "\$IPTABLES" ] || startup_error "Can't find iptables executable"
__EOF__
    fi

    [ -n "$EXPORTPARAMS" ] && append_file params

    cat >&3 << __EOF__

    STOPPING=
    COMMENT=

    #
    # The library requires that ${VARDIR} exist
    #
    [ -d \${VARDIR} ] || mkdir -p \${VARDIR}

}
__EOF__

    report_capabilities

    if [ -n "$BRIDGING" ]; then
	[ -n "$PHYSDEV_MATCH" ] || fatal_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
	fatal_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" ] && \
	fatal_error "RFC1918_STRICT=Yes requires Connection Tracking match"

    progress_message2 "Determining Zones..."

    determine_zones

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

    progress_message2 "Validating interfaces file..."

    validate_interfaces_file

    progress_message2 "Validating hosts file..."

    validate_hosts_file

    define_builtin_actions

    if [ -n "$USE_ACTIONS" ]; then
	progress_message2 "Pre-processing Actions..."
	process_actions1
    fi

    progress_message2 "Validating Policy file..."

    validate_policy

    progress_message2 "Determining Hosts in Zones..."

    determine_interfaces
    determine_hosts

    if [ -s $TMP_DIR/tcrules ]; then
	progress_message2 "Compiling $(find_file tcrules)..."
	process_tc_rules
    fi

    if [ "$TC_ENABLED" = Internal ]; then
	[ -n "$LIB_tc_LOADED" ] && setup_traffic_shaping
    fi

    if [ -n "$(find_hosts_by_option blacklist)" ]; then
	process_blacklist
    fi
    
    cat >&3 << __EOF__

#
# Start/Restart/Reload the firewall
#
define_firewall() {
    local restore_file
    restore_file=\$1
__EOF__

    INDENT="    "

    save_progress_message "Initializing..."

    if [ -n "$EXPORT" ]; then
	f=$(find_file modules)

	if [ "$f" != ${SHAREDIR}/modules -a -f $f ]; then
	    save_command 'echo MODULESDIR="$MODULESDIR" > ${VARDIR}/.modulesdir'
	    save_command "cat > \${VARDIR}/.modules $LEFTSHIFT EOF"
	    grep loadmodule $f | sed 's/^\s*//' >&3
	    save_command_unindented EOF
	    save_command "reload_kernel_modules < \${VARDIR}/.modules"
	else
	    save_command load_kernel_modules Yes
	fi
    else
	save_command load_kernel_modules Yes
    fi

    for interface in $ALL_INTERFACES; do
	if interface_has_option $interface norfc1918; then
	    indent >&3 << __EOF__
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 has been specified on an interface with an RFC 1918 address. Interface:$interface"
        fi
    done
fi

__EOF__
	fi
    done

    append_file init

    TERMINATOR=fatal_error

    deletechain shorewall

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

    delete_proxy_arp

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

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

    [ -n "$CLEAR_TC" ] && delete_tc

    progress_message2 "Deleting user chains..."

    save_progress_message "Deleting user chains..."

    exists_INPUT=Yes
    exists_OUTPUT=Yes
    exists_FORWARD=Yes

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then

	setpolicy INPUT ACCEPT
	setpolicy OUTPUT ACCEPT
	setpolicy FORWARD DROP

	deleteallchains

	enable_critical_hosts

	setpolicy INPUT DROP
	setpolicy OUTPUT DROP

	[ -n "$CLAMPMSS" ] && setup_mss

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    else

	setpolicy INPUT DROP
	setpolicy OUTPUT DROP
	setpolicy FORWARD DROP

	deleteallchains

	[ -n "$CLAMPMSS" ] && setup_mss

	setcontinue FORWARD
	setcontinue INPUT
	setcontinue OUTPUT
    fi

    indent >&3 << __EOF__

f=\$(find_file ipsets)

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

__EOF__

    append_file continue

    f=$(find_file routestopped)

    progress_message2 "$DOING $f ..."

    process_routestopped -A

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

    save_progress_message "Enabling Loopback and DNS Lookups"

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

    #
    # Allow DNS lookups during startup for FQDNs
    #

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

    [ -n "$LIB_accounting_LOADED" ] && setup_accounting $(find_file accounting)

    createchain reject	no
    createchain dynamic no
    createchain logdrop no
    createchain logreject no
    createchain smurfs no

    log_rule ${BLACKLIST_LOGLEVEL:-info} logdrop DROP
    log_rule ${BLACKLIST_LOGLEVEL:-info} logreject REJECT

    run_iptables -A logdrop   -j DROP
    run_iptables -A logreject -j reject

    indent >&3 << __EOF__

if [ -f \${VARDIR}/save ]; then
    progress_message2 "Setting up dynamic rules..."
    rangematch='source IP range'
    while read target ignore1 ignore2 address ignore3 rest; do
        case \$target in
            DROP|reject|logdrop|logreject)
                case \$rest in
                    \$rangematch*)
                        run_iptables -A dynamic -m iprange --src-range \${rest#source IP range} -j \$target
                        ;;
                    *)
                        if [ -z "\$rest" ]; then
                            run_iptables -A dynamic -s \$address -j \$target
                        else
                            error_message "WARNING: Unable to restore dynamic rule \"\$target \$ignore1 \$ignore2 \$address \$ignore3 \$rest\""
                        fi
                        ;;
                esac
                ;;
        esac
    done < \${VARDIR}/save
fi
__EOF__

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

    progress_message2 "Creating Interface Chains..."

    save_progress_message "Creating Interface Chains..."

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

	createchain $(out_chain $interface) no
    done

    if [ -n "$LIB_proxyarp_LOADED" ]; then
	progress_message2 "$DOING Proxy ARP"
	setup_proxy_arp
    else
	> $STATEDIR/proxyarp
    fi

    #
    # [re]-Establish routing
    #
    if [ -s $TMP_DIR/providers ]; then
	setup_providers $(find_file providers)
	[ -n "$ROUTEMARK_INTERFACES" ] && setup_route_marking
    else
	save_command
	save_command undo_routing
	save_command restore_default_route
    fi

	
    if [ -s $TMP_DIR/nat ]; then
	progress_message2 "$DOING NAT..."
	setup_nat
    else
	> $STATEDIR/nat
    fi

    if [ -s $TMP_DIR/netmap ]; then
	progress_message2 "$DOING NETMAP..."
	setup_netmap
    fi

    progress_message2 "$DOING Common Rules"
    add_common_rules

    save_progress_message "Setting up SYN Flood Protection..."

    setup_syn_flood_chains

    setup_ipsec

    maclist_hosts=$(find_hosts_by_option maclist)

    if [ -n "$maclist_hosts" ]; then
	save_progress_message "Setting up MAC Filtration -- Phase 1..."
	setup_mac_lists 1
    fi

    progress_message2 "$DOING $(find_file rules)..."
    save_progress_message "Setting up Rules..."
    process_rules

    if [ -s $TMP_DIR/tunnels ]; then
	tunnels=$(find_file tunnels)
	progress_message2 "$DOING $tunnels..."
	save_progress_message "Setting up Tunnels..."
	setup_tunnels $tunnels
    fi

    if [ -n "$DEFAULT_MACROS" ]; then
	progress_message2 "$DOING default macros..."
	save_progress_message "Creating default macro chains..."
	for macro in $DEFAULT_MACROS; do
	    process_default_macro $macro
	done
    fi

    if [ -n "$USEDACTIONS" ]; then
	save_progress_message "Setting up Actions..."

	progress_message2 "$DOING Actions...";
	[ -n "$USE_ACTIONS" ] && process_actions2
	process_actions3
    fi

    if [ -n "$maclist_hosts" ]; then
	save_progress_message "Setting up MAC Filtration -- Phase 2..."
	setup_mac_lists 2
    fi

    save_progress_message "Applying Policies..."

    progress_message2 "$DOING $(find_file policy)...";   apply_policy_rules

    if [ -s $TMP_DIR/masq ]; then
	setup_masq $(find_file masq)
    fi

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

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

	setup_tc
    fi

    progress_message2 "$DOING Rule Activation..."
    save_progress_message "Activating Rules..."
    activate_rules

    for file in chains nat proxyarp zones; do
	save_command "cat > \${VARDIR}/$file $LEFTSHIFT __EOF__"
	cat $STATEDIR/$file >&3
	save_command_unindented __EOF__
    done

    if [ -n "$ALIASES_TO_ADD" ]; then
	save_command add_ip_aliases $ALIASES_TO_ADD
    fi

    cat >&3 << __EOF__

    if [ \$COMMAND = restore ]; then
	iptables-restore < \$restore_file
    fi

__EOF__
    setup_forwarding
    save_command "date > \${VARDIR}/restarted"

    append_file start

    if [ -n "$DELAYBLACKLISTLOAD" -a -s ${TMP_DIR}/blacklist ]; then
	save_command
	save_command progress_message2 \"Loading Black List...\"
	save_command load_blacklist
	save_command
    fi

    createchain shorewall no

    save_command set_state "Started"

    append_file started

    cat >&3 << __EOF__

    cp -f \$(my_pathname) \${VARDIR}/.restore

    case \$COMMAND in
	start)
	    logger -p kern.info "\$PRODUCT started"
	    ;;
	restart)
	    logger -p kern.info "\$PRODUCT restarted"
	    ;;
	restore)
	    logger -p kern.info "\$PRODUCT restored"
	    ;;
    esac

}

#
# Silently define Firewall and ignore errors
#
restore_firewall()
{
    iptables_save_file=\${VARDIR}/\$(basename \$0)-iptables

    fatal_error()
    {
	echo "   ERROR: \$@" >&2
    }

    startup_error() # \$@ = Error Message
    {
	echo "   ERROR: \$@" >&2
    }

    run_iptables() { return 0; }

    VERBOSE=-1 # The progress messages don't make sense without iptables

    IPTABLES=run_iptables

    if [ -f \$iptables_save_file ]; then
	{
	    define_firewall \$iptables_save_file
	}
    else
	fatal_error "\$iptables_save_file does not exist"
	exit 2
    fi
}

__EOF__

    compile_refresh_firewall

    exec 3>&-

    if [ -n "$checking" ]; then
	progress_message3 "Shorewall configuration verified"
    else
	INDENT=
	mycat ${SHELLSHAREDIR}/prog.header $OUTPUT ${SHELLSHAREDIR}/prog.footer > $outfile
	chmod 700 $outfile
	if [ -n "$EXPORT" ]; then
	    exec 3>${outfile}.conf
	    cat >&3 << __EOF__
#
# Shorewall auxiliary configuration file created by Shorewall version $VERSION - $(date)
#
__EOF__
	    for option in VERBOSITY LOGFILE LOGFORMAT IPTABLES PATH SHOREWALL_SHELL SUBSYSLOCK RESTOREFILE LOCKFILE SAVE_IPSETS; do
		conditionally_add_option $option
	    done

	    conditionally_add_option1 TC_ENABLED

	    exec 3>&-
	fi

	progress_message3 "Shorewall configuration compiled to $(resolve_file $outfile)"
	rm -f $OUTPUT
    fi

    rm -rf $TMP_DIR

}

#
# Give Usage Information
#
usage() {
    echo "Usage: $0 [debug] check|compile <filename>}"
    exit 1
}

#
# E X E C U T I O N    B E G I N S   H E R E
#
#
# Start trace if first arg is "debug" or "trace"
#
[ $# -gt 1 ] && [ "x$1" = xdebug -o "x$1" = xtrace ] && { set -x ; shift ; } 

NOLOCK=

[ $# -gt 1 ] && [ "$1" = "nolock" ] && { NOLOCK=Yes; shift ; }

trap "exit 2" 1 2 3 4 5 6 9

SHAREDIR=/usr/share/shorewall
VARDIR=/var/lib/shorewall
[ -z "$EXPORT" ] && CONFDIR=/etc/shorewall || CONFDIR=${SHAREDIR}/configfiles

[ -n "${VERBOSE:=2}" ]

for library in lib.base lib.config; do
    FUNCTIONS=${SHAREDIR}/${library}

    if [ -f $FUNCTIONS ]; then
	[ $VERBOSE -ge 2 ] && echo "Loading $FUNCTIONS..."
	. $FUNCTIONS
    else
	fatal_error "Installation Error: $FUNCTIONS does not exist!"
    fi
done

VERSION=$(cat $SHELLSHAREDIR/version)

[ "$SHOREWALL_LIBVERSION"    -eq $BASE_VERSION ]   || fatal_error "Shorewall-shell $VERSION requires Shorewall-common lib.base version $BASE_VERSION_PRINTABLE"
[ "$SHOREWALL_CONFIGVERSION" -eq $CONFIG_VERSION ] || fatal_error "Shorewall-shell $VERSION requires Shorewall-common lib.config version $CONFIG_VERSION_PRINTABLE"

PROGRAM=compiler

COMMAND="$1"

case "$COMMAND" in

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

    compile)
	[ $# -ne 2 ] && usage
	do_initialize
	compile_firewall $2
	;;

    call)
	#
	# Undocumented way to call functions in ${SHAREDIR}/compiler directly
	#
	shift
	do_initialize
	EMPTY=
	$@
	;;

    *)
	usage
	;;

esac