#!/bin/bash
#
# SuSEfirewall2 - stateful packetfilter rules generator
# Copyright (C) 2000-2002 Marc Heuse <marc@suse.de>
# Copyright (C) 2003,2004 SUSE Linux AG
#
# 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.3, Copyright (C) 2004  SUSE Linux AG

stateful packet filter rules generator for iptables.

$0 start|test|debug [file FILENAME] [batch]
$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 SuSEfirwall2 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
    logger $dashs $pri -t SuSEfirewall2 "$*"
}

message()
{
    syslog 0 "$*"
}

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

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

SCRIPTSDIR="/etc/sysconfig/scripts"
FWCONFIG="/etc/sysconfig/SuSEfirewall2"
PERSFWCONFIG="/etc/sysconfig/personal-firewall"
LOCKFILE="/var/lock/SuSEfirewall2.pid"
BOOTLOCKFILE="/var/lock/SuSEfirewall2.booting"
FW_CUSTOMRULES=""

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

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

getopttmp=`getopt -o hq --long help,scriptsdir:,batch,file:,debug,test,bootlock,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=1; shift ;;
                --scriptsdir) SCRIPTSDIR="$2" ; shift 2 ;;
                --test) MODE="test" ; shift ;;
                --debug) MODE="debug"; needlock=0 ; shift ;;
                --bootlock) create_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"; needlock=0 ;;
    open) ACTION="open"; needlock=0 ;;
    log) ACTION="showlog" ;;
    help) help ;;
    off) ACTION="stop"; needconfig=1; INITSCRIPTS="off" ;;
    on) ACTION="start"; needconfig=1; INITSCRIPTS="on" ;;
    *) help ;;
esac
shift

case "$ACTION" in
    start|stop)
	while [ $# -gt 0 ]; do
	    case "$1" in
		file) FWCONFIG="$2"; shift 2 ;;
		batch) USE_IPTABLES_BATCH=1; shift;;
		force) 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 [ -n "$USE_IPTABLES_BATCH" ]; then
    need batch
    check_iptables_batch
fi

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

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

modinfo="/sbin/modinfo"
TC="/usr/sbin/tc"
if [ "$MODE" = debug ]; then
    IPTABLES="iptables"
    iptables()
    {
	echo iptables "$@"
    }
    IP6TABLES="ip6tables"
    ip6tables()
    {
	echo ip6tables "$@"
    }
    TC="tc"
    tc()
    {
	echo tc "$@"
    }
    modprobe()
    {
	echo modprobe "$@"
    }
else
    IPTABLES="/usr/sbin/iptables"
    IP6TABLES="/usr/sbin/ip6tables"

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


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

    $modinfo ip6t_REJECT >/dev/null 2>&1 || IP6TABLES_HAVE_REJECT=0

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

    if [ -n "$USE_IPTABLES_BATCH" ]; then
	IPTABLES=iptables
	[ "$IP6TABLES" != ":" ] && IP6TABLES=ip6tables
    fi
fi
AWK="/bin/awk"
SED="/bin/sed"
IFCONFIG="/sbin/ifconfig"
NETSTAT="/bin/netstat"
SORT="/bin/sort"
LOGGER="/bin/logger"
GREP="/bin/grep"
PERL="/usr/bin/perl"

for i in "$GREP" "$AWK" "$SED" "$IFCONFIG" "$NETSTAT" "$SORT"; do
    if [ ! -x "$i" ]; then
	die 1 "$i is required for SuSEfirewall2 but it is not an executable"
    fi
done

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

# fwmarks

mark_redir=1

# ipsec

ipsec_chain=""
IPSEC_MATCH="-m policy --dir in --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"

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

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

    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
    LA=''   # log any

    # '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" = no ]; then
	    LAC=":"
	    LAAC=":"
	fi
    else 
	LAC=':'
    fi

    # LDA and LDC are : if logging is disabled so check if empty
    [ -z "$LDA" -o -z "$LDC" -o -z "$LDA" -o -z "$LAA" ] || LA=:

    [ "$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
	'')   error "proto must not be empty in ${var}"; return 1; ;;
	_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
	;;
	*)
	    if [ -n "$port" ]; then
		error "port is only valid with tcp, udp or icmp in ${var}"
		return 1
	    fi
	;;
    esac
    proto="-p $proto"
    return 0
}

function reset_rules_silent() {
    if [ "$FW_STOP_KEEP_ROUTING_STATE" != "yes" ]; then
	setproc 0 /proc/sys/net/ipv4/ip_forward
    fi
    $IPTABLES -F INPUT
    $IPTABLES -F OUTPUT
    $IPTABLES -F FORWARD
    $IPTABLES -P INPUT ACCEPT
    $IPTABLES -P OUTPUT ACCEPT
    $IPTABLES -P FORWARD ACCEPT
    $IPTABLES -F
    $IPTABLES -X
    $IPTABLES -t nat -F
    $IPTABLES -t nat -X
    $IPTABLES -t mangle -F
    $IPTABLES -t mangle -X

    $IP6TABLES -F INPUT
    $IP6TABLES -F OUTPUT
    $IP6TABLES -F FORWARD
    $IP6TABLES -P INPUT ACCEPT
    $IP6TABLES -P OUTPUT ACCEPT
    $IP6TABLES -P FORWARD ACCEPT
    $IP6TABLES -F
    $IP6TABLES -X
    $IP6TABLES -t mangle -F
    $IP6TABLES -t mangle -X
}

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 reset_rules() {
	reset_rules_silent
        clear_qdisc_settings
}

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

function rulelog()
{
    rule=$1
    case $rule in
	input_*)
		echo IN${rule#input_}
		;;
	forward_*)
		echo FWD${rule#forward_}
		;;
    esac	
}


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

	    # 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 -j LOG ${LOG}"-IN-REJECT-ACK " -m state --state INVALID -p tcp --tcp-flags SYN,RST,ACK ACK
#	    $iptables -A INPUT -j "$REJECT" -m state --state INVALID -p tcp --tcp-flags SYN,RST,ACK ACK
    done
}

function set_basic_rules()
{
    load_modules ip_tables ip_conntrack ip_conntrack_ftp ip_nat_ftp

    if [ "$FW_IPv6" != no ]; 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
    $IPTABLES -F INPUT
    $IPTABLES -F OUTPUT
    $IPTABLES -F FORWARD
    $IPTABLES -P INPUT "$DROP_JUMP"
    $IPTABLES -P OUTPUT ACCEPT
    $IPTABLES -P FORWARD "$DROP_JUMP"
    $IPTABLES -F
    $IPTABLES -X
    # Special REJECT function #
    $IPTABLES -t nat -F
    $IPTABLES -t nat -X
    $IPTABLES -t mangle -F
    $IPTABLES -t mangle -X
    $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

    # loopback is always allowed
    $IPTABLES -A INPUT  -j "$ACCEPT" -i lo
    $IPTABLES -A OUTPUT -j "$ACCEPT" -o lo

    $IP6TABLES -F INPUT
    $IP6TABLES -F OUTPUT
    $IP6TABLES -F FORWARD
    $IP6TABLES -P INPUT "$DROP_JUMP"
    $IP6TABLES -P OUTPUT ACCEPT
    $IP6TABLES -P FORWARD "$DROP_JUMP"
    $IP6TABLES -F
    $IP6TABLES -X
    # Special REJECT function #
    $IP6TABLES -t mangle -F
    $IP6TABLES -t mangle -X
    $IP6TABLES -N reject_func
    if [ "$IP6TABLES_HAVE_REJECT" != 1 ]; then
	$IP6TABLES -A reject_func -j DROP
    else
	$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

    # loopback is always allowed
    $IP6TABLES -A INPUT  -j "$ACCEPT" -i lo
    $IP6TABLES -A OUTPUT -j "$ACCEPT" -o lo

    # 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 -j LOG ${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" = yes ]; then
	    $LAA $IP6TABLES -A INPUT -j LOG ${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 -j LOG ${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 -j LOG ${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_related
}

handle_initscripts()
{
    local i
    case "$INITSCRIPTS" in
	on)
	    for i in SuSEfirewall2_init SuSEfirewall2_setup; do
		/sbin/insserv -f $i
	    done
	    ;;
	off)
	    for i in SuSEfirewall2_setup SuSEfirewall2_init; do
		/sbin/insserv -r -f $i
	    done
	    ;;
    esac
}

# set $ip and $net to list of ips and ip/prefix separated by space. set $dev to actual name of device
getdevinfo()
{
    local dev=`getcfg-interface "$1"`

    if [ "$?" -ne 0 -o -z "$dev" \
	-o  \( ! -e /proc/sys/net/ipv4/conf/"$dev" \
	-a ! -e /proc/sys/net/ipv6/conf/"$dev" \)  ]; then
	return 1
    fi

    echo "$dev"
    return 0
}

# find interface name for hardware description
function getcfg-interface()
{
    if [ -x /sbin/getcfg-interface ]; then
	/sbin/getcfg-interface "$1" || echo "$1"
    else
	echo "$1"
    fi
}


auto_detect_done=0
auto_detect_interfaces()
{
    [ "$auto_detect_done" = 1 ] && return
    local zone
    while read line; do
	set -- $line
	zone="$1"
	shift
	case "$zone" in
	    External:)
		FW_DEV_EXT_AUTO="$*"
	    ;;
	    Internal:)
		FW_DEV_INT_AUTO="$*"
	    ;;
	esac
    done < <(/bin/bash $SCRIPTSDIR/SuSEfirewall2-autointerface.sh)
    auto_detect_done=1
}

# auto interface detection
auto_detect_all_interfaces()
{
    if [ "$FW_DEV_EXT" = "auto" ]; then
	auto_detect_interfaces
	FW_DEV_EXT="$FW_DEV_EXT_AUTO"
    fi

    if [ "$FW_DEV_INT" = "auto" ]; then
	auto_detect_interfaces
	FW_DEV_INT="$FW_DEV_INT_AUTO"
    fi

    if [ "$FW_MASQ_DEV" = "auto" ]; then
	FW_MASQ_DEV="$FW_DEV_EXT"
    fi
}

setlock()
{
    ### 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
	if [ "$create_bootlock" -eq 1 ]; then
	    touch "$BOOTLOCKFILE"
	fi
	set +o noclobber
    fi
}

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

# Provide empty functions for transparent hook support for customised rules
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; }

evaluateinterfaces()
{
    local devs=""
    local dev_any=""
    local dev
    for dev in "$@" ; do
	if [ "$dev" = 'any' ]; then
	    dev_any='any'
	    continue
	fi
	dev=`getdevinfo "$dev"` || continue
	case "$dev" in *:*) continue; ;; esac

	devs="$devs $dev"
    done
    echo $dev_any $devs
}

parse_interfaces()
{
    local zone devs var

    for zone in $all_zones; do
	eval devs="\$FW_DEV_`cibiz $zone`"
	set -- `evaluateinterfaces $devs`
	if [ "$1" = 'any' ]; then
	    eval DEV_${zone}_ANY="yes"
	    shift
	fi
	eval FW_DEV_$zone="\"$*\""
    done

    set -- `evaluateinterfaces $FW_MASQ_DEV`
    [ "$1" = 'any' ] &&	shift
    FW_MASQ_DEV="$*"

}

load_customrules()
{
    ### Load custom rules
    if [ -n "$FW_CUSTOMRULES" ]; then
	if [ ! -r "$FW_CUSTOMRULES" ]; then
	    die 1 "Firewall customary rules file can not be read from $FW_CUSTOMRULES"
	fi
	. "$FW_CUSTOMRULES"
	message "Firewall customary 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
	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
	setproc 1 /proc/sys/net/ipv4/tcp_syncookies 
	setproc 1 /proc/sys/net/ipv4/ip_always_defrag  # XXX not there?
	setproc 0 /proc/sys/net/ipv4/tcp_ecn 
	setproc 1 /proc/sys/net/ipv4/icmp_ignore_bogus_error_responses 
	setproc 5 /proc/sys/net/ipv4/icmp_echoreply_rate 
	setproc 5 /proc/sys/net/ipv4/icmp_destunreach_rate 
	setproc 5 /proc/sys/net/ipv4/icmp_paramprob_rate 
	setproc 6 /proc/sys/net/ipv4/icmp_timeexceed_rate 
	setproc 20 /proc/sys/net/ipv4/ipfrag_time 
	setproc 1 /proc/sys/net/ipv4/igmp_max_memberships 
	setproc "1024 29999" /proc/sys/net/ipv4/ip_local_port_range 
	for i in /proc/sys/net/ipv4/conf/*; do
	    setproc 1 $i/log_martians 
	    setproc 0 $i/bootp_relay 
	    [ "$FW_ROUTE" != yes ] && setproc 0 $i/forwarding
	    setproc 0 $i/proxy_arp 
	    setproc 1 $i/secure_redirects 
	    #setproc 0 $i/accept_redirects # let kernel decide this
	    setproc 0 $i/accept_source_route 
	    setproc 1 $i/rp_filter 
	done
	setproc 1 /proc/sys/net/ipv4/route/flush
    fi
}

# Make input/forward chains
create_chains()
{
    local iptables
    for iptables in "$IPTABLES" "$IP6TABLES"; do
	for TARGET in input forward; do
	    for CHAIN in $all_zones; do
		$iptables -N ${TARGET}_${CHAIN}
	    done
	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`"

	[ "$allow" = yes ] && continue
	
	local match="-A input_$zone -m pkttype --pkt-type broadcast"

	for port in $allow; do
	    [ $port = no ] && continue
	    $LAA $IPTABLES $match -p udp --dport $port -j LOG ${LOG}"-ACC-BCAST${zone:0:1} "
	    $IPTABLES $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
	    done

	    $LDA $IPTABLES $match -j LOG ${LOG}"-DROP-BCAST${zone:0:1} "
	fi

	$IPTABLES $match -j DROP # no need to send icmp for broadcasts
    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" ;;
	    *) all_zones="$all_zones $zone" ;;
	esac
    done
}

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

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

create_zones_hash()
{
    local zone
    need bashhash
    for zone in $all_zones; do
	hashadd zones $zone 1
    done
}

# 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_MATCH
	$IPTABLES -A FORWARD -j "forward_$ipsec_chain" $IPSEC_MATCH
    fi
}

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

    for iptables in "$IPTABLES" "$IP6TABLES"; do
	for zone in $saved_input_zones; do
	    eval devs="\$FW_DEV_$zone"
	    for dev in $devs; do
		$iptables -A INPUT -j input_$zone -i $dev
	    done
	    eval any="\$DEV_${zone}_ANY"
	    if [ "$any" = 'yes' ]; then
		$iptables -A INPUT -j input_$zone
	    fi
	done
	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 -j LOG ${LOG}"-IN-ILL-TARGET " 
	$iptables -A INPUT -j "$DROP"

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

	$iptables -A OUTPUT -j ACCEPT -m state --state NEW,ESTABLISHED,RELATED
	$iptables -A OUTPUT -j LOG ${LOG}"-OUT-ERROR " 
	# 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 chain newzones
    for chain in $input_zones; do

	if [ "$chain" = "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 $chain`"
	    eval val="\"\$$var\""
	fi

	if [ -z "$val" -o "$val" != no ]; then
	    if [ -z "$newzones" ]; then
		newzones="$chain"
	    else
		newzones="$newzones $chain"
	    fi
	else
	    eval devs="\$FW_DEV_$zone"
	    chain=input_$chain
	    for iptables in "$IPTABLES" "$IP6TABLES"; do
		$LAA $iptables -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-ALL " 
		$iptables -A $chain -j "$ACCEPT"
	    done
	fi
    done

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

# 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 -j LOG ${LOG}"-`rulelog $chain`-ACC-SOURCEQUENCH "  -p icmp --icmp-type source-quench
	    $IPTABLES -A $chain -j "$ACCEPT" -p icmp --icmp-type source-quench
	done
    fi

    if [ "$FW_ALLOW_PING_FW" = yes ]; then
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAA $IPTABLES -A $chain -j LOG ${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 -j LOG ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-request
	    $IP6TABLES -A $chain -j "$ACCEPT" -p icmpv6 --icmpv6-type echo-request
	done
    fi
    local icmp_types="$safe_icmp_replies"
    for itype in $icmp_types; do
	for chain in $input_zones; do
	    chain=input_$chain
	    $LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-ICMP "  -m state --state ESTABLISHED,RELATED -p icmp --icmp-type $itype
	    $IPTABLES -A $chain -j "$ACCEPT" -m state --state 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
	    $LAA $IP6TABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-ICMP " -m state --state ESTABLISHED,RELATED  -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A $chain -j "$ACCEPT" -m state --state 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 -j LOG ${LOG}"-`rulelog $chain`-ACC-ICMP " -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A $chain -j "$ACCEPT" -p icmpv6 --icmpv6-type $itype
	done
    done
}

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 -j LOG ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmp --icmp-type echo-request -o $dev
		$IPTABLES -A $chain -j "$ACCEPT" -m state --state NEW -p icmp --icmp-type echo-request -o $dev
		$LAA $IP6TABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-request -o $dev
		$IP6TABLES -A $chain -j "$ACCEPT" -m state --state NEW -p icmpv6 --icmpv6-type echo-request -o $dev
	    done
	done
	chain=forward_$zone
	$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmp --icmp-type echo-reply
	$IPTABLES -A $chain -j "$ACCEPT"  -m state --state ESTABLISHED -p icmp --icmp-type echo-reply
	$LAA $IP6TABLES -A $chan -j LOG ${LOG}"-`rulelog $chain`-ACC-PING "  -p icmpv6 --icmpv6-type echo-reply
	$IP6TABLES -A $chain -j "$ACCEPT"  -m state --state 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 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 -j LOG ${LOG}"-`rulelog $chain`-ACC-TRUST " -m state --state NEW -s $net $proto $port
		$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-TRUST " -s $net $proto $port
		$IPTABLES -A $chain -j "$ACCEPT"  -m state --state 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()
{
    local chain="$1"
    shift
    [ -z "$1" ] && return
    $PERL "$SCRIPTSDIR/SuSEfirewall2-rpcinfo" "$@" | while read ARG; do
	$LAC $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-RPC " -m state --state NEW $ARG
	$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-RPC " $ARG
	$IPTABLES -A $chain -j "$ACCEPT" $ARG
    done
}

# 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
	    if [ "$#" -lt 2 ]; then
		error "too few parameters in $var -> $service"
		continue
	    fi

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

	    if [ "$2" = "_rpc_" ]; then
		rpcservicerules $chain $service
	    elif check_proto_port "$2" "$3" "$4" "$var"; then
		for iptables in $iptables; do
		    $LDA $iptables -A $chain -s $net $proto $port $sport -m state --state NEW -j LOG ${LOG}"-`rulelog $chain`-$action "
		    $iptables -A $chain -s $net $proto $port $sport -m state --state NEW -j "$target"
		done
	    fi
	done
    done
}

accept_services()
{
    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_`cibiz $zone`"
	eval services="\"\$$var\""

	for service in $services; do
	    IFS=, eval set -- \$service
	    if [ "$#" -lt 3 ]; then
		error "too few parameters in $var -> $service"
		continue
	    fi

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

	    if [ "$2" = "_rpc_" ]; then
		rpcservicerules $chain $service
	    elif check_proto_port "$2" "$3" "$4" "$var"; then
		for iptables in $iptables; do
		    $LAC $iptables -A $chain -s $net $proto $port $sport -m state --state NEW -j LOG ${LOG}"-`rulelog $chain`-ACC "
		    $LAA $iptables -A $chain -s $net $proto $port $sport -j LOG ${LOG}"-`rulelog $chain`-ACC "
		    $iptables -A $chain -s $net $proto $port $sport -j ACCEPT
		done
	    fi
	done
    done
}

allow_tcp_highports()
{
    local j chain
    for j in $FW_ALLOW_INCOMING_HIGHPORTS_TCP; do
	case "$j" in
	[Dd][Nn][Ss]) warning "FW_ALLOW_INCOMING_HIGHPORTS_TCP=DNS is no longer supported" ;;
	no) ;;
	yes)
	    for chain in $input_zones; do
		chain=input_$chain
		$LAC $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-HiTCP " -p tcp --dport 1024: --syn
		$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-HiTCP " -p tcp --dport 1024:
		$IPTABLES -A $chain -j "$ACCEPT" -p tcp --dport 1024:
	    done
	    break;
	    ;;
	*)
	    for chain in $input_zones; do
		chain=input_$chain
		$LAC $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-HIGH " -p tcp --sport $j --dport 1024: --syn
		$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-HIGH " -p tcp --sport $j --dport 1024:
		$IPTABLES -A $chain -j "$ACCEPT" -p tcp --sport $j --dport 1024:
	    done
	    ;;
	esac
    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 $chain $ports
    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 -j LOG ${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 -j LOG ${LOG}"-`rulelog $chain`-ACC-TCP " -p tcp --dport $port --syn
		$LAA $iptables -A $chain -j LOG ${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 -j LOG ${LOG}"-`rulelog $chain`-ACC-UDP " -p udp --dport $port
		$iptables -A $chain -j "$ACCEPT" -p udp --dport $port
	    done
	done
    done
}

allow_highports_udp()
{
    # checking "yes" in the loop allows you to log individual services plus
    # allowing all
    for j in $FW_ALLOW_INCOMING_HIGHPORTS_UDP; do
	case "$j" in
	    [Dd][Nn][Ss])
		    warning "FW_ALLOW_INCOMING_HIGHPORTS_UDP=DNS is no longer supported"
		    ;;
	    no) ;;
	    yes)
		for CHAIN in $input_zones; do
		    chain=input_$chain
		    $LAC $IPTABLES -A $CHAIN -j LOG ${LOG}"-`rulelog $CHAIN`-ACC-HiUDP " -m state --state NEW -p udp --dport 1024:
		    $LAA $IPTABLES -A $CHAIN -j LOG ${LOG}"-`rulelog $CHAIN`-ACC-HiUDP " -p udp --dport 1024:
		    $IPTABLES -A $CHAIN -j "$ACCEPT" -m state --state NEW -p udp --dport 1024:
		done
		break;
		;;
	    *)
		for CHAIN in $input_zones; do
		    chain=input_$chain
		    $LAC $IPTABLES -A $CHAIN -j LOG ${LOG}"-`rulelog $CHAIN`-ACC-HiUDP " -m state --state NEW -p udp --sport $j --dport 1024:
		    $LAA $IPTABLES -A $CHAIN -j LOG ${LOG}"-`rulelog $CHAIN`-ACC-HiUDP " -p udp --sport $j --dport 1024:
		    $IPTABLES -A $CHAIN -j "$ACCEPT" -m state --state NEW -p udp --sport $j --dport 1024:
		done
		;;
	esac
    done
}

# redirect packets from one port to another, opens ports in input_*
redirect_rules()
{
    local chain nets net1 net2 proto port1 port2
    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" -o -z "$port1" -o -z "$port2" ]; 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
	    net1=${net1/\!/\! }
	    net2=${net2/\!/\! }
	    $IPTABLES -A PREROUTING -t mangle -j MARK  -p $proto -s $net1 -d $net2 --dport $port1 --set-mark $mark_redir
	    $IPTABLES -A PREROUTING -t nat -j REDIRECT -p $proto -s $net1 -d $net2 --dport $port1 --to-ports $port2
	    redirectinstalled=1
	fi
    done

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

}

# parameters: zone interfaces
# assuming that only traffic from $zone interface enter the forward_$zone chain
# anyways, we don't need the -i parameter
allow_class_routing()
{
    [ "$FW_ALLOW_CLASS_ROUTING" != yes ] && return

    local chain iface devs zone
    for zone in $forward_zones; do
	eval devs="\$FW_DEV_$zone"
	chain=forward_$zone
	for iface in $devs; do
	    $LAA $IPTABLES -A $chain -j LOG $LOG"-`rulelog $chain`-ACC-CLASS "  -o $iface
	    $IPTABLES -A $chain -j "$ACCEPT" -o $iface
	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 -j LOG ${LOG}"-`rulelog $chain`-FWD-RELA"  -m state --state ESTABLISHED,RELATED -p icmp --icmp-type $itype
	    $IPTABLES -A $chain -j "$ACCEPT" -m state --state ESTABLISHED,RELATED -p icmp --icmp-type $itype
	done
	for itype in $safe_icmpv6_replies; do
	    $LAA $IP6TABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-FWD-RELA " -m state --state ESTABLISHED,RELATED  -p icmpv6 --icmpv6-type $itype
	    $IP6TABLES -A $chain -j "$ACCEPT" -m state --state ESTABLISHED,RELATED -p icmpv6 --icmpv6-type $itype
	done
    done
}

forwarding_rules()
{
    local nets net1 net2 flags more_args chain
    for nets in $FW_FORWARD; do
	IFS=, eval set -- \$nets

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

	case "$flags" in
	    "") ;;
	    ipsec)
		more_args="$IPSEC_MATCH"
		;;
	    *)
		echo "Error: unsupported flag in FW_FORWARD: $flags"
		net1=""
		;;
	esac

	if [ -n "$proto" ] && ! check_proto_port "$proto" "$port" '' "FW_FORWARD"; then
	    continue
	fi

	if [ -n "$net1" -a -n "$net2" ]; then
	    for chain in $forward_zones; do
		chain=forward_$chain
		$LAC $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-FORW "  -s $net1 -d $net2 $proto $port -m state --state NEW $more_args
		$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-FORW "  -s $net1 -d $net2 $proto $port $more_args
		$IPTABLES -A $chain -j "$ACCEPT" -m state --state NEW,ESTABLISHED,RELATED -s $net1 -d $net2 $proto $port $more_args
		$IPTABLES -A $chain -j "$ACCEPT" -m state --state ESTABLISHED,RELATED -s $net2 -d $net1 $proto $rport $more_args
	    done
	else
	    error "too few parameters in FW_FORWARD -> $nets"
	fi
     done
}

masquerading_rules()
{
    local nets net1 net2 proto port dev chain snet2 sport
    for nets in $FW_MASQ_NETS; do
	IFS=, eval set -- \$nets

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

	if [ -n "$5" ]; then
   	    error "Too many arguments in FW_MASQ_NETS -> $nets"
	elif [ -z "$net1" ]; then
	    error "source network must not be empty in FW_MASQ_NETS -> $nets"
	elif [ -z "$proto" -a -z "$port" ] || check_proto_port "$proto" "$port" '' 'FW_MASQ_NETS'; then
	    net1=${net1/\!/\! }
	    net2=${net2/\!/\! }
	    snet2=""
	    if [ -n "$net2" ]; then
		snet2="-s $net2"
		net2="-d $net2"
	    fi

	    for dev in $FW_MASQ_DEV; do
		for chain in $forward_zones; do
		    chain=forward_$chain
		    $LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-MASQ "  -s $net1 $net2 $proto $port -o $dev
		    $IPTABLES -A $chain -j "$ACCEPT" -m state --state NEW,ESTABLISHED,RELATED -s $net1 $net2 $proto $port -o $dev
		    # we need to allow the replies as well
		    $LAA $IPTABLES -A $chain -d $net1 $snet2 $proto $rport -i $dev -j LOG ${LOG}"-`rulelog $CHAIN`-ACC-MASQ " -m state --state ESTABLISHED,RELATED
		    $IPTABLES -A $chain -d $net1 $snet2 $proto $rport -i $dev -j "$ACCEPT" -m state --state ESTABLISHED,RELATED
	        done
		$IPTABLES -A POSTROUTING -j MASQUERADE -t nat -s $net1 $net2 $proto $port -o $dev
	    done
	fi
    done
}

forward_masquerading_rules()
{
    local nets net1 net2 proto port1 port2 lip
    for nets in $FW_FORWARD_MASQ; do
	IFS=, eval set -- \$nets

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

	case "$net2" 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
	    net1="-s $net1"
	    proto="-p $proto"
	    test -z "$port2" && port2="$port1"
	    port1="--dport $port1"
	    dport2="--dport $port2"
	    port2=":${port2/:/-}"
	    test -n "$lip" && lip="-d $lip"
	    for dev in $FW_MASQ_DEV; do
		$IPTABLES -A PREROUTING -j DNAT -t nat $proto $net1 $lip $port1 --to-destination ${net2}${port2} -i $dev
	    done
	    for chain in $forward_zones; do
		chain=forward_$chain
		$LAC $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-REVMASQ " $proto $net1 -d $net2 $dport2 -m state --state NEW
		$LAA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-ACC-REVMASQ " $proto $net1 -d $net2 $dport2
		$IPTABLES -A $chain -j "$ACCEPT" $proto $net1 -d $net2 $dport2
	    done
	fi
    done
}

# Special Logging + Deny #
drop_all()
{
    local chain
    local itype
    local chains

    for chain in $input_zones; do
	chains="$chains input_$chain"
    done

    if [ "$FW_ROUTE" = yes ]; then
	for chain in $forward_zones; do
	    chains="$chains forward_$chain"
	done
    fi
    
    for chain in $chains; do
	# some packet types are considered critical
	if [ -z "$LDC" ]; then
	    local log=${LOG}"-`rulelog $chain`-DROP-DEFLT"
	    $IPTABLES  -A $chain -j LOG $log" " -p tcp --syn
	    $IP6TABLES -A $chain -j LOG $log" " -p tcp --syn
	    $IPTABLES  -A $chain -j LOG $log" " -p icmp
	    $IP6TABLES -A $chain -j LOG $log" " -p icmpv6
	    $IPTABLES  -A $chain -j LOG $log" " -p udp
	    $IP6TABLES -A $chain -j LOG $log" " -p udp
	    $IPTABLES  -A $chain -j LOG ${LOG}"-`rulelog $chain`-DROP-DEFLT-INV " -m state --state INVALID
	    $IP6TABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-DROP-DEFLT-INV " -m state --state INVALID
	fi
	# log anything else
	$LDA $IPTABLES -A $chain -j LOG ${LOG}"-`rulelog $chain`-DROP-DEFLT "
	$IPTABLES -A $chain -j "$DROP"
	$LDA $IP6TABLES -A $chain -j LOG ${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

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

parse_logging

setlock

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 state --state NEW -j LOG $LOG"-IN-DROP-NEW-CONNECT "

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

    die 0 "Firewall rules successfully set in simple mode"
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
	
if [ "$ACTION" = "stop" ]; then
    reset_rules
    handle_initscripts
    die 0 "Firewall rules unloaded."
fi

if [ "$ACTION" = "close" ]; then
    set_basic_rules
    setproc 0 /proc/sys/net/ipv4/ip_forward

    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 ! $IPTABLES -nL reject_func >/dev/null 2>&1; then
	die 1 "SuSEfirewall2 not active"
    fi
    # 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

auto_detect_all_interfaces

parse_interfaces
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

#create_zones_hash

# Set default rules + flush
set_basic_rules

switch_on_ip_forwarding

set_proc_stuff

create_chains

# HOOK
fw_custom_before_antispoofing

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

allow_ip_services

allow_tcp_services

allow_rpc_services

accept_services

reject_or_drop_services DROP

reject_or_drop_services REJECT

allow_tcp_highports

allow_udp_services

allow_highports_udp

# HOOK
fw_custom_before_masq

redirect_rules

if [ "$FW_ROUTE" = yes ]; then

    allow_related_forward_icmp

    allow_class_routing

    forwarding_rules

    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

# END #
die 0 "Firewall rules successfully set"

# vim: sw=4
