#!/bin/bash
#
# SuSEfirewall2 - stateful packetfilter rules generator
# Copyright (C) 2000-2002 Marc Heuse <marc@suse.de>
# Copyright (C) 2003,2004 SUSE Linux AG
# Copyright (C) 2005-2011 SUSE LINUX Products GmbH
#
# Author:     Marc Heuse
# Maintainer: Ludwig Nussel
# 
# Please send feedback via http://www.suse.de/feedback
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 2 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#
# For all those fellow experts out there: yes I know that this is NOT a 
# firewall setup but a simple (no, not simple, it tries actually to be
# clever) packet filter. But if we would call this "SuSEpacketfilter",
# only a few user would install it, hence general security would be bad.
#
###########################################################################
#                                                                         #
# The configuration file for this firewall script is                      #
# /etc/sysconfig/SuSEfirewall2                                            #
#                                                                         #
# Please make only modifications to this script if you know what you      #
# are doing! A small explanation of the setup can be found in             #
# /usr/share/doc/packages/SuSEfirewall2/README                            #
#                                                                         #
# For new-user help concerning configuring this firewall, take a look at  #
# the configuration file /etc/sysconfig/SuSEfirewall2 - it tells          #
# you all                                                                 #
# (if not: sorry, but configuring a packet filter/screening router is NOT #
# trivial - you must know what you are doing and what it actually does!)  #
#                                                                         #
###########################################################################

help() {
cat <<EOF
SuSEfirewall2 3.6, Copyright (C) 2005  SUSE LINUX Products GmbH

stateful packet filter rules generator for iptables.

$0 start|test|debug [file FILENAME]
$0 basic|stop|close|status|help
$0 open ZONE TYPE services...
$0 on|off

Options:
  start	  generate and load the firewall filter rules from 
          /etc/sysconfig/SuSEfirewall2
  stop    unload all filter rules
  close   no incoming network traffic except bootp+ping (for boot security)
  basic   set basic filter rules that drop all incoming access
  test    generate and load the filter rules but do not drop any packet but log
          to syslog anything which *would* be denied
  status  print the output of "iptables -nvL"
  debug   print the iptables command to stdout instead of executing them
  log     show SuSEfirewall2 related syslog messages in a better readable format
  help    this output
  open    open the specified services in the specified zone. You need to
          restart SuSEfirewall2 for changes to take effect.
  on      add SuSEfirewall2 initscripts to boot process and start
  off     remove SuSEfirwall2 initscripts from boot process and stop

  file FILENAME  same as "start" but load alternate config file FILENAME

Calling $0 without any option is the same as the "start" option.
The "file FILENAME" option may be used with the start, test and debug options.
EOF
    exit 0
}

removefiles=()
declare -a  removefiles
removeonexit()
{
    removefiles[${#removefiles[@]}]="$1"
}

cleanup()
{
    local file
    for file in "${removefiles[@]}"; do
	    rm -f "$file"
    done
}
trap cleanup EXIT

quiet=''
syslog()
{
    local pri="-p auth.err"
    local dashs='-s'
    if [ "$1" = '0' ]; then
	pri="-p auth.info"
	if [ -n "$quiet" ]; then
	    dashs=''
	fi
    elif [ "$1" = '-1' ]; then
	pri="-p auth.warn"
    fi
    shift
    /bin/logger $dashs $pri -t SuSEfirewall2 "$*"
}

message()
{
    syslog 0 "$*"
}

warning()
{
    syslog -1 "Warning:" "$*"
}

deprecated()
{
    warning "$@ is deprecated and will likely be removed in the future."
    warning "If you think it should be kept please report your use case at"
    warning "https://features.opensuse.org/"
}

error()
{
    syslog -2 "Error:" "$*"
}

die()
{
    local ret=1
    trap EXIT
    cleanup
    case "$1" in
	[0-9]) ret="$1"; shift ;;
    esac
    [ -n "$*" ] && syslog "$ret" "$*"
    exit $ret
}

need()
{
    . "$SCRIPTSDIR/SuSEfirewall2-$1"
}

set_defaults()
{
    local dir file line var val
    for dir in "${DEFAULTSDIR[@]}"; do
	for file in "$dir"/*.cfg; do
	    test -r "$file" || continue
	    # we can't just source it as this would override config settings
	    while read line; do
		[ "$line" = "${line#\#}" ] || continue
		var="${line%%=*}"
		[ "$var" = ${var//[^A-Za-z0-9]/_} ] || continue
		eval val=\"\$$var\"
		if [ -z "$val" ]; then
		    eval "$line"
		fi
	    done < "$file"
	done
    done
}

CONFIGURATIONSDIR_0="/etc/sysconfig/SuSEfirewall2.d/services"
CONFIGURATIONSDIR_1="/usr/share/SuSEfirewall2/services"
DEFAULTSDIR=("/etc/sysconfig/SuSEfirewall2.d/defaults" "/usr/share/SuSEfirewall2/defaults")
SCRIPTSDIR="/etc/sysconfig/scripts"
FWCONFIG="/etc/sysconfig/SuSEfirewall2"
LOCKFILE="/run/SuSEfirewall2.pid"
BOOTLOCKFILE="/run/SuSEfirewall2.booting"
STATUSDIR="/var/run/SuSEfirewall2"
FW_CUSTOMRULES=""

FW_ZONE_DEFAULT=""
USE_IPTABLES_BATCH=''

# whether ip6tables supports state matching
IP6TABLES_HAVE_STATE=1

# whether ip6tables support REJECT
IP6TABLES_HAVE_REJECT=1

ACTION="start"
MODE="standard"
INITSCRIPTS="" # on|off
needconfig=
needlock=1
create_bootlock=0
remove_bootlock=0

# prevent double log entries when called from ip-up
if test -x /usr/bin/tty && ! /usr/bin/tty -s; then
    quiet=1
fi

getopttmp=`/usr/bin/getopt -o hq --long help,scriptsdir:,batch,nobatch,file:,debug,test,bootlock,bootunlock,quiet \
     -n 'SuSEfirewall2' -- "$@"`

[ $? != 0 ] && die 1 "getopt error"

eval set -- "$getopttmp"

while true ; do
        case "$1" in
		--file) FWCONFIG="$2"; shift 2 ;;
		--batch) USE_IPTABLES_BATCH='yes'; shift ;;
		--nobatch) USE_IPTABLES_BATCH='no'; shift ;;
                --scriptsdir) SCRIPTSDIR="$2" ; shift 2 ;;
                --test) MODE="test" ; shift ;;
                --debug) MODE="debug"; needlock=0 ; shift ;;
                --bootlock) create_bootlock=1 ; shift ;;
                --bootunlock) remove_bootlock=1 ; shift ;;
                -h|--help) help ; shift ;;
                -q|--quiet) quiet=1 ; shift ;;
                --) shift ; break ;;
                *) die 1 "getopt error"; ;;
        esac
done

case "$1" in
    start|"") needconfig=1 ;;
    test) MODE="test" ;;
    debug) MODE="debug"; needlock=0 ;;
    easy|novice|basic) ACTION="basic" ;;
    stop|halt|down|shutdown) ACTION="stop"; needconfig=1 ;;
    close) ACTION="close" ;;
    status) ACTION="status"; USE_IPTABLES_BATCH='no'; needlock=0 ;;
    open) ACTION="open"; USE_IPTABLES_BATCH='no'; needlock=0 ;;
    log) ACTION="showlog"; USE_IPTABLES_BATCH='no' ;;
    bootlock) ACTION="bootlock"; create_bootlock=1; needlock=0 ;;
    bootunlock) ACTION="bootunlock"; remove_bootlock=1; needlock=0 ;;
    help) help ;;
    off) ACTION="stop"; needconfig=1; INITSCRIPTS="off" ;;
    on) ACTION="start"; needconfig=1; INITSCRIPTS="on" ;;
    boot_init) ACTION="init"; create_bootlock=1 ;;
    boot_setup) ACTION="start"; remove_bootlock=1 ;;
    systemd_stop) ACTION="$1"; needconfig=1 ;;
    *) help ;;
esac
shift

if [ "$ACTION" = "systemd_stop" ]; then
	# XXX: find a better way to check whether shutdown is in progress
	if systemctl --no-pager --full --all  list-units | grep -q 'basic\.target.*active.*stop'; then
		die 0 "Not unloading firewall rules at system shutdown"
	else
		ACTION="stop"
	fi
fi


case "$ACTION" in
    start|stop)
	while [ $# -gt 0 ]; do
	    case "$1" in
		file) FWCONFIG="$2"; shift 2 ;;
		force|batch) shift ;;  # unused, for compatability
		*) echo "invalid option: $1"; exit 1 ;;
	    esac
	done
	;;
esac

test -e /etc/sysconfig/network/config && . /etc/sysconfig/network/config

if [ -r "$FWCONFIG" ]; then
    . "$FWCONFIG"
elif [ -n "$needconfig" ]; then
    die 6 " Can not read $FWCONFIG"
fi

if [ "$ACTION" = "init" ]; then
    if [ "$FW_BOOT_FULL_INIT" = 'yes' ]; then
	ACTION='start'
    else
	ACTION='close'
    fi
fi

set_defaults

if [ -z "$USE_IPTABLES_BATCH" ]; then
    case "$FW_USE_IPTABLES_BATCH" in
	no) USE_IPTABLES_BATCH='' ;;
	yes) USE_IPTABLES_BATCH='yes' ;;
	''|auto) USE_IPTABLES_BATCH='auto' ;;
    esac
fi

if [ "$FW_ZONE_DEFAULT" = 'yes' -o "$FW_ZONE_DEFAULT" = 'auto' ]; then
    FW_ZONE_DEFAULT=''
fi

if [ -n "$USE_IPTABLES_BATCH" -a "$USE_IPTABLES_BATCH" != 'no' ]; then
    need batch
    check_iptables_batch
fi

[ "$USE_IPTABLES_BATCH" = 'no' ] && USE_IPTABLES_BATCH=''

### Definitions - fixed
unset ${!LC_*} ${!RC_LC_*} LANGUAGE RC_LANG
export LANG=en_US

export PATH="/sbin:/bin:/usr/sbin:/usr/bin"

hwdesc2iface=/etc/sysconfig/network/scripts/hwdesc2iface
modinfo="/sbin/modinfo"
TC="/usr/sbin/tc"
IPTABLES_BIN="/usr/sbin/iptables"
IP6TABLES_BIN="/usr/sbin/ip6tables"
if [ "$MODE" = debug ]; then
    IPTABLES="iptables"
    iptables()
    {
	echo iptables "$@"
    }
    IP6TABLES="ip6tables"
    ip6tables()
    {
	echo ip6tables "$@"
    }
    TC="tc"
    tc()
    {
	echo tc "$@"
    }
    modprobe()
    {
	echo modprobe "$@"
    }
    syslog()
    {
	echo "# <$1> ${*:2}"
    }

    ### ipv6 checks
    case "$FW_IPv6" in
	    drop|reject) IP6TABLES_HAVE_STATE=0 ;;
	    no) IP6TABLES=":" ;;
	    *) FW_IPv6="" ;;
    esac
else
    IPTABLES="$IPTABLES_BIN"
    IP6TABLES="$IP6TABLES_BIN"

    ### ipv6 checks
    case "$FW_IPv6" in
	    drop|reject) IP6TABLES_HAVE_STATE=0 ;;
	    no) IP6TABLES=":" ;;
	    *) FW_IPv6="" ;;
    esac

    ####

    if [ -n "$USE_IPTABLES_BATCH" ]; then
	IPTABLES=iptables
	[ "$IP6TABLES" != ":" ] && IP6TABLES=ip6tables
    fi
fi

### jump targets
ACCEPT="ACCEPT"
DROP="DROP"
REJECT="reject_func"
[ "$FW_REJECT" = yes ] && DROP="reject_func"

# fwmarks

mark_redir=1

# ipsec

ipsec_chain=""
IPSEC_INPUT_MATCH="-m policy --dir in --pol ipsec --proto esp"
IPSEC_OUTPUT_MATCH="-m policy --dir out --pol ipsec --proto esp"

### zones

all_zones="int ext dmz"
forward_zones=
input_zones=

#### constants

safe_icmp_replies="echo-reply destination-unreachable time-exceeded parameter-problem timestamp-reply address-mask-reply protocol-unreachable redirect"
safe_icmpv6_replies="echo-reply destination-unreachable packet-too-big time-exceeded parameter-problem"
stateless_icmpv6_types="router-solicitation router-advertisement neighbour-solicitation neighbour-advertisement redirect 130"

###############

check_ip6tables_support()
{
    [ "$IP6TABLES" != ':' ] || return

    # Do we have a kernel with IPv6 enabled?
    if $IP6TABLES_BIN -nvL >/dev/null 2>&1; then
	if ! $IP6TABLES_BIN -m conntrack --help >/dev/null; then
	    warning "ip6tables does not support state matching. Extended IPv6 support disabled."
	    IP6TABLES_HAVE_STATE=0
	    # reject incoming packets if not specified otherwise
	    [ "$FW_IPv6" != 'no' -a "$FW_IPv6" != 'drop' ] && FW_IPv6='reject'
	fi
    else
	IP6TABLES=:
    fi

# assume kernel just supports this nowadays
#    $modinfo ip6t_REJECT >/dev/null 2>&1 || IP6TABLES_HAVE_REJECT=0

#    if [ \( "$FW_REJECT" = "yes" -o "$FW_IPv6" = "reject" \) \
#	    -a "$IP6TABLES_HAVE_REJECT" != 1 ]; then
#	    warning "Kernel lacks support for IPv6 REJECT target! Using DROP for IPv6 instead."
#    fi
}

is_running()
{
	test -e /proc/net/ip_tables_names && $IPTABLES_BIN -nL reject_func >/dev/null 2>&1
}

parse_logging()
{
    if [ -z "$FW_LOG_LIMIT" ]; then
	FW_LOG_LIMIT="-m limit --limit 3/minute"
    elif [ "$FW_LOG_LIMIT" = 'no' ]; then
	FW_LOG_LIMIT=''
    else
	FW_LOG_LIMIT="-m limit --limit $FW_LOG_LIMIT"
    fi

    ### logging setup
    if [ -z "$FW_LOG" ]; then
	LOG='--log-level warning --log-tcp-options --log-ip-options --log-prefix SFW2'
    else
	LOG="$FW_LOG"
    fi

    case "$LOG" in
	-j\ *) ;;
	*) LOG="-j LOG $LOG" ;;
    esac

    LDC=''  # log drop critical
    LDA=''  # log drop all
    LDAC='' # log drop all or critical
    LAC=''  # log accept critical
    LAA=''  # log accept all
    LAAC='' # log accept all or critical

    # 'critical' will be unset if 'all' is set since 'critical' might be a special case of 'all'.
    if [ "$FW_LOG_DROP_ALL" != yes ]; then
	LDA=":"
	if [ "$FW_LOG_DROP_CRIT" = no ]; then
	    LDC=":"
	    LDAC=":"
	fi
    else
	LDC=':'
    fi

    if [ "$FW_LOG_ACCEPT_ALL" != yes ]; then
	LAA=":"
	if [ "$FW_LOG_ACCEPT_CRIT" != yes ]; then
	    LAC=":"
	    LAAC=":"
	fi
    else 
	LAC=':'
    fi

    [ "$LAA" = ':' ] && LOG="$FW_LOG_LIMIT $LOG"
}

### Functions
function setproc()
{
    [ -z "$2" -o ! -w "$2" ] && return
    if [ "$MODE" != "debug" ]; then
	echo "$1" > "$2"
    else
	echo "echo \"$1\" > \"$2\""
    fi
}

# parameters: protocol port variable
# check whether $1 and $2/$3 are a valid protocol/port combination and sets global
# variables $proto, $port ($rport) and $sport ($rsport) in iptables syntax
# $4 is used for logging
# returns 1 on error, the content of $proto and $port is undefined then
check_proto_port()
{
    proto="$1"
    port="$2"
    sport="$3"
    local var="$4"
    case "$proto" in
	'')
	    if [ -z "$proto" -a -z "$port" -a -z "$sport" ]; then
		proto=''
		port=''
		rport=''
		sport=''
		rsport=''
	    else
		error "proto must not be empty in ${var}"
		return 1
	    fi
	    ;;
	_rpc_) # cannot check ports here
	    ;;
	tcp|udp)
	    if [ -n "$port" ]; then
		rport="--sport $port"
		port="--dport $port"
	    else
		port=''
		rport=''
	    fi
	    if [ -n "$sport" ]; then
		rsport="--dport $sport"
		sport="--sport $sport"
	    else
		sport=''
		rsport=''
	    fi

	    ;;
	icmp)
	    if [ -n "$port" ]; then
		port="--icmp-type $port"
		rport="$port"
	    else
		port=''
		rport=''
	    fi
	    if [ -n "$sport" ]; then
		sport="--icmp-type $sport"
		rsport="$sport"
	    else
		sport=''
		rsport=''
	    fi
	    ;;
	icmpv6)
	    if [ -n "$port" ]; then
		port="--icmpv6-type $port"
		rport="$port"
	    else
		port=''
		rport=''
	    fi
	    if [ -n "$sport" ]; then
		sport="--icmpv6-type $sport"
		rsport="$sport"
	    else
		sport=''
		rsport=''
	    fi
	    ;;
	*)
	    if [ -n "$port" ]; then
		error "port is only valid with tcp, udp or icmp in ${var}"
		return 1
	    fi
	;;
    esac
    [ -z "$proto" ] || proto="-p $proto"
    return 0
}

reset_rules() {
    local policy_input policy_output policy_forward

    policy_input=${1:-ACCEPT}
    policy_output=${2:-ACCEPT}
    policy_forward=${3:-ACCEPT}

    $IPTABLES -P INPUT $policy_input
    $IPTABLES -P OUTPUT $policy_output
    $IPTABLES -P FORWARD $policy_forward
    # yes we need cat for /proc
    local names
    if [ -r /proc/net/ip_tables_names ]; then
	names=`sort < /proc/net/ip_tables_names`
    else
	names="filter nat raw"
    fi
    for i in $names; do
	$IPTABLES -t $i -F
	$IPTABLES -t $i -X
    done
    if [ "$IP6TABLES" != ":" ]; then
	$IP6TABLES -P INPUT $policy_input
	$IP6TABLES -P OUTPUT $policy_output
	$IP6TABLES -P FORWARD $policy_forward
	if [ -r /proc/net/ip6_tables_names ]; then
	    names=`sort < /proc/net/ip6_tables_names`
	else
	    names="filter nat raw"
	fi
	for i in $names; do
	    $IP6TABLES -t $i -F
	    $IP6TABLES -t $i -X
	done
    fi
}

clear_qdisc_settings() {
    local data
    local dev
    local bandwidth
    for data in $FW_HTB_TUNE_DEV; do
        IFS="," read dev bandwidth < <(echo "$data")
        $TC qdisc del dev $dev root 2> /dev/null
    done
}

function load_modules()
{
    local i
    for i in "$@"; do
	modprobe "$i"
    done
}

function rulelog()
{
    local chain="$1"
    if [ "$chain" != "${chain#input_}" ]; then
	chain="IN${chain#input_}"
    else
	chain="FWD${chain#forward_}"
    fi
    # cut off too long strings (bnc#644527)
    echo "${chain:0:11}"
}


function allow_basic_established()
{
    # needed for dhcp and dns replies
    local iptables
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	    $LAA $iptables -A INPUT ${LOG}"-IN-ACC-EST " -m conntrack --ctstate ESTABLISHED
	    $iptables -A INPUT -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED

	    # if two hosts have a tcp connection on fixed ports and
	    # one of the hosts crashes it will send a SYN to the
	    # peer if it comes back up. The peer sends back ACK as
	    # it thinks there is already a connection established.
	    # This ACK is INVALID and must be answered with RST
	    # otherwise long timeouts may occur (#46818).
#	    $LDA $iptables -A INPUT ${LOG}"-IN-REJECT-ACK " -m conntrack --ctstate INVALID -p tcp --tcp-flags SYN,RST,ACK ACK
#	    $iptables -A INPUT -j "$REJECT" -m conntrack --ctstate INVALID -p tcp --tcp-flags SYN,RST,ACK ACK
    done

    # need to accept icmp RELATED packets (bnc#382004)
    $LAA $IPTABLES -A INPUT ${LOG}"-IN-ACC-REL " -p icmp -m conntrack --ctstate RELATED
    $IPTABLES -A INPUT -j "$ACCEPT" -p icmp -m conntrack --ctstate RELATED
    $LAA $IP6TABLES -A INPUT ${LOG}"-IN-ACC-REL " -p icmpv6 -m conntrack --ctstate RELATED
    $IP6TABLES -A INPUT -j "$ACCEPT" -p icmpv6 -m conntrack --ctstate RELATED
}

have_bridgeinterfaces()
{
    local i
    for i in /sys/class/net/*/bridge; do
	[ -e "$i" ] && return 0
    done
    return 1
}

allow_bridgetraffic()
{
    local iptables
    case "$FW_FORWARD_ALLOW_BRIDGING" in
	yes) ;;
	no)
	    return
	    ;;
	auto|'')
	    have_bridgeinterfaces || return
	;;
    esac
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	$iptables -A FORWARD -m physdev --physdev-is-bridged -j ACCEPT
    done
}

xen_forward_hack()
{
    local dev iptables

    if [ -n "$FW_FORWARD_ALWAYS_INOUT_DEV" ]; then
	warning "FW_FORWARD_ALWAYS_INOUT_DEV is deprecated and most likely not needed at all anymore"
    fi
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	for dev in $FW_FORWARD_ALWAYS_INOUT_DEV; do
	    $iptables -A FORWARD -i $dev -o $dev -j ACCEPT
	done
    done
}

function set_basic_rules()
{
    local itype

    check_ip6tables_support

    load_modules ip_tables ip_conntrack $FW_LOAD_MODULES

    if [ "$IP6TABLES" != ':' ]; then
	load_modules ip6table_filter ip6table_mangle
    fi
    
    # determine policy
    local DROP_JUMP
    if [ "$DROP" = "ACCEPT" ]; then
	DROP_JUMP="ACCEPT"
    else
	DROP_JUMP="DROP"
    fi

    reset_rules "$DROP_JUMP" ACCEPT "$DROP_JUMP"

    # loopback is always allowed
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	$iptables -A INPUT  -j "$ACCEPT" -i lo
	$iptables -A OUTPUT -j "$ACCEPT" -o lo
	if [ "$FW_LO_NOTRACK" != 'no' ]; then
	    $iptables -t raw -A PREROUTING -j CT --notrack -i lo
	    $iptables -t raw -A OUTPUT -j CT --notrack -o lo
	fi
    done

    # Special REJECT function #
    
    $IPTABLES -N reject_func
    $IPTABLES -A reject_func -p tcp -j REJECT --reject-with tcp-reset
    $IPTABLES -A reject_func -p udp -j REJECT --reject-with icmp-port-unreachable
    $IPTABLES -A reject_func        -j REJECT --reject-with icmp-proto-unreachable

    $IP6TABLES -N reject_func
    if [ "$IP6TABLES_HAVE_REJECT" = 1 ]; then
	$IP6TABLES -A reject_func -p tcp -j REJECT --reject-with tcp-reset
	$IP6TABLES -A reject_func -p udp -j REJECT --reject-with port-unreach
	$IP6TABLES -A reject_func        -j REJECT --reject-with addr-unreach # know anything better?
    fi
    # sometimes the above lines do not work despite prior checks, so add drop
    # always to be on the safe side
    $IP6TABLES -A reject_func -j DROP

    allow_bridgetraffic
    xen_forward_hack

    # workaround for ip6tables without state matching
    if [ "$IP6TABLES_HAVE_STATE" != 1 ]; then
	for itype in $stateless_icmpv6_types $safe_icmpv6_replies; do
	    $LAA $IP6TABLES -A INPUT ${LOG}"-IN-ACC-ICMP " -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A INPUT -j "$ACCEPT" -p icmpv6 --icmpv6-type $itype
	done
	if [ "$FW_ALLOW_PING_FW" != no ]; then
	    $LAA $IP6TABLES -A INPUT ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-request
	    $IP6TABLES -A INPUT -j "$ACCEPT" -p icmpv6 --icmpv6-type echo-request
	fi
	$LAA $IP6TABLES -A INPUT -p tcp --tcp-flags ACK ACK ${LOG}"-IN-ACC-TCP "
	$IP6TABLES -A INPUT -p tcp --tcp-flags ACK ACK -j "$ACCEPT"
	# Drop all until IPv6 is really supported
	$LDA $IP6TABLES -A INPUT ${LOG}"-IN-IPv6_PROHIB "
	if [ "$FW_IPv6" = "drop" -o "$IP6TABLES_HAVE_REJECT" != 1 ]; then
	    $IP6TABLES -A INPUT -j "$DROP"
	else
	    $IP6TABLES -A INPUT -j "$REJECT"
	fi
	$IP6TABLES -A OUTPUT -p icmpv6 -j "$ACCEPT"
	$IP6TABLES -A OUTPUT -p tcp -j "$ACCEPT"
	if [ "$IP6TABLES_HAVE_REJECT" != 1 -o "$FW_IPv6_REJECT_OUTGOING" = "no" ]; then
	    $IP6TABLES -A OUTPUT -j "$DROP"
	else
	    $IP6TABLES -A OUTPUT -j "$REJECT"
	fi
	IP6TABLES=":" # disable further rules
    fi

    allow_basic_established

    # Allow DHCPv6 by default. While the requests go out to multicast address, they
    # can come back from unicast hosts and we might not know them.
    $LAA $IP6TABLES -A INPUT -p udp --dport dhcpv6-client ${LOG}"-IN-DHCPv6 "
    $IP6TABLES -A INPUT -p udp --dport dhcpv6-client -j "$ACCEPT"

    # make sure basic rules get committed even if there are errors later
    [ -n "$USE_IPTABLES_BATCH" ] && iptables_batch_commitpoint
}

handle_initscripts()
{
    local i
    case "$INITSCRIPTS" in
	on)
	    for i in SuSEfirewall2_init SuSEfirewall2; do
		/bin/systemctl enable $i.service
	    done
	    ;;
	off)
	    for i in SuSEfirewall2 SuSEfirewall2_init; do
		/bin/systemctl disable $i.service
	    done
	    ;;
    esac
}

setlock()
{
    if [ "$remove_bootlock" -ne 0 ]; then
	rm -f "$BOOTLOCKFILE"
    fi
    ### Locking mechanism
    if [ "$needlock" -ne 0 ]; then
	if [ -e "$BOOTLOCKFILE" ]; then
	    die 0 "$BOOTLOCKFILE exists which means system boot in progress, exit." 
	fi
	if [ -e "$LOCKFILE" ]; then
	    read PID < $LOCKFILE
	    message "Another SuSEfirewall2 with PID $PID found, waiting ..."
	    i=0
	    while [ -e "$LOCKFILE" -a "$i" -lt 15 ]; do
		sleep 2
		i=$(( i + 1 ))
	    done
	    if [ -e "$LOCKFILE" ]; then
		message "Lockfile is still there, ignoring it ..."
		kill -TERM $PID
		sleep 2
		kill -KILL $PID
		rm -f "$LOCKFILE"
	    fi
	fi
	removeonexit "$LOCKFILE"
	set -o noclobber
	echo "$$" > $LOCKFILE || exit 1
	set +o noclobber
    fi
    if [ "$create_bootlock" -eq 1 ]; then
	touch "$BOOTLOCKFILE"
    fi
}

##
## function definitions for full featured mode
##

# Provide empty functions for transparent hook support for customised rules
fw_custom_after_chain_creation() { true; }
fw_custom_before_antispoofing() { true; }
fw_custom_after_antispoofing() { true; }
fw_custom_before_port_handling() { true; }
fw_custom_before_masq() { true; }
fw_custom_before_denyall() { true; }
fw_custom_after_finished() { true; }

evaluateinterfaces()
{
    local devs=""
    local var dev
    local zone="$1"
    var="FW_DEV_`cibiz $zone`"
    eval set -- \$$var
    for dev in "$@" ; do
	if [ "$dev" = 'any' ]; then
	    if [ -n "$FW_ZONE_DEFAULT" ]; then
		error "zone '$FW_ZONE_DEFAULT' is already default, ignoring 'any' in '$var'"
	    else
		FW_ZONE_DEFAULT="$zone"
	    fi
	    continue
	elif [ "$dev" = 'auto' ]; then
	    warning "ignoring deprecated interface 'auto' in $var"
	    continue
	fi
	case "$dev" in *:*) continue; ;; esac

	devs="$devs $dev"
    done
    set -- $devs
    eval FW_DEV_$zone="\$*"
}

#sets iface_$name=$zone
check_interfaces_unique()
{
    local zone devs d z
    for zone in $all_zones; do
	eval devs="\$FW_DEV_$zone"
	for d in $devs; do
	    [ -z "$d" ] && continue
	    d=${d//[^A-Za-z0-9]/_}
	    eval z=\${iface_$d}
	    if [ -z "$z" ]; then
		eval iface_$d=$zone
	    else
		error "$d already in zone '$z' but also configured for '$zone'"
	    fi
	done
    done
}

source_config_for_iface()
{
    local iface="$1"
    local dir=/etc/sysconfig/network
    if [ -x /sbin/getcfg ] ; then
	eval `/sbin/getcfg -d $dir -f ifcfg- -- $iface 2>/dev/null`
	cfg="$HWD_CONFIG_0"
    fi
    if [ -z "$cfg" ]; then
	cfg=$iface
    fi
    . $dir/ifcfg-$cfg 2>/dev/null
}

check_iface_override()
{
    local iface="$1"
    local f="$STATUSDIR/override/interfaces/$iface/zone"
    local z dummy
    test -e "$f" || return 0
    read z dummy < "$f" || return 0
    echo "$z"
}

autodetect_interfaces()
{
    local d z
    local have_override=''
    [ "${FW_AUTODETECT_INTERFACES:-yes}" = 'yes' ] || return
    set -- /sys/class/net/*
    for d in "$@"; do
	test -d "$d" || continue
	d="${d#/sys/class/net/}"
	[ -z "$d" -o "$d" = 'lo' -o "$d" = 'sit0' ] && continue
	d=${d//[^A-Za-z0-9]/_}
	unset z
	if [ "$FW_RUNTIME_OVERRIDE" != 'no' ]; then
	    z=`check_iface_override $d`
	    if [ -n "$z" ]; then
		eval iface_$d=$z
		if eval [ -n "\"\$zone_$z\"" ]; then
		    message "runtime zone override '$z' for interface '$d'"
		    have_override=1
		    continue
		else
		    error "invalid zone '$z' as override for interface '$d'"
		fi
	    fi
	fi
	eval z=\${iface_$d}
	[ -n "$z" ] && continue
	eval [ -n "\"\$seen_$d\"" ] && continue
	eval local seen_$d=1
	z=`source_config_for_iface $d && echo $FW_ZONE`
	if [ -n "$z" ]; then
	    if eval [ -n "\"\$zone_$z\"" ]; then
		eval FW_DEV_$z="\"\$FW_DEV_$z \$d\""
		#" fix vim syntax
		eval iface_$d=$z
	    else
		error "invalid zone '$z' specified for interface '$d'"
	    fi
	elif [ -n "$FW_ZONE_DEFAULT" -a "$FW_ZONE_DEFAULT" != 'no' ]; then
	    z=${FW_ZONE_DEFAULT//[^A-Za-z0-9]/_}
	    message "using default zone '$z' for interface $d"
	    eval FW_DEV_$z="\"\$FW_DEV_$z \$d\""
	    #" fix vim syntax
	    eval iface_$d=$z
	else
	    warning "no firewall zone defined for interface $d"
	fi
    done

    # runtime override, we have to reconstruct FW_DEV_*
    if [ -n "$have_override" ]; then
	for z in $all_zones; do
	    eval "FW_DEV_$z=''"
	done
	for d in ${!iface_*}; do
	    eval z="\$$d"
	    eval "FW_DEV_$z=\"\$FW_DEV_$z ${d#iface_}\""
	done
    fi
}

write_interface_status()
{
    local d z
    mkdir -p "$STATUSDIR"/status/interfaces
    for d in "$STATUSDIR/status/interfaces/"*; do
	d=${d##*/}
	[ "$d" != '*' ] || break
	d=${d//[^A-Za-z0-9]/_}
	eval z="\$iface_$d"
	if [ -z "$z" ]; then
	    rm -rf "$STATUSDIR/status/interfaces/$d"
	else
	    eval local seen_$d=1
	    echo $z > "$STATUSDIR/status/interfaces/$d/.zone.new"
	    mv "$STATUSDIR/status/interfaces/$d/.zone.new" "$STATUSDIR/status/interfaces/$d/zone"
	fi
    done
    for d in ${!iface_*}; do
	eval z="\$$d"
	d=${d#iface_}
	eval [ -n "\"\$seen_$d\"" ] && continue
	mkdir "$STATUSDIR/status/interfaces/$d"
	echo $z > "$STATUSDIR/status/interfaces/$d/.zone.new"
	mv "$STATUSDIR/status/interfaces/$d/.zone.new" "$STATUSDIR/status/interfaces/$d/zone"
    done
}

write_zone_status()
{
    local v z
    mkdir -p "$STATUSDIR/status/zones"
    for z in "$STATUSDIR/status/zones/"*; do
	z=${z##*/}
	[ "$z" != '*' ] || break
	z=${z//[^A-Za-z0-9]/_}
	eval v="\$zone_$d"
	if [ -z "$v" ]; then
	    rm -rf "$STATUSDIR/status/zones/$z"
	else
	    eval local seen_$z=1
	fi
    done
    for z in $all_zones; do
	eval [ -n "\"\$seen_$z\"" ] && continue
	mkdir "$STATUSDIR/status/zones/$z"
    done
}

write_status()
{
    [ "$MODE" != "debug" ] || return
    [ "$FW_WRITE_STATUS" != "no" ] || return

    write_interface_status
    write_zone_status
}

parse_interfaces()
{
    local zone devs var

    for zone in $all_zones; do
	evaluateinterfaces $zone
    done
    if [ -z "$FW_ZONE_DEFAULT" ]; then
	FW_ZONE_DEFAULT='ext'
    fi
}

process_masq_dev()
{
    local devs=""
    local dev
    set -- $FW_MASQ_DEV
    for dev in "$@" ; do
	if [ "$dev" = 'auto' ]; then
	    warning "ignoring deprecated interface 'auto' in FW_MASQ_DEV"
	    continue
	fi
	# zone specified?
	if [ "$dev" != "${dev#zone:}" ]; then
	    dev=${dev#zone:}
	    if eval [ -n "\"\$zone_$dev\"" ]; then
		eval devs="\"\$devs \$FW_DEV_$dev\""
		continue
	    fi
	fi
	case "$dev" in *:*) continue; ;; esac

	devs="$devs $dev"
    done
    FW_MASQ_DEV="$devs"
}

load_customrules()
{
    ### Load custom rules
    if [ -n "$FW_CUSTOMRULES" ]; then
	if [ ! -r "$FW_CUSTOMRULES" ]; then
	    die 1 "Firewall custom rules file can not be read from $FW_CUSTOMRULES"
	fi
	. "$FW_CUSTOMRULES"
	message "Firewall custom rules loaded from $FW_CUSTOMRULES"
    fi
}

check_interfaces()
{
    local v
    for zone in $all_zones; do
	eval v="\$FW_DEV_$zone"
	[ -n "$v" ] && return
    done
    warning 'no interface active'
}

verify_parameters()
{
    if [ "$FW_ROUTE" = no ]; then
	if [ "$FW_MASQUERADE" = yes ]; then
	    warning 'FW_ROUTE needs to be set to yes for masquerading to work!'
	fi
	if [ "$FW_ALLOW_CLASS_ROUTING" = yes ]; then
	    warning 'FW_ROUTE needs to be set to yes for FW_ALLOW_CLASS_ROUTING to work!'
	fi
	if [ "$FW_ALLOW_PING_DMZ" = yes -o "$FW_ALLOW_PING_EXT" = yes ]; then
	    warning 'FW_ROUTE needs to be set to yes for FW_ALLOW_PING_EXT and/or FW_ALLOW_PING_DMZ to work!'
	fi
    fi

    if [ "$FW_MASQUERADE" = no ]; then
	[ -n "$FW_MASQ_NETS" -a "$FW_MASQ_NETS" != "0/0" ] && warning 'FW_MASQ_NETS needs FW_MASQUERADE set to yes to work!'
	[ -n "$FW_FORWARD_MASQ" ] && warning 'FW_FORWARD_MASQ needs FW_MASQUERADE set to yes to work!'
    fi
}

# Turn ip forwarding on if configured
switch_on_ip_forwarding()
{
    if [ "$FW_ROUTE" = yes ]; then
	setproc 1 /proc/sys/net/ipv4/ip_forward
    else
	setproc 0 /proc/sys/net/ipv4/ip_forward
    fi
}

# warn user if device to masquerade is  from a non private network
# 0/0 has to be skipped here, otherwise the spoof rules would block
# anything
verify_masq_nets()
{
    local nets net1
    for nets in $FW_MASQ_NETS ; do
	IFS=, eval set -- \$nets
	net1="$1"

	# skip 0/0
	test "$net1" = "0/0" && continue

	IFS=/ eval set -- \$net1

	[ -z "$2" ] && continue

	case "$1" in
	    10.*|172.1[6789].*|172.2[0-9].*|172.3[01].*|192.168.*) ;;
	    *) warning "The network $net1 you want to masquerade is not from a private network " \
		    'like 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16'
	esac
    done
}

#################################
# Configuring more kernel stuff #
#################################
set_proc_stuff()
{
    if [ "$FW_KERNEL_SECURITY" != no ]; then
	# kernel default 1: setproc 1 /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts 
# test "$FW_ALLOW_PING_FW" = yes || setproc 1 /proc/sys/net/ipv4/icmp_echo_ignore_all  # XXX
	# gone? setproc 1 /proc/sys/net/ipv4/ip_always_defrag  # XXX not there?
	# kernel default 1: setproc 1 /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses 
	# gone? setproc 5 /proc/sys/net/ipv4/icmp_echoreply_rate 
	# gone? setproc 5 /proc/sys/net/ipv4/icmp_destunreach_rate 
	# gone? setproc 5 /proc/sys/net/ipv4/icmp_paramprob_rate 
	# gone? setproc 6 /proc/sys/net/ipv4/icmp_timeexceed_rate 
	# 30*HZ in the kernel, setproc 20 /proc/sys/net/ipv4/ipfrag_time 
	for i in /proc/sys/net/ipv4/conf/*; do
	    setproc 1 $i/log_martians		# default is 0 in the kernel
	    #kernel default 0: setproc 0 $i/bootp_relay 
	    [ "$FW_ROUTE" != yes ] && setproc 0 $i/forwarding	# should stay
	    # kernel default 0: setproc 0 $i/proxy_arp 
	    # kernel default 1: setproc 1 $i/secure_redirects 
	    #setproc 0 $i/accept_redirects # let kernel decide this
	    setproc 0 $i/accept_source_route 	# default is 1, should stay?
	    setproc 1 $i/rp_filter 		# default is 0, should stay?
	done
	setproc 1 /proc/sys/net/ipv4/route/flush
    fi
}

# Make input/forward chains
create_chains()
{
    local iptables
    local zone target
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	for target in input forward; do
	    for zone in $all_zones; do
		$iptables -N ${target}_${zone}
	    done
	done
    done
}

# accept a packet coming from ext zone:
#  create_cond_chain ACCEPT in ext
# reject paket leaving int zone:
#  create_cond_chain REJECT out int
create_cond_chain()
{
    local iptables dir
    local zone target chain chain devs
    local check
    target="$1"
    dir="$2"
    zone="$3"
    chain="${target}_if_${dir}_${zone}"
    eval check=\"\$${chain}_created\"
    [ -z "$check" ] || return
    eval devs="\$FW_DEV_$zone"
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	$iptables -N $chain
	for dev in $devs; do
	    $iptables -A $chain -j $target -${dir:0:1} $dev
	done
    done
    eval ${chain}_created=1
}

### configurations ###

parse_configurations()
{
    local var zone configurations config

    for zone in $input_zones; do
	var="FW_CONFIGURATIONS_`cibiz $zone`"
	eval configurations="\"\$$var\""

	for config in $configurations; do
	    local TCP=''
	    local UDP=''
	    local RPC=''
	    local IP=''
	    local BROADCAST=''
	    local RELATED=''
	    local MODULES=''
	    local ACCEPT=''

	    # XXX: could use a sub shell in order to enforce use of known variables only
	    if [ ! -r $CONFIGURATIONSDIR_0/$config ] || ! . $CONFIGURATIONSDIR_0/$config; then
		if [ ! -r $CONFIGURATIONSDIR_1/$config ] || ! . $CONFIGURATIONSDIR_1/$config; then
		    warning "config '$config' not available"
		    continue
		fi
	    fi

	    for var in TCP UDP RPC IP; do
		eval [ -n \"\$$var\" ] \&\& FW_SERVICES_`cibiz $zone`_$var="\"\$FW_SERVICES_`cibiz $zone`_$var \$$var\""
	    done

	    if [ -n "$BROADCAST" ]; then
		var=FW_ALLOW_FW_BROADCAST_`cibiz $zone`
		eval allow="\"\$$var\""
		if [ "$allow" != 'yes' ]; then
		    # 'no' is ignored later anyways
		    eval $var="\"\$$var \$BROADCAST\""
		fi
	    fi
	    
	    if [ -n "$RELATED" ]; then
		eval FW_SERVICES_ACCEPT_RELATED_`cibiz $zone`="\"\$FW_SERVICES_ACCEPT_RELATED_`cibiz $zone` \$RELATED\""
	    fi

	    if [ -n "$ACCEPT" ]; then
		eval FW_SERVICES_ACCEPT_`cibiz $zone`="\"\$FW_SERVICES_ACCEPT_`cibiz $zone` \$ACCEPT\""
	    fi

	    if [ -n "$MODULES" ]; then
		eval FW_LOAD_MODULES="\"\$FW_LOAD_MODULES \$MODULES\""
	    fi

	done
    done
}

### handling of broadcasts ###

check_convert_old_broadcast()
{
    if [ -n "$FW_ALLOW_FW_BROADCAST" -o -n "$FW_IGNORE_FW_BROADCAST" ]; then
	need oldbroadcast
	convert_old_broadcast
    fi
}

drop_broadcast()
{
    local allow ignore port zone

    for zone in $input_zones; do
	eval allow="\$FW_ALLOW_FW_BROADCAST_`cibiz $zone`"
	eval ignore="\$FW_IGNORE_FW_BROADCAST_`cibiz $zone`"

	local match="-A input_$zone -m pkttype --pkt-type broadcast"

	for port in $allow; do
	    [ $port = no -o $port = yes ] && continue
	    $LAA $IPTABLES $match -p udp --dport $port ${LOG}"-ACC-BCAST${zone:0:1} "
	    $IPTABLES $match -p udp --dport $port -j "$ACCEPT"
	    $LAA $IP6TABLES $match -p udp --dport $port ${LOG}"-ACC-BCAST${zone:0:1} "
	    $IP6TABLES $match -p udp --dport $port -j "$ACCEPT"
	done

	if [ "$ignore" != yes ]; then
	    for port in $ignore; do
		[ $port = no ] && continue
		$IPTABLES $match -p udp --dport $port -j "$DROP"
		$IP6TABLES $match -p udp --dport $port -j "$DROP"
	    done

	    if [ "$allow" != 'yes' ]; then
		$LDA $IPTABLES $match ${LOG}"-DROP-BCAST${zone:0:1} "
		$LDA $IP6TABLES $match ${LOG}"-DROP-BCAST${zone:0:1} "
	    fi
	fi

	if [ "$allow" != 'yes' ]; then
	    $IPTABLES $match -j "$DROP" # no need to send icmp for broadcasts
	    $IP6TABLES $match -j "$DROP" # no need to send icmp for broadcasts
	fi
    done
}

### zones ###

parse_zones()
{
    local zone
    for zone in $FW_ZONES; do
	case $zone in
	    [Ii][Nn][Tt]|[Ee][Xx][Tt]|[Dd][Mm][Zz])
		error "FW_ZONES=$zone ignored" ;;
	    *)
		if [ "$zone" != "${zone//[^A-Za-z0-9]/_}" ]; then
		    error "ignoring invalid zone name $zone in FW_ZONES"
		else
		    all_zones="$all_zones $zone"
		fi
		;;
	esac
    done
    for zone in $all_zones; do
	eval "zone_$zone=0"
    done
}

remove_unused_zones()
{
    local zone zones devs
    for zone in $all_zones; do
	eval devs="\$FW_DEV_$zone"
	if [ -n "$devs" -o "$ipsec_chain" = $zone -o "$FW_ZONE_DEFAULT" = "$zone" ]; then
	    if [ -z "$zones" ]; then
		zones=$zone
	    else
		zones="$zones $zone"
	    fi
	fi
    done

    [ "$all_zones" != "$zones" ] && all_zones="$zones"
}

# convert if built in zone, eg ext -> EXT
cibiz()
{
    case $1 in
	int) echo INT ;;
	ext) echo EXT ;;
	dmz) echo DMZ ;;
	*)   echo "$1" ;;
    esac
}

### IPsec ###

parse_ipsec()
{
    if [ "$FW_IPSEC_TRUST" = yes ]; then
	ipsec_chain="int"
    elif [ "$FW_IPSEC_TRUST" = no ]; then
	:
    elif [ -n "$FW_IPSEC_TRUST" ]; then
	for zone in $all_zones; do
	    if [ "$FW_IPSEC_TRUST" = "$zone" ]; then
		ipsec_chain="$zone"
		break;
	    fi
	done
	if [ -z "$ipsec_chain" ]; then
	    warning "FW_IPSEC_TRUST: $FW_IPSEC_TRUST is no valid zone"
	fi
    fi
}

allow_ipsec()
{
    if [ -n "$ipsec_chain" ]; then
	$IPTABLES -A INPUT -j "input_$ipsec_chain" $IPSEC_INPUT_MATCH
	$IPTABLES -A FORWARD -j "forward_$ipsec_chain" $IPSEC_INPUT_MATCH
	$IPTABLES -A FORWARD -j "forward_$ipsec_chain" $IPSEC_OUTPUT_MATCH
    fi
}

#####################################
# Rule split up - forking to chains #
#####################################
# ipv4 and ipv6
fork_to_chains()
{
    local iptables
    local zone
    local dev
    local devs
    local var val

    for iptables in "$IPTABLES" "$IP6TABLES"; do
	for zone in $saved_input_zones; do
	    var="FW_PROTECT_FROM_`cibiz $zone`"
	    eval val="\"\$$var\""
	    if [ "$val" = 'notrack' ]; then
		# already have rules for that
		continue
	    fi
	    if [ -n "$FW_ZONE_DEFAULT" -a "$FW_ZONE_DEFAULT" = "$zone" ]; then
		# default rule will catch it
		continue
	    fi
	    eval devs="\$FW_DEV_$zone"
	    for dev in $devs; do
		$iptables -A INPUT -j input_$zone -i $dev
	    done
	done
	if [ -n "$FW_ZONE_DEFAULT" -a "$FW_ZONE_DEFAULT" != 'no' ]; then
	    $iptables -A INPUT -j "input_$FW_ZONE_DEFAULT"
	fi
	if [ "$FW_ROUTE" = yes ]; then
	    for zone in $forward_zones; do
		eval devs="\$FW_DEV_$zone"
		for dev in $devs; do
		    $iptables -A FORWARD -j forward_$zone -i $dev
		done
	    done
	fi
    done
}

# ipv4 and ipv6
finish_chains()
{
    local iptables
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	# anything which is now not in the input_* chains is evil
	$LDAC $iptables -A INPUT ${LOG}"-IN-ILL-TARGET " 
	$iptables -A INPUT -j "$DROP"

	$LDAC $iptables -A FORWARD ${LOG}"-FWD-ILL-ROUTING "
	if [ "$FW_ROUTE" = yes ]; then
	    $iptables -A FORWARD -j "$DROP" # this is an unneeded rule, but looks nice :)
	fi

	# we want to let locally generated packets out since our task is not
	# to protect the world from us, but protect us from the world ;)
	# policy is ACCEPT $iptables -A OUTPUT -j ACCEPT
    done

    # MSS stuff needs this?
    if [ "$FW_ROUTE" = yes ]; then
	$IPTABLES -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
    fi
}

# Protect the firewall from the internal network? #
protect_from_internal()
{
    local iptables zone devs dev chain
    local newzones=
    for zone in $input_zones; do

	if [ "$zone" = "int" -a "$FW_PROTECT_FROM_INTERNAL" = "no" ]; then
	    val="no"
	    warning "FW_PROTECT_FROM_INTERNAL is deprecated, use FW_PROTECT_FROM_INT instead"
	else
	    var="FW_PROTECT_FROM_`cibiz $zone`"
	    eval val="\"\$$var\""
	fi

	if [ "$val" = notrack ]; then
	    eval devs="\$FW_DEV_$zone"
	    for dev in $devs; do
		for iptables in "$IPTABLES" "$IP6TABLES"; do
		    $iptables -t raw -i $dev -I PREROUTING -j CT --notrack
		    $iptables -t raw -o $dev -I OUTPUT -j CT --notrack
		    $iptables -i $dev -I INPUT -j ACCEPT
		    $iptables -o $dev -I OUTPUT -j ACCEPT
		done
	    done
	elif [ "$val" = no ]; then
	    chain=input_$zone
	    for iptables in "$IPTABLES" "$IP6TABLES"; do
		$LAA $iptables -A $chain ${LOG}"-`rulelog $chain`-ACC-ALL " 
		$iptables -A $chain -j "$ACCEPT"
	    done
	else
	    if [ -z "$newzones" ]; then
		newzones="$zone"
	    else
		newzones="$newzones $zone"
	    fi
	fi
    done

    if [ "$input_zones" != "$newzones" ]; then 
	saved_input_zones="$input_zones" # need that for fork_to_chains
	input_zones="$newzones"
    fi
}

allow_related()
{
    local var
    local services target service proto net
    local iptables zone chain

    for zone in $input_zones; do
	chain=input_$zone
	var="FW_SERVICES_ACCEPT_RELATED_`cibiz $zone`"
	eval services="\"\$$var\""

	for service in $services; do
	    IFS=, eval set -- \$service
	    
	    net="${1:-0/0}"
	    proto="$2"
	    port="$3"
	    sport="$4"

	    iptables="$IPTABLES $IP6TABLES"
	    case "$net" in
		*:*) iptables="$IP6TABLES" ;;
		[0-9]*.*.*.*) iptables="$IPTABLES" ;;
	    esac

	    if check_proto_port "$proto" "$port" "$sport" "$var"; then
		for iptables in $iptables; do
		    $LAA $iptables -A $chain -s $net $proto $rport $rsport -m conntrack --ctstate RELATED ${LOG}"-`rulelog $chain`-REL "
		    $iptables -A $chain -s $net $proto $rport $rsport -j ACCEPT -m conntrack --ctstate RELATED
		done
	    fi
	done
    done
}

# ICMP stuff
allow_icmp()
{
    local chain dev itype zone devs
    # INPUT ICMP rules
    if [ "$FW_ALLOW_FW_SOURCEQUENCH" != "no" ]; then
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-SQUENCH "  -p icmp --icmp-type source-quench
	    $IPTABLES -A $chain -j "$ACCEPT" -p icmp --icmp-type source-quench
	done
    fi

    if [ "$FW_ALLOW_PING_FW" != no ]; then
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmp --icmp-type echo-request
	    $IPTABLES -A $chain -j "$ACCEPT" -p icmp --icmp-type echo-request

	    $LAA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-request
	    $IP6TABLES -A $chain -j "$ACCEPT" -p icmpv6 --icmpv6-type echo-request
	done
    fi

# not needed as there is a generic accept rule for ICMP RELATED packets
#    local icmp_types="$safe_icmp_replies"
#    for itype in $icmp_types; do
#	for chain in $input_zones; do
#	    chain=input_$chain
#	    $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-ICMP "  -m conntrack --ctstate ESTABLISHED,RELATED -p icmp --icmp-type $itype
#	    $IPTABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED,RELATED -p icmp --icmp-type $itype
#	done
#    done
#    icmp_types="$safe_icmpv6_replies"
#    for itype in $icmp_types; do
#	for chain in $input_zones; do
#	    chain=input_$chain
#	    $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-ICMP " -m conntrack --ctstate ESTABLISHED,RELATED  -p icmpv6 --icmpv6-type $itype
#	    $IP6TABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED,RELATED -p icmpv6 --icmpv6-type $itype
#	done
#    done
    # DROP rules for input ICMP are after trusted handling (see below)
    
    # state matching for these does not work
    icmp_types="$stateless_icmpv6_types"
    for itype in $icmp_types; do
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-ICMP " -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A $chain -j "$ACCEPT" -p icmpv6 --icmpv6-type $itype
	done
    done
    $IP6TABLES -A OUTPUT -j "$ACCEPT" -p icmpv6 # XXX: some are not catched by conntrack, should be fixed in kernel
}

allow_forward_icmp_echo()
{
    [ "$FW_ROUTE" != "yes" ] && return

    local zone chain var val

    # FORWARD ICMP rules
    for zone in $forward_zones; do
	var="FW_ALLOW_PING_`cibiz $zone`"
	eval val="\"\$$var\""

	[ "$val" != yes ] && continue

	eval devs="\$FW_DEV_$zone"

	for dev in $devs; do
	    for chain in $forward_zones; do
		[ "$chain" = "$zone" ] && continue

		chain=forward_$chain
		$LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmp --icmp-type echo-request -o $dev
		$IPTABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate NEW -p icmp --icmp-type echo-request -o $dev
		$LAA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-request -o $dev
		$IP6TABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate NEW -p icmpv6 --icmpv6-type echo-request -o $dev
	    done
	done
	chain=forward_$zone
	$LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmp --icmp-type echo-reply
	$IPTABLES -A $chain -j "$ACCEPT"  -m conntrack --ctstate ESTABLISHED -p icmp --icmp-type echo-reply
	$LAA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-reply
	$IP6TABLES -A $chain -j "$ACCEPT"  -m conntrack --ctstate ESTABLISHED -p icmpv6 --icmpv6-type echo-reply
    done
    # drop rule for forwarding chains are at the end of the forwarding rules
}

#############################
# Trusted Networks Handling #
#############################
process_trusted_nets()
{
    local nets net chain
    for nets in $FW_TRUSTED_NETS; do
	IFS=, eval set -- \$nets
	net="$1"
	proto="$2"
	port="$3"
	if [ -n "$4" ]; then
	    error "Too many arguments in FW_TRUSTED_NETS -> $nets"
	elif [ -z "$net" ]; then
	    error "network parameter of FW_TRUSTED_NETS is empty -> $nets"
	elif [ -z "$proto" -a -n "$port" ]; then
	    error "need to specify protocol for port $port in FW_TRUSTED_NETS"
	elif check_proto_port "$proto" "$port" "" 'FW_TRUSTED_NETS'; then
	    for chain in $input_zones; do # trusted networks can be on any interface ...
		chain=input_$chain
		$LAC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-TRUST " -m conntrack --ctstate NEW -s $net $proto $port
		$LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-TRUST " -s $net $proto $port
		$IPTABLES -A $chain -j "$ACCEPT"  -m conntrack --ctstate NEW,ESTABLISHED,RELATED -s $net $proto $port
	    done
	fi
    done
}

# determine port numbers of rpc services and generate a suitable iptables
# parameter fragment
#
# parameters: names of rpc services, e.g. ypbind mountd
rpcservicerules()
{
    perl "$SCRIPTSDIR/SuSEfirewall2-rpcinfo" "$@" 2>/dev/null
}

# parameters: REJECT|DROP
reject_or_drop_services()
{
    local action="$1"
    local var
    local services target service proto net port
    local iptables zone chain

    eval target=\$$action

    for zone in $input_zones; do
	chain=input_$zone
	var="FW_SERVICES_${action}_`cibiz $zone`"
	eval services="\"\$$var\""

	for service in $services; do
	    IFS=, eval set -- \$service

	    net="${1:-0/0}"
	    proto="$2"
	    port="$3"
	    sport="$4"

	    iptables="$IPTABLES $IP6TABLES"
	    case "$net" in
		*:*) iptables="$IP6TABLES" ;;
		[0-9]*.*.*.*) iptables="$IPTABLES" ;;
	    esac

	    if [ "$proto" = "_rpc_" ]; then
		rpcservicerules $service | while read ARG; do
		    $LDC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-$action " -m conntrack --ctstate NEW $ARG
		    $IPTABLES -A $chain -j "$target" $ARG
		done
	    elif check_proto_port "$proto" "$port" "$sport" "$var"; then
		for iptables in $iptables; do
		    $LDA $iptables -A $chain -s $net $proto $port $sport -m conntrack --ctstate NEW ${LOG}"-`rulelog $chain`-$action "
		    $iptables -A $chain -s $net $proto $port $sport -m conntrack --ctstate NEW -j "$target"
		done
	    fi
	done
    done
}

accept_services()
{
    local var
    local services target service proto net
    local iptables zone chain
    local ipt_recent_update ipt_recent_set ipt_recent_rcheck

    for zone in $input_zones; do
	chain=input_$zone
	var="FW_SERVICES_ACCEPT_`cibiz $zone`"
	eval services="\"\$$var\""

	for service in $services; do
	    ipt_recent_update=''
	    ipt_recent_set=''
	    ipt_recent_rcheck=''
	    IFS=, eval set -- \$service
	    if [ "$#" -lt 1 ]; then
		error "too few parameters in $var -> $service"
		continue
	    fi
	    
	    net="${1:-0/0}"
	    proto="$2"
	    port="$3"
	    sport="$4"

	    while [ "$#" -gt 4 ]; do
		case "$5" in
		    hitcount=*) ipt_recent_update="$ipt_recent_update --hitcount ${5#*=}"; shift ;;
		    blockseconds=*) ipt_recent_update="$ipt_recent_update --seconds ${5#*=}"; shift ;;
		    recentname=*)
			ipt_recent_update="$ipt_recent_update --name ${5#*=}"
			ipt_recent_set="$ipt_recent_set --name ${5#*=}"
			shift
			;;
		    *) error "unknown parameter $5 in $var -> $service"; continue 2 ;;
		esac
	    done

	    if [ -n "$ipt_recent_update" ]; then
		ipt_recent_rcheck="-m recent --rcheck$ipt_recent_update"
		ipt_recent_update="-m recent --update$ipt_recent_update"
		ipt_recent_set="-m recent --set$ipt_recent_set"
	    fi

	    iptables="$IPTABLES $IP6TABLES"
	    case "$net" in
		*:*) iptables="$IP6TABLES" ;;
		[0-9]*.*.*.*) iptables="$IPTABLES" ;;
	    esac

	    if [ "$proto" = "_rpc_" ]; then
		rpcservicerules $service | while read ARG; do
		    if [ -n "$ipt_recent_set" ]; then
			$LDC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-DROPr " $ARG -m conntrack --ctstate NEW $ipt_recent_rcheck
			$IPTABLES -A $chain -j "$DROP" $ARG -m conntrack --ctstate NEW $ipt_recent_update
		    fi
		    $LAC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC " -m conntrack --ctstate NEW $ARG
		    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC " $ARG
		    [ -n "$ipt_recent_set" ] && $IPTABLES -A $chain -j ACCEPT $ARG -m conntrack --ctstate NEW $ipt_recent_set
		    $IPTABLES -A $chain -j ACCEPT $ARG
		done
	    elif check_proto_port "$proto" "$port" "$sport" "$var"; then
		for iptables in $iptables; do
		    if [ -n "$ipt_recent_set" ]; then
			$LDC $iptables -A $chain ${LOG}"-`rulelog $chain`-DROPr " -s $net $proto $port $sport -m conntrack --ctstate NEW $ipt_recent_rcheck
			$iptables -A $chain -j "$DROP" -s $net $proto $port $sport -m conntrack --ctstate NEW $ipt_recent_update
		    fi
		    $LAC $iptables -A $chain -s $net $proto $port $sport -m conntrack --ctstate NEW ${LOG}"-`rulelog $chain`-ACC "
		    $LAA $iptables -A $chain -s $net $proto $port $sport ${LOG}"-`rulelog $chain`-ACC "
		    [ -n "$ipt_recent_set" ] && $iptables -A $chain -s $net $proto $port $sport -j ACCEPT -m conntrack --ctstate NEW $ipt_recent_set
		    $iptables -A $chain -s $net $proto $port $sport -j ACCEPT
		done
	    fi
	done
    done
}

allow_rpc_services()
{
    local zone chain ports
    for zone in $input_zones; do
	chain=input_$zone
	eval ports="\$FW_SERVICES_`cibiz $zone`_RPC"
	rpcservicerules $ports | while read ARG; do
	    $LAC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-RPC " -m conntrack --ctstate NEW $ARG
	    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-RPC " $ARG
	    $IPTABLES -A $chain -j "$ACCEPT" $ARG
	done
    done
}

allow_ip_services()
{
    local proto zone iptables protos

    for zone in $input_zones; do
	chain=input_$zone
	eval protos="\$FW_SERVICES_`cibiz $zone`_IP"
	for iptables in "$IPTABLES" "$IP6TABLES"; do
	    for proto in $protos; do
		$LAA $iptables -A $chain ${LOG}"-`rulelog $chain`-ACC-IP " -p $proto
		$iptables -A $chain -j "$ACCEPT" -p $proto
	    done
	done
    done
}

allow_tcp_services()
{
    local port zone iptables ports

    for zone in $input_zones; do
	chain=input_$zone
	eval ports="\$FW_SERVICES_`cibiz $zone`_TCP"
	for iptables in "$IPTABLES" "$IP6TABLES"; do
	    for port in $ports; do
		$LAC $iptables -A $chain ${LOG}"-`rulelog $chain`-ACC-TCP " -p tcp --dport $port --syn
		$LAA $iptables -A $chain ${LOG}"-`rulelog $chain`-ACC-TCP " -p tcp --dport $port
		$iptables -A $chain -j "$ACCEPT" -p tcp --dport $port
	    done
	done
    done
}

# UDP Stuff
allow_udp_services()
{
    local port zone iptables ports

    for zone in $input_zones; do
	chain=input_$zone
	eval ports="\$FW_SERVICES_`cibiz $zone`_UDP"
	for iptables in "$IPTABLES" "$IP6TABLES"; do
	    for port in $ports; do
		$LAA $iptables -A $chain ${LOG}"-`rulelog $chain`-ACC-UDP " -p udp --dport $port
		$iptables -A $chain -j "$ACCEPT" -p udp --dport $port
	    done
	done
    done
}

warn_highports()
{
    if [ -n "$FW_ALLOW_INCOMING_HIGHPORTS_UDP" ]; then
	warning "FW_ALLOW_INCOMING_HIGHPORTS_UDP is obsolete, ignored"
    fi
    if [ -n "$FW_ALLOW_INCOMING_HIGHPORTS_TCP" ]; then
	warning "FW_ALLOW_INCOMING_HIGHPORTS_TCP is obsolete, ignored"
    fi
}

# construct -s/-d pairs with correct negation
net2srcdst()
{
	local name="$1"
	local value=${2#\!}
	if [ -z "$value" ]; then
		echo "${name}_src="
		echo "${name}_dst="
		echo "${name}_neg="
		return
	fi
	local neg=
	if [ "$2" != "$value" ]; then
		neg='! '
		echo "${name}_neg=1"
	else
		echo "${name}_neg="
	fi
	echo "${name}_src=\"$neg-s $value\""
	echo "${name}_dst=\"$neg-d $value\""
}

# redirect packets from one port to another, opens ports in input_*
redirect_rules()
{
    local chain nets proto port1 port2
    local net1 net1_neg net1_src net1_dst
    local net2 net2_neg net2_src net2_dst
    local redirectinstalled
    for nets in $FW_REDIRECT; do
	IFS=, eval set -- \$nets

	net1="$1"
	net2="$2"
	proto="$3"
	port1="$4"
	port2="$5"
	
	if [ -n "$6" ]; then
	    error "Too many arguments in FW_REDIRECT -> $nets"
	elif [ -z "$net1" -o -z "$net2" -o -z "$proto" ]; then
	    error "Missing parameter in FW_REDIRECT -> $nets"
	elif [ "$proto" != tcp -a "$proto" != udp ]; then
	    error "FW_REDIRECT supports only tcp and udp -> $nets"
	else
	    if [ -n "$port1" ]; then
		port1="--dport $port1"
	    fi
	    if [ -n "$port2" ]; then
		port2="--to-ports $port2"
	    fi
	    eval `net2srcdst net1 "$net1"`
	    eval `net2srcdst net2 "$net2"`
	    $IPTABLES -A PREROUTING -t mangle -j MARK  -p $proto $net1_src $net2_dst $port1 --set-mark $mark_redir
	    $IPTABLES -A PREROUTING -t nat -j REDIRECT -p $proto $net1_src $net2_dst $port1 $port2
	    redirectinstalled=1
	fi
    done

    if [ -n "$redirectinstalled" ]; then
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-REDIR " -m mark --mark $mark_redir -m conntrack --ctstate NEW
	    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-REDIR " -m mark --mark $mark_redir -m conntrack --ctstate NEW,ESTABLISHED,RELATED
	    $IPTABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -m mark --mark $mark_redir
	done
    fi

}

allow_class_routing()
{
    local chain iface devs zone iptables

    if [ "$FW_ALLOW_CLASS_ROUTING" = 'yes' ]; then
	FW_ALLOW_CLASS_ROUTING="$forward_zones"
    elif [ "$FW_ALLOW_CLASS_ROUTING" = 'no' ]; then
	return
    fi
    
    # assuming that only traffic from $zone interface enter the
    # forward_$zone chain anyways, we don't need the -i parameter
    for zone in $FW_ALLOW_CLASS_ROUTING; do
	eval devs="\$FW_DEV_$zone"
	chain=forward_$zone
	for iface in $devs; do
	    for iptables in "$IPTABLES" "$IP6TABLES"; do
		$LAA $iptables -A $chain $LOG"-`rulelog $chain`-ACC-CLASS "  -o $iface
		$iptables -A $chain -j "$ACCEPT" -o $iface
	    done
	done
    done
}

allow_related_forward_icmp()
{
    local chain itype

    for chain in $forward_zones; do
	chain=forward_$chain
	for itype in $safe_icmp_replies; do
	    $LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-FWD-RELA"  -m conntrack --ctstate ESTABLISHED,RELATED -p icmp --icmp-type $itype
	    $IPTABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED,RELATED -p icmp --icmp-type $itype
	done
	for itype in $safe_icmpv6_replies; do
	    $LAA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-FWD-RELA " -m conntrack --ctstate ESTABLISHED,RELATED  -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A $chain -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED,RELATED -p icmpv6 --icmpv6-type $itype
	done
    done
}

# <source network>,<destination network>[,protocol[,port[,flags]]]
forwarding_rules()
{
    local nets net1 net2 flags more_args_in more_args_out chain iptables var services
    local zone zonein zoneout
    local target="$1"
    if [ "$target" = ACCEPT ]; then
	var="FW_FORWARD"
    else
	var="FW_FORWARD_${target}"
    fi
    eval services="\"\$$var\""
    for nets in $services; do
	IFS=, eval set -- \$nets

	net1="$1"
	net2="$2"
	proto="$3"
	port="$4"
	shift; shift; shift; shift
	rport=""
	more_args_in=
	more_args_out=
	zonein=
	zoneout=

	while [ "$#" -gt 0 ]; do
	    case "$1" in
		ipsec)
		    more_args_in="$IPSEC_INPUT_MATCH"
		    more_args_out="$IPSEC_OUTPUT_MATCH"
		    ;;
		zonein=*) zonein="${1#*=}" ;;
		zoneout=*) zoneout="${1#*=}" ;;
		*) error "unknown parameter $1 in FW_FORWARD -> $1"; net1= ;;
	    esac
	    shift
	done

	if ! check_proto_port "$proto" "$port" '' "FW_FORWARD"; then
	    continue
	fi

	if [ -n "$net1" -a -n "$net2" ]; then
	    if [ "${net1//:/_}" != "$net1" -o "${net2//:/_}" != "$net2" ]; then
		iptables=$IP6TABLES
	    else
		iptables=$IPTABLES
	    fi
	    for zone in $forward_zones; do
		chain=forward_$zone
		set -- $iptables -A $chain $proto $more_args_in
		if [ -z "$zonein" -o "$zonein" = "$zone" ]; then
		    if [ -n "$zoneout" ]; then
			create_cond_chain "$target" "out" "$zoneout"
			jt=${target}_if_out_$zoneout
		    else
			jt="$target"
		    fi
		    $LAC "$@" -s $net1 -d $net2 ${LOG}"-`rulelog $chain`-${target:0:3}-FORW " $port -m conntrack --ctstate NEW $more_args_in
		    $LAA "$@" -s $net1 -d $net2 ${LOG}"-`rulelog $chain`-${target:0:3}-FORW " $port $more_args_in
		    "$@" -s $net1 -d $net2 -j "$jt" -m conntrack --ctstate NEW,ESTABLISHED,RELATED $port
		fi
		if [ -z "$zoneout" -o "$zoneout" = "$zone" ]; then
		    if [ -n "$zonein" ]; then
			create_cond_chain "$target" "out" "$zonein"
			jt=${target}_if_out_$zonein
		    else
			jt="$target"
		    fi
		    "$@" -s $net2 -d $net1 -j "$jt" -m conntrack --ctstate ESTABLISHED,RELATED $rport
		fi
		if [ -n "$more_args_out" ]; then
		    if [ -z "$zonein" -o "$zonein" = "$zone" ]; then
			$LAC $iptables -A $chain ${LOG}"-`rulelog $chain`-${target:0:3}-FORW "  -s $net1 -d $net2 $proto $port -m conntrack --ctstate NEW $more_args_out
			$LAA $iptables -A $chain ${LOG}"-`rulelog $chain`-${target:0:3}-FORW "  -s $net1 -d $net2 $proto $port $more_args_out
		    fi
		    if [ -z "$zoneout" -o "$zoneout" = "$zone" ]; then
			$iptables -A $chain -j "$target" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -s $net1 -d $net2 $proto $port $more_args_out
			$iptables -A $chain -j "$target" -m conntrack --ctstate ESTABLISHED,RELATED -s $net2 -d $net1 $proto $rport $more_args_out
		    fi
		fi
	    done
	else
	    error "too few parameters in $var -> $nets"
	fi
     done
}

masquerading_rules()
{
    local nets proto port dev sport
    local net1 net1_neg net1_src net1_dst
    local net2 net2_neg net2_src net2_dst
    local szone dzone sdev sdevs
    local z d
    local var='FW_NOMASQ_NETS'
    for nets in $FW_NOMASQ_NETS -- $FW_MASQ_NETS; do
	if [ "$nets" = '--' ]; then # cheap hack
	    var='FW_MASQ_NETS'
	    continue
	fi
	IFS=, eval set -- \$nets

	net1="$1"
	net2="$2"
	proto="$3"
	port="$4"
	rport=""

	if [ -n "$5" ]; then
   	    error "Too many arguments in $var -> $nets"
	elif [ -z "$net1" ]; then
	    error "source network must not be empty in $var -> $nets"
	elif check_proto_port "$proto" "$port" '' "$var"; then
	    eval `net2srcdst net1 "$net1"`
	    eval `net2srcdst net2 "$net2"`
	    for dev in $FW_MASQ_DEV; do
		d=${dev//[^A-Za-z0-9]/_}
		eval z=\${iface_$d}

		if [ "$var" = "FW_NOMASQ_NETS" ]; then # cheap hack
		    $IPTABLES -A POSTROUTING -j ACCEPT -t nat $net1_src $net2_dst $proto $port -o $dev
		    continue
		fi

		for dzone in $forward_zones; do
		    dzone=forward_$dzone
		    for szone in $forward_zones; do
			[ "$z" = "$szone" ] && continue
			eval sdevs="\$FW_DEV_$szone"
			for sdev in $sdevs; do
			    [ "$sdev" = "$dev" ] && continue
			    if [ "forward_$z" != "$dzone" ]; then
				#echo "$dzone: $sdev ($szone) -> $dev ($z)"
				$LAA $IPTABLES -A $dzone ${LOG}"-`rulelog $dzone`-ACC-MASQ "  $net1_src $net2_dst $proto $port -i $sdev -o $dev
				$IPTABLES -A $dzone -j "$ACCEPT" -m conntrack --ctstate NEW,ESTABLISHED,RELATED $net1_src $net2_dst $proto $port -i $sdev -o $dev
			    else
				#echo "$dzone: $sdev ($szone) <- $dev ($z)"
				# we need to allow the replies as well
				$LAA $IPTABLES -A $dzone $net1_dst $net2_src $proto $rport -i $dev -o $sdev ${LOG}"-`rulelog $dzone`-ACC-MASQ " -m conntrack --ctstate ESTABLISHED,RELATED
				$IPTABLES -A $dzone $net1_dst $net2_src $proto $rport -i $dev -o $sdev -j "$ACCEPT" -m conntrack --ctstate ESTABLISHED,RELATED
			    fi
			done
		    done
	        done

		$IPTABLES -A POSTROUTING -j MASQUERADE -t nat $net1_src $net2_dst $proto $port -o $dev
	    done
	fi
    done
}

# <source network>,<ip to forward to>,<protocol>,<port>[,redirect port,[destination ip]]
# XXX: really stupid syntax. That one would be more obvious:
# <source network>,<destination>,<protocol>,<port>,<ip to forward to>,<redirect port>
forward_masquerading_rules()
{
    local nets proto port1 port2 lip
    local net1 net1_neg net1_src net1_dst
    local net2 net2_neg net2_src net2_dst
    for nets in $FW_FORWARD_MASQ; do
	IFS=, eval set -- \$nets

	net1="$1"
	target="$2"
	proto="$3"
	port1="$4"
	port2="$5"
	net2="$6"

	case "$target" in
	    */*|\!*|'')
		error "target must be a single host in FW_FORWARD_MASQ -> $nets"
		continue
		;;
	esac

	if [ -n "$7" ]; then
   	    error "too many arguments in FW_FORWARD_MASQ -> $nets"
	elif [ -z "$net1" ]; then
	    error "source network must not be empty in FW_FORWARD_MASQ -> $nets"
        elif [ "$proto" != tcp -a "$proto" != udp ]; then
	    error "The protocol with FW_FORWARD_MASQ must be tcp or udp -> $nets"
        elif [ -z "$port1" ]; then
	    error "Port missing in FW_FORWARD_MASQ -> $nets"
	else
	    eval `net2srcdst net1 "$net1"`
	    eval `net2srcdst net2 "$net2"`
	    eval `net2srcdst target "$target"`
	    proto="-p $proto"
	    test -z "$port2" && port2="$port1"
	    port1="--dport $port1"
	    dport2="--dport $port2"
	    port2=":${port2/:/-}"
	    for dev in $FW_MASQ_DEV; do
		$IPTABLES -A PREROUTING -j DNAT -t nat $proto $net1_src $net2_dst $port1 --to-destination ${target}${port2} -i $dev
		# to install minimal rule set we'd need to check if
		# $net1 is covered by $FW_MASQ_NETS. Not feasible in
		# bash code so just check for 0/0
		if [ "$FW_MASQ_NETS" != "0/0" ]; then
		    $IPTABLES -A POSTROUTING -j MASQUERADE -t nat $net1_src $net2_dst $proto $dport2 -o $dev
		fi
	    done
	    for chain in $forward_zones; do
		chain=forward_$chain
		$LAC $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-REVMASQ " $proto $net1_src $target_dst $dport2 -m conntrack --ctstate NEW
		$LAA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-ACC-REVMASQ " $proto $net1_src $target_dst $dport2
		$IPTABLES -A $chain -j "$ACCEPT" $proto $net1_src $target_dst $dport2
		$IPTABLES -A $chain -j "$ACCEPT" $proto $net1_dst $target_src -m conntrack --ctstate ESTABLISHED,RELATED
	    done
	fi
    done
}

# Special Logging + Deny #
drop_all()
{
    local chain
    local zone
    local drop
    local chainprefix='input_'

    for zone in $input_zones '--' $forward_zones; do

	if [ "$zone" = '--' ]; then
	    [ "$FW_ROUTE" != 'yes' ] && break
	    chainprefix='forward_'
	    continue
	fi

	chain="$chainprefix$zone"

	eval drop="\$FW_REJECT_`cibiz $zone`"
	if [ "$drop" = "yes" ]; then
	    drop="$REJECT"
	else
	    drop="$DROP"
	fi

	# log and drop multicast packets separately to not flood
	# other log targets (#155326, #538053)
	$LDA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-DROP-DEFLT " -m pkttype --pkt-type multicast
	$IPTABLES -A $chain -j "$DROP" -m pkttype --pkt-type multicast

	eval local ignore="\$FW_IGNORE_FW_BROADCAST_`cibiz $zone`"
	if [ "$ignore" != 'yes' ]; then
	    $LDA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-DROP-DEFLT " -m pkttype --pkt-type broadcast
	fi
	$IPTABLES -A $chain -j "$DROP" -m pkttype --pkt-type broadcast
	# some packet types are considered critical
	if [ -z "$LDC" ]; then
	    local log=${LOG}"-`rulelog $chain`-DROP-DEFLT"
	    $IPTABLES  -A $chain $log" " -p tcp --syn
	    $IP6TABLES -A $chain $log" " -p tcp --syn
	    $IPTABLES  -A $chain $log" " -p icmp
	    $IP6TABLES -A $chain $log" " -p icmpv6
	    $IPTABLES  -A $chain $log" " -p udp -m conntrack --ctstate NEW
	    $IP6TABLES -A $chain $log" " -p udp -m conntrack --ctstate NEW
	fi
	# log anything else
	$LDA $IPTABLES -A $chain ${LOG}"-`rulelog $chain`-DROP-DEFLT "
	$IPTABLES -A $chain -j "$drop"
	$LDA $IP6TABLES -A $chain ${LOG}"-`rulelog $chain`-DROP-DEFLT "
	$IP6TABLES -A $chain -j "$drop"
    done
}

############################################
#                                          #
# Now we begin to set the filter rules ... #
#                                          #
############################################

if [ "$ACTION" = "showlog" ]; then
    exec perl "$SCRIPTSDIR/SuSEfirewall2-showlog" "$@"
    die 1 "failed to execute $SCRIPTSDIR/SuSEfirewall2-showlog"
fi

###############

setlock

if [ "$ACTION" = "bootlock" -o "$ACTION" = "bootunlock" ]; then
    # lock file already set in setlock
    die 0
fi

if [ "$MODE" = "test" ]; then
    DROP="ACCEPT"
    REJECT="ACCEPT"
    FW_LOG_DROP_ALL=yes
    FW_LOG_DROP_CRIT=yes
    FW_LOG_ACCEPT_ALL=no
    FW_LOG_ACCEPT_CRIT=no
    warning "SuSEfirewall2 is running in TEST MODE, no packet filtering is done!"
fi

parse_logging

if [ "$ACTION" = "basic" ]; then
    # Reset the filter rules
    set_basic_rules

    $IPTABLES -A INPUT -j "$ACCEPT" -p icmp --icmp-type echo-request

    # log incoming tcp connection requests. also logging udp etc would just flood the log
    $IPTABLES -A INPUT -p tcp -m conntrack --ctstate NEW $LOG"-IN-DROP-NEW-CONNECT "

    # reject anything else
    $IPTABLES -A INPUT -j "$DROP"

    [ -n "$USE_IPTABLES_BATCH" ] && commit_iptables_batch

    die 0 "Firewall rules successfully set in simple mode"
fi

if [ "$ACTION" = "stop" ]; then
    if [ "$FW_STOP_KEEP_ROUTING_STATE" != "yes" ]; then
	setproc 0 /proc/sys/net/ipv4/ip_forward
    fi
    # Do we have a kernel with IPv6 enabled?
    $IP6TABLES_BIN -nvL >/dev/null 2>&1 || IP6TABLES=:
    reset_rules
    clear_qdisc_settings
    handle_initscripts
    rm -rf "$STATUSDIR"/{override,status}
    [ -n "$USE_IPTABLES_BATCH" ] && commit_iptables_batch
    die 0 "Firewall rules unloaded."
fi

if [ "$ACTION" = "close" ]; then
    set_basic_rules
    setproc 0 /proc/sys/net/ipv4/ip_forward
    [ -n "$USE_IPTABLES_BATCH" ] && commit_iptables_batch
    die 0 "Firewall rules set to CLOSE."
fi

if [ "$ACTION" = "status" ]; then
    if [ "$UID" != 0 ]; then
	die 2 "You need to be root to check the status"
    fi
    if ! is_running; then
	die 1 "SuSEfirewall2 not active"
    fi

    [ -z "$quiet" ] || die 0

    # yes we need cat here, while read ... does no work :-(
    for i in `sort < /proc/net/ip_tables_names`; do
	echo "### iptables $i ###"
	$IPTABLES -t $i -vnL
	echo ""
    done
    if [ "$IP6TABLES" != ":" ]; then
	for i in `sort /proc/net/ip6_tables_names`; do
	    echo "### ip6tables $i ###"
	    $IP6TABLES -t $i -vnL
	    echo ""
	done
    fi
    die 0
fi

if [ "$ACTION" = "open" ]; then
    OPENHELPER="$SCRIPTSDIR/SuSEfirewall2-open"
    exec perl -w $OPENHELPER "$@"
    die 1 "failed to execute $OPENHELPER"
fi

### main mode ###

message "Setting up rules from $FWCONFIG ..."

parse_zones
parse_interfaces
check_interfaces_unique
autodetect_interfaces
write_status
process_masq_dev

load_customrules

check_interfaces

verify_parameters
#verify_masq_nets

parse_ipsec

remove_unused_zones
[ "$FW_ROUTE" = 'yes' ] && forward_zones="$all_zones"
input_zones="$all_zones"
saved_input_zones="$input_zones" # need that for fork_to_chains

parse_configurations

# Set default rules + flush
set_basic_rules

switch_on_ip_forwarding

set_proc_stuff

create_chains

# HOOK
fw_custom_after_chain_creation

# HOOK, deprecated
fw_custom_before_antispoofing

# HOOK, deprecated
fw_custom_after_antispoofing

protect_from_internal

check_convert_old_broadcast

drop_broadcast

allow_ipsec

allow_icmp

allow_forward_icmp_echo

# HOOK
fw_custom_before_port_handling

process_trusted_nets

if [ "$FW_LEGACY_ACCEPT_FIRST" != 'yes' ]; then
    reject_or_drop_services DROP

    reject_or_drop_services REJECT
fi

allow_related

allow_ip_services

allow_tcp_services

allow_udp_services

allow_rpc_services

accept_services

if [ "$FW_LEGACY_ACCEPT_FIRST" = 'yes' ]; then
    reject_or_drop_services DROP

    reject_or_drop_services REJECT
fi

warn_highports

# HOOK
fw_custom_before_masq

redirect_rules

if [ "$FW_ROUTE" = yes ]; then

    allow_related_forward_icmp

    allow_class_routing

    forwarding_rules DROP
    forwarding_rules REJECT
    forwarding_rules ACCEPT

    if [ "$FW_MASQUERADE" = yes ]; then
	masquerading_rules
	forward_masquerading_rules
    fi
fi

# HOOK
fw_custom_before_denyall

drop_all

fork_to_chains

finish_chains

# HTB settings
if [ -n "$FW_HTB_TUNE_DEV" ]; then
    need qdisc
    do_qdisc_settings
fi

[ -n "$USE_IPTABLES_BATCH" ] && commit_iptables_batch

handle_initscripts

# HOOK
fw_custom_after_finished

# END #
die 0 "Firewall rules successfully set"

# vim: sw=4
