#!/bin/ksh93

### BEGIN INIT INFO
# Provides:          iptables
# Required-Start:    networking
# Required-Stop:     $local_fs
# Default-Start:     S
# Default-Stop:      0 6
# Short-Description: Initialize iptables
# Description:       Bring/down up the IP firewall.
### END INIT INFO

# NO '-e' because a faulty rule should not cause skipping subsequent rules
#
# The file should be installed as /lib/systemd/scripts/iptables


typeset -r FPROG="${.sh.file}" PROG="${FPROG##*/}"

typeset -r RULES_FILE='/etc/iptables.rules' \
	SYSTEMD_SCRIPT='/lib/systemd/scripts/iptables' \
	SYSV_SCRIPT='/etc/init.d/iptables' \
	SYSTEMD_SVC='/lib/systemd/system/iptables.service'

FW='/sbin/iptables'
[[ -x ${FW} ]] || FW='/usr/sbin/iptables'
typeset -r FW

function showUsage {
	typeset X='--man'
	[[ -z $1 ]] && X='-?'
    getopts -a ${PROG} "${ print ${USAGE}; }" OPT $X
}


function getIP {
	[[ -z $1 ]] && return
	typeset X=${ ip addr show $1 ; } Y
	Y=${X#* inet }
	[[ -z ${.sh.match} ]] && return
	X=${Y%%/*}
	[[ -n ${.sh.match} ]] && print $X
}

function getIpByName {
	[[ -z $1 ]] && retun 0
	typeset -A RES
	# since buggy/braindamaged/POSIX-incompatible ubuntu returns IPv6 addresses
	# even in IPv4 only environments, we need to use the non-standard DB
	# 'ahostsv4' - fuck.
	typeset DB='ahostsv4'
	while [[ -n $1 ]]; do
		getent ${DB} $1 | while read X Y ; do
			[[ $Y == 'RAW' ]] || continue
			A=( ${X//./ } )
			T=${ printf "_%03d.%03d.%03d.%03d" ${A[@]} ; }
			RES["$T"]="$X"
		done
		shift
	done
	set -s ${!RES[@]}
	Y=
	for X ; do
		Y+=",${RES[$X]}"
	done
	print "${Y:1}"
}

function checkGlobals {
	typeset X
	if [[ -n ${INTERNAL_IF} ]]; then
		[[ -d /proc/sys/net/ipv4/conf/${INTERNAL_IF} ]] && \
			IF_INT=${INTERNAL_IF} || \
			print -u2 "Internal NIC '${INTERNAL_IF}' does not exist - ignored."
	fi
	if [[ -n ${EXTERNAL_IF} ]]; then
		[[ -d /proc/sys/net/ipv4/conf/${EXTERNAL_IF} ]] && \
			IF_EXT=${EXTERNAL_IF} || \
			print -u2 "External NIC '${EXTERNAL_IF}' does not exist - ignored."
	fi

	if [[ -z ${IF_INT} && -z ${IF_EXT} ]]; then
		X=${ uname -n ; }
		[[ $X =~ [0-9]$ ]] && X+='_0' || X+='0'
		if [[ -d /proc/sys/net/ipv4/conf/$X ]]; then
			IF_EXT="$X"
		elif [[ -d /proc/sys/net/ipv4/conf/eth0 ]]; then
			IF_EXT='eth0'
		else
			for X in ~(N)/proc/sys/net/ipv4/conf/* ; do
				[[ $X =~ (all|default|lo)$ || ! -d $X ]] && continue
				IF_EXT="${X##*/}"
				print -u2 "WARNING: Using interface ${IF_EXT} as external NIC"
				break
			done
		fi
	fi
	if [[ -z ${IF_INT} || -z ${IF_EXT} ]]; then
		IS_GW=0 && IS_ROUTER=0
	fi
	IP_INT=${ getIP ${IF_INT} ; }
	IP_EXT=${ getIP ${IF_EXT} ; }
}

# Actually this should be part of /etc/sysctl.conf - however, especially within
# zone we cannot be sure, that interfaces are not already there.
# See also: http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/theconfvariables.html
function tuneNet {
	typeset F X IPv4='/proc/sys/net/ipv4'

	# Reverse Path Filtering: If the source of the received packet is not 
	# reachable through the interface it came in, drop it (prevent IP spoofing).
	# NOTES: Has no effect if /all/  is not set to 1 as well!
	# 	RedHat also allows '2' for "reachable through _any_ interface"
	for F in ${IPv4}/conf/*/rp_filter; do
		[[ $F =~ /lo/rp_filter$ ]] && X=0 || X=1
		print $X 2>&1 > $F|grep -v 'Read-only file system'
		print ${LOG_MARTIAN} 2>&1 >${F%/*}/log_martians|\
			grep -v 'Read-only file system'
	done

	# No tuning of net.ipv4.tcp_syncookies. SACK + window scaling should work.
	[[ -e ${IPv4}/tcp_sack ]] && print 1 >${IPv4}/tcp_sack
	[[ -e ${IPv4}/tcp_window_scaling ]] && print 1 >${IPv4}/tcp_window_scaling

	# En/Disable packet forwarding
	print ${IS_GW} 2>&1 >${IPv4}/ip_forward|grep -v 'Read-only file system'

	if (( ! IS_ROUTER )); then
		# Drop pakets with Strict Source Route (SSR) or Loose Source Routing
		# (LSR) option set. Our pakets take the default route(s).
		# We usually have only a single route and if one tells us something
		# different it is probably a fake or man-in-the-middle attack (MITM).
		# In turn we do not send such pakets as well.
		for F in ${IPv4}/conf/*/accept_source_route; do
			X=${F%/*}
			print 0 2>&1 >$X/accept_source_route|grep -v 'Read-only file system'
			print 0 2>&1 >$X/accept_redirects|grep -v 'Read-only file system'
			print 0 2>&1 >$X/send_redirects|grep -v 'Read-only file system'
		done
	fi
}

function deleteTables {
	print -u2 -f 'Flushing iptable rules: %T\n' now
	${FW} -t filter -F
	${FW} -t filter -X
	${FW} -t nat -F
	${FW} -t nat -X
	${FW} -t mangle -F
	${FW} -t mangle -X
	print 0 2>&1 >/proc/sys/net/ipv4/ip_forward|grep -v 'Read-only file system'
	print -u2 'Done.'
	return 0
}

function dropWin {
	# Drop win stuff -> avoid log flooding. Do not rely on /etc/services!
	${FW} -A $1 -p tcp --dport 137 -j DROP	# netbios-ns
	${FW} -A $1 -p udp --dport 137 -j DROP	# netbios-ns
	${FW} -A $1 -p tcp --dport 138 -j DROP	# netbios-dgm
	${FW} -A $1 -p udp --dport 138 -j DROP	# netbios-dgm
	${FW} -A $1 -p tcp --dport 139 -j DROP	# netbios-ssn
	${FW} -A $1 -p udp --dport 139 -j DROP	# netbios-ssn
}

function createChains {
	# limit:
	# When 10 pakets/s matched, skip logging pakets for the next 200ms (1s/5).
	# Burst gets re-charged by 1 every 200ms if no paket matched the rule =>
	# full burst is avalable again after 2s (burst/limit  using the same unit).
	#
	# For zones one should _NOT_ use -j LOG, since than messages from the GZ are
	# randomly spread across all GZ's kernel loggers (rsyslog) and NGZ born
	# messages end up in the nirwana aka nowhere. However, logging to the zone's
	# userland daemon like ulogd2 seems to work for GZ as well as NGZs.
	#
	# We log to the ulogd2 stack having 'group=0' set, so we do need to add here
	# something like '--nflog-group=2'. ULOG does not support --log-level ...!
	typeset LOG='-m limit --limit=5/s --limit-burst=10 -j NFLOG --nflog-prefix '

	if [[ -n ${IF_EXT} ]]; then
		${FW} -N NeverExt
		${FW} -A NeverExt ${LOG} 'NEVERe: ' #--log-level crit
		${FW} -A NeverExt -j REJECT

		${FW} -N LogDropExt
		${FW} -A LogDropExt ${LOG} 'EXT: ' 
		${FW} -A LogDropExt -j REJECT

		${FW} -N ExtIn
		${FW} -A INPUT -i ${IF_EXT} -j ExtIn
		${FW} -A ExtIn -m state --state ESTABLISHED,RELATED -j ACCEPT
		# drop all INVALID or UNTRACKED pakets
		${FW} -A ExtIn -m state ! --state NEW -j LogDropExt

		${FW} -N ExtOut
		${FW} -A OUTPUT -o ${IF_EXT} -j ExtOut
		${FW} -A ExtOut -m state --state ESTABLISHED,RELATED -j ACCEPT
		# drop all INVALID or UNTRACKED pakets
		${FW} -A ExtOut -m state ! --state NEW -j LogDropExt
	fi

	if [[ -n ${IF_INT} ]]; then
		${FW} -N LogDropInt
		${FW} -A LogDropInt ${LOG} 'INT: ' 
		${FW} -A LogDropInt -j REJECT

		${FW} -N NeverInt
		${FW} -A NeverInt ${LOG} 'NEVERi: ' #--log-level crit
		${FW} -A NeverInt -j REJECT

		${FW} -N IntIn
		${FW} -A INPUT -i ${IF_INT} -j IntIn
		${FW} -A IntIn -m state --state ESTABLISHED,RELATED -j ACCEPT
		${FW} -A IntIn -m state ! --state NEW -j LogDropInt

		${FW} -N IntOut
		${FW} -A OUTPUT -o ${IF_INT} -j IntOut
		${FW} -A IntOut -m state --state ESTABLISHED,RELATED -j ACCEPT
		${FW} -A IntOut -m state ! --state NEW -j LogDropInt
	fi

	if (( IS_GW && IS_ROUTER == 0 )); then
		${FW} -N NeverFw
		${FW} -A NeverFw ${LOG} 'NEVERf: '
		${FW} -A NeverFw -j REJECT

		${FW} -N LogDropIntExt
		${FW} -A LogDropIntExt ${LOG} 'INTe: ' 
		${FW} -A LogDropIntExt -j REJECT

		${FW} -N LogDropExtInt
		${FW} -A LogDropExtInt ${LOG} 'EXTi: ' 
		${FW} -A LogDropExtInt -j REJECT

		${FW} -N IntExt
		${FW} -N ExtInt
		${FW} -A FORWARD -i ${IF_INT} -o ${IF_EXT} -j IntExt
		${FW} -A FORWARD -i ${IF_EXT} -o ${IF_INT} -j ExtInt
		${FW} -A FORWARD -j NeverFw

		${FW} -A ExtInt -m state --state ESTABLISHED,RELATED -j ACCEPT
		${FW} -A ExtInt -m state ! --state NEW -j LogDropExtInt
		${FW} -A IntExt -m state --state ESTABLISHED,RELATED -j ACCEPT
		${FW} -A IntExt -m state ! --state NEW -j LogDropIntExt

		[[ -n ${IP_EXT} ]] && \
			${FW} -t nat -A POSTROUTING -o ${IF_EXT} -jSNAT --to-source ${IP_EXT}
	fi
	${FW} -N Icmp
}

function setupIcmpChain {
	${FW} -A Icmp -p icmp --icmp-type ping -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type pong -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type destination-unreachable -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type port-unreachable -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type host-unreachable -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type protocol-unreachable -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type source-quench -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type time-exceeded -j ACCEPT
	${FW} -A Icmp -p icmp --icmp-type parameter-problem -j ACCEPT
	${FW} -A Icmp -p icmp -j DROP
}

function setupTables {
	typeset X Y Z
	typeset -A DUP

	print -u2 -f 'Init iptables: %T\n'
	if [[ -z ${DNS_SERVER} ]]; then
		# stupid ubuntu stuff puts it in several times
		while read X Y Z ; do
			[[ $X == 'nameserver' && -n $Y && -z ${DUP[$Y]} ]] && \
				DNS_SERVER+=",$Y" && DUP[$Y]=1
		done </etc/resolv.conf
		DNS_SERVER=${DNS_SERVER:1}
	fi
	deleteTables

	# close all 'filter' channels first. We assume they are empty.
	${FW} -A INPUT ! -i lo -j DROP
	${FW} -A OUTPUT ! -o lo -j DROP
	${FW} -A FORWARD -j DROP

	checkGlobals
	tuneNet
	print -u2 'Creating new chains ...'
	createChains

	setupIcmpChain
	[[ -n ${IF_EXT} ]] && setupExtChain
	(( IS_GW )) && setupExtIntChain
	[[ -n ${IF_INT} ]] && setupIntChain
	(( IS_GW )) && setupIntExtChain
	if (( IS_GW == 1 && IS_ROUTER == 0 )); then
		whence -q setupNAT && setupNAT
	fi

	print -u2 'done - iptables are enabled now.'

	# Now open all std channels, since all required rules are now in the chain
	${FW} -D INPUT 1
	${FW} -D OUTPUT 1
	${FW} -D FORWARD 1
}

function showExample {
	print '# File: '"${RULES_FILE}"'

# This ksh93 snippet gets pulled in by the /lib/systemd/scripts/iptables script.
# It contains 5 functions which get called when related chains are populated.
# The ACCEPT and DROP targets mentioned below are default targets as
# described in iptables(8).

# Define env variables, which should be seen by all functions.
function initGlobals {
	#IS_GW=1         # {0|1} - IP forw. via different NICs required?
	#IS_ROUTER=1     # {0|1} - act as a real router, i.e. _no_ NAT?
	#LOG_MARTIAN=0   # {0|1} - log impossible/maleformed pakets

	# The name of the external aka public network interface. Gets determined 
	# automatically if not set here.
	# Related chains: NeverExt, LogDropExt, ExtIn, ExtOut.
	#EXTERNAL_IF=
	# The name of the internal aka private network interface. If unset, IS_GW
	# and IS_ROUTER get automat. reset to 0 and the functions to setup related
	# rules, i.e. setupIntChain(), setupExtIntChain(), setupIntExtChain() are
	# ignored.
	# Related chains: NeverInt, LogDropInt, IntIn, IntOut,
	#   NeverFw, LogDropIntExt, LogDropExtInt, IntExt, ExtInt
	#INTERNAL_IF=

	###########################################################################
	# Other global env vars, which have to contain a comma separated list of IP
	# addresses or CIDRs. They get passed as is to -d and -s iptables option.
	###########################################################################
	#DNS_SERVER=        # if not set here, it gets deduced from /etc/resolv.conf

	#BAD_CLIENTS='82.80.246.0/21'
	ADMINHOSTS="192.168.4.62/30,192.168.8.62/30"
	# mail
	#MAIL_SERVER="192.168.33.8"
	#NTP_SERVER="123.45.67.88"
	#BACKUP_SERVER="123.45.67.6/31"
	# apt-get: de.archive.ubuntu.com security.ubuntu.com extras.ubuntu.com archive.canonical.com changelogs.ubuntu.com
	PKG_SERVER="91.189.88.149,91.189.88.152,91.189.88.161,91.189.88.162,91.189.91.15,91.189.91.23,91.189.91.26,91.189.92.150,91.189.92.152,91.189.92.191,141.30.62.21,141.30.62.22/31,141.30.62.24/31,141.30.62.26,91.189.95.15"
	# pkg.iks.cs.ovgu.de
	PKG_SERVER+=",141.44.24.61"
	# pkg source servers: ppa.launchpad.net
	#HTTP_SERVER="91.189.95.83"
	#NFS_SERVER="123.45.67.89/31"
	#NFS_CLIENTS="123.45.67.192/27"
}

# Rules for the external interface.
# Chains: ExtIn .. incomming traffic, ExtOut .. outgoing traffic
# Targets: LogDropExt .. log paket info with prefix "EXT: " and reject it
#          NeverExt   .. log paket info with prefix "NEVERe: " and reject it
function setupExtChain {
	# allow ping/traceroute
	${FW} -A ExtIn -p icmp -j Icmp
	${FW} -A ExtOut -p icmp -j Icmp

	# SSH
	[[ -n ${ADMINHOSTS} ]] && \
		${FW} -A ExtIn -p tcp -s "${ADMINHOSTS}" --dport ssh -j ACCEPT

	[[ -n ${BAD_CLIENTS} ]] && ${FW} -A ExtIn -s "${BAD_CLIENTS}" -j DROP

	if [[ -n ${IP_EXT} ]]; then
		typeset BCAST=${IP_EXT%.*}.255
		# allow broadcasts here
		#${FW} -A ExtIn -p udp -d ${BCAST} --dport ntp -j ACCEPT

		# finally drop everything not addressed to our server - we do not REJECT
		# because we do not wanna send RST pakets to broad/multicasts
		${FW} -A ExtIn ! -d ${IP_EXT} -j DROP
	fi

	# DNS
	if [[ -n ${DNS_SERVER} ]]; then
		${FW} -A ExtOut -p tcp -d "${DNS_SERVER}" --dport domain -j ACCEPT
		${FW} -A ExtOut -p udp -d "${DNS_SERVER}" --dport domain -j ACCEPT
	else
		print 'WARNING: No dedicated nameservers set!'
	fi
	# NTP date
	[[ -n ${NTP_SERVER} ]] && \
		${FW} -A ExtOut -p udp -d "${NTP_SERVER}" --dport 123 -j ACCEPT
	# Mail
	[[ -n ${MAIL_SERVER} ]] && \
		${FW} -A ExtOut -p tcp -d "${MAIL_SERVER}" --dport smtp -j ACCEPT
	# PKG
	if [[ -n ${PKG_SERVER} ]]; then
		${FW} -A ExtOut -p tcp -d "${PKG_SERVER}" --dport http -j ACCEPT
		${FW} -A ExtOut -p tcp -d "${PKG_SERVER}" --dport https -j ACCEPT
	fi
	# HTTP
	if [[ -n ${HTTP_SERVER} ]]; then
		${FW} -A ExtOut -p tcp -d "${HTTP_SERVER}" --dport http -j ACCEPT
		${FW} -A ExtOut -p tcp -d "${HTTP_SERVER}" --dport https -j ACCEPT
	fi
	# NFS
	[[ -n ${NFS_SERVER} ]] && \
		${FW} -A ExtOut -p tcp -d "${NFS_SERVER}" --dport 2049 -j ACCEPT
	[[ -n ${NFS_CLIENTS} ]] && \
		${FW} -A ExtIn -p tcp -s "${NFS_CLIENTS}" --dport 2049 -j ACCEPT

	# ignore netbios-ns, netbios-dgm, netbios-ssn pakets + avoid log flooding
	dropWin LogDropExt

	# log and  drop everything not caught
	${FW} -A ExtIn -j LogDropExt
	${FW} -A ExtIn -j DROP
	${FW} -A ExtOut -j LogDropExt
	${FW} -A ExtOut -j DROP
}

# Rules for the internal interface.
# Chains: IntIn .. incomming traffic, IntOut .. outgoing traffic
# Targets: LogDropInt .. log paket info with prefix "INT: " and reject it
#          NeverInt   .. log paket info with prefix "NEVERi: " and reject it
function setupIntChain {
	# add more 'allow' rules ...

	dropWin LogDropInt
	${FW} -A IntIn -j LogDropInt
	# just to make sure
	${FW} -A IntIn -j DROP
}

# Rules for forwarding traffic from external to internal interface.
# Chains: ExtInt .. ingress traffic
# Targets: LogDropExtInt .. log paket info with prefix "EXTi: " and reject it
#          NeverFw       .. log paket info with prefix "NEVERf: " and reject it
function setupExtIntChain {
	# add more 'allow' rules ...

	dropWin LogDropExtInt
	${FW} -A ExtInt -j LogDropExtInt
	# just to make sure
	${FW} -A ExtInt -j DROP
}

# Rules for forwarding traffic from internal to external interface.
# Chains: IntExt .. outgress traffic
# Targets: LogDropIntExt .. log paket info with prefix "INTe: " and reject it
#          NeverFw       .. log paket info with prefix "NEVERf: " and reject it
function setupIntExtChain {
	# add more 'allow' rules ...

	dropWin LogDropIntExt
	${FW} -A IntExt -j LogDropIntExt
	# just to make sure
	${FW} -A IntExt -j DROP
}

# Rules to NAT connections from extern to internal targets (from internal to
# external NIC gets NATed automagically). If there is no internal or no
# external interface or IS_GW == 0 or if IS_ROUTER != 0, this rule gets ignored.
# If no NAT from ext to int is needed, deleting this function is ok.
function setupNAT {
	:
	# ${FW} -t nat -A PREROUTING -i ${IF_EXT} -p tcp -s ${IP_EXT} --dport $ExtPort -j DNAT --to-destination $IntIp:$IntPort
	# ...
}

# vim: ts=4 sw=4 filetype=sh'
}

function showService {
	print '# File: '"${SYSTEMD_SVC}"'
[Unit]
Description=iptables firewall

DefaultDependencies=no
# before ulogd2, so that it finds a "producer" and activates all related
# plugins/modules. If ulogd2 finds no producer at all, it fails to start.
Before=ulogd2.service
# Start after Network Interfaces are up
After=systemd-networkd.service systemd-resolved.service
Conflicts=shutdown.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/lib/systemd/scripts/iptables -w 60 start
ExecStop=/lib/systemd/scripts/iptables stop
TimeoutStartSec=1min

[Install]
WantedBy=multi-user.target
WantedBy=network.target
WantedBy=ulogd2.service
'
}

function enableDebug {
	[[ -n ${DEBUG} ]] || return
	DEBUG="${DEBUG//,/ }"
	[[ ${DEBUG} =~ ' ALL ' ]] && typeset -ft ${ typeset +f ; } || \
		typeset -ft ${DEBUG//,/ }
}

# prepare/init global vars
unset IS_GW IS_ROUTER LOG_MARTIAN
integer IS_GW=1 IS_ROUTER=1 LOG_MARTIAN=0		# global VARs !!!
unset IF_INT IP_INT IF_EXT IP_EXT
IF_INT= IP_INT= IF_EXT= IP_EXT=					# global VARs !!!

function doMain {
	PATH=/opt/ast/bin:${PATH}

	if [[ $1 == 'start' || $1 == 'refresh' ]]; then
		if [[ -e ${RULES_FILE} ]]; then
			. ${RULES_FILE}
			enableDebug
			whence -q initGlobals && . initGlobals
		else
			print -u2 "WARNING: '${RULES_FILE}' not readable."
		fi
	fi
	IF_INT= IP_INT= IF_EXT= IP_EXT=				# global VARs !!!

	# wait for routes and make sure, that NICs are already renamed as needed.
	typeset I
	for I in ${EXTERNAL_IF} ${INTERNAL_IF} ; do
		[[ -z $I ]] && continue
		SECONDS=0
		while (( SECONDS < WAIT )); do
			X=${ ip route show dev $I ; }
			[[ -n $X ]] && break
			sleep 0.2
		done
		(( SECONDS > WAIT )) && \
			print -u2 "WARNING: no route for NIC '$I' yet!"
	done

	case "$1" in
		start|refresh)	setupTables ;;
		stop)	deleteTables ;;
		*)		showUsage ; exit 1 ;;
	esac
}

function doInstall {
	typeset A X N D
	integer SYSV=0 RES=1
	A=( ${ ls -l /sbin/init ; } )
	X="${A[10]}"
	[[ ${X##*/} == 'systemd' ]] || SYSV=1
	N=${SYSV_SCRIPT##*/}
	D=${.sh.match}
	if [[ -e ${SYSV_SCRIPT} ]]; then
		/usr/sbin/update-rc.d -f $N remove
		if [[ ! -d $D/OLD ]]; then
			mkdir $D/OLD || { print -u2 "${SYSV_SCRIPT} backup failed." ; }
		fi
		[[ -d $D/OLD && ! -e $D/OLD/$N ]] && mv ${SYSV_SCRIPT} $D/OLD/$N
		rm -f ${SYSV_SCRIPT}
	fi
	if (( SYSV )); then
		cp ${FPROG} ${SYSV_SCRIPT} && chmod 755 ${SYSV_SCRIPT} && RES=0 && \
		/usr/sbin/update-rc.d $N defaults
		print -u2 "Run '${SYSV_SCRIPT} start' to start ipfilter immediately" \
			'or reboot the OS.'
	else
		[[ -d ${SYSTEMD_SCRIPT%/*} ]] || mkdir -p ${SYSTEMD_SCRIPT%/*}
		if cp ${FPROG} ${SYSTEMD_SCRIPT} ; then
			chmod 755 ${SYSTEMD_SCRIPT} && RES=0
			showService >${SYSTEMD_SVC}
			chmod 644 ${SYSTEMD_SVC}
			/bin/systemctl daemon-reload
			/bin/systemctl enable $N
			print -u2 "Run '/bin/systemctl start $N' to start ipfilter" \
				'immediately or reboot the OS.'
		fi
	fi
	if [[ ! -e ${RULES_FILE} ]]; then
		showExample >${RULES_FILE} && chmod 644 ${RULES_FILE}
	fi
	return ${RES}
}

function copyOld {
	typeset N D
	N=${SYSV_SCRIPT##*/}
	D=${.sh.match}
	[[ -e $D/OLD/$N ]] && cp $D/OLD/$N ${RULES_FILE} && chmod 644 ${RULES_FILE}
}

function waitForDefaultRoute {
	integer TIMEOUT=$1
	(( TIMEOUT < 1 )) && return

	SECONDS=0
	while (( SECONDS < TIMEOUT )); do
		X=${ ip route show scope global ; }
		[[ -n $X ]] && return 0
		sleep 0.2
	done

	return 1
}

USAGE='[-?1.0 ]
[-copyright?Copyright (c) 2014 Jens Elkner. All rights reserved.]
[-license?CDDL 1.0]
[+NAME?'"${PROG}"' - script to setup or flush iptables.]
[+DESCRIPTION?This script is used primarily by the systemd \biptables.service\b to start/stop iptables firewall. On non-systemd systems it might be installed as \b'"${SYSV_SCRIPT}"'\b and activated via the usual \bupdate-rc.d iptables \b{\bdefaults\b|\bremove\b} command.]
[+?On start it sources in the \bksh93\b fragment \b'"${RULES_FILE}"'\b, which should provide the functions \binitGlobals\b, \bsetupExtChain\b, \bsetupExtIntChain\b, \bsetupIntExtChain\b, \bsetupIntChain\b and \bsetupNAT\b. \binitGlobals\b should just contain definitions of environment variables, which should be visible to all functions of this script - just for convinience, to ease the setup. All other functions should just contain \biptables\b(8) calls to add rulles to the corresponding chains. The functions having \bInt\b in their name are called only, if the env var \bINTERNAL_IF\b is set. For convinience all functions may use the \b$FW\b instead of \b/sbin/iptables\b to get a more readable, less bloated rules file.]
[h:help?Print this help and exit.]
[F:functions?Print a list of all functions available.]
[T:trace]:[functionList?A comma separated list of functions of this script to tr
ace (convinience for troubleshooting).] 
[+?]
[c:copy?Copies the old SysV init script to \b'"${RULES_FILE}"'\b (overwrites exting file unconditionally) for convinience (combinable with \b-i\b). It certainly needs to be adjusted, but makes migration easier and helps to free the brain wrt. old locations.]
[i:install?Convinience option to install this script at the preferred location wrt. systemd or SysV init and generate an corresponding example rules file if there is not already one (see \bFILES\b). If \b'"${SYSV_SCRIPT}"'\b exists, it gets unregistered and moved to \b'"${SYSV_SCRIPT%/*}/OLD${.sh.match}"'\b unless there is such a file already. In this case the script gets simply deleted.]
[l:lookup?Just lookup the IP addresses of the given operands using the name services of the running system and exit.]
[r:rules?Print out a rules file example (which can be taken as a start for \b'"${RULES_FILE}"'\b) and exit.]
[s:service?Print out the default service definition and exit.]
[w:wait]:[timeout?Sleep until the system has a route with global scope set or the given \atimeout\a in seconds has been reached, before actually iptables get installed. Usually used as workaround for poor systemd-networkd(8), which provides no way to determine, when encountered interfaces are actually configured and up (systemd-networkd-wait-online is buggy, cannot be used).]
[+FILES?]{
	[+'"${SYSTEMD_SVC}"'?The iptables service definition.]
	[+'"${SYSTEMD_SCRIPT}"'?The preferred location of this script on systemd driven boxes.]
	[+'"${SYSV_SCRIPT}"'?The preferred location of this script on SysV driven boxes.]
	[+'"${RULES_FILE}"'?The file containing the rules to setup the IP tables.]
}
[+SEE ALSO?\biptables\b(8), \bsystemctl\b(1).]
\n\n{start|stop|refresh}|{IP...}
'
X="${ print ${USAGE} ; }"
integer LOOKUP=0 EXAMPLE=0 SVC=0 COPY=0 INSTALL=0 WAIT=0
DEBUG=
while getopts "${X}" OPT ; do
	case "${OPT}" in
		# common options
		h) showUsage MAIN ; exit 0 ;;
		F) typeset +f ; exit 0 ;;
		T) DEBUG+=" ${OPTARG} " ;;
		c) COPY=1 ;;
		i) INSTALL=1 ;;
		l) LOOKUP=1 ;;
		r) EXAMPLE=1 ;;
		s) SVC=1 ;;
		w) WAIT="${OPTARG}" ;;
		*) showUsage ; exit 0 ;;
	esac
done
X=$((OPTIND-1))
shift $X && OPTIND=1
unset X
enableDebug

(( LOOKUP )) && { getIpByName "$@" ; exit $? ; }
(( EXAMPLE )) && { showExample "$@" ; exit $? ; }
(( SVC )) && { showService "$@" ; exit $? ; }
if (( INSTALL || COPY )); then
	SVC=0
	(( INSTALL )) && { doInstall "$@" ; (( SVC+=$? )) ; }
	(( COPY )) && { copyOld "$@" ; (( SVC+=$? )) ; }
	exit ${SVC}
fi
(( WAIT )) && waitForDefaultRoute "${WAIT}"
doMain "$@"
