#!/bin/bash

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# EXTERNAL/SYSTEM COMMANDS MANAGEMENT
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

# External commands FireHOL will need.
# If one of those is not found, FireHOL will refuse to run.

which_cmd() {
	local block=1
	if [ "a${1}" = "a-n" ]
	then
		local block=0
		shift
	fi	
	
	unalias $2 >/dev/null 2>&1
	local cmd=`which $2 2>/dev/null| head -n 1`

	if [ $? -gt 0 -o ! -x "${cmd}" ]
	then
		if [ ${block} -eq 1 ]
		then
			echo >&2
			echo >&2 "ERROR:	Command '$2' not found in the system path."
			echo >&2 "	FireHOL requires this command for its operation."
			echo >&2 "	Please install the required package and retry."
			echo >&2		
			echo >&2 "	Note that you need an operational 'which' command"
			echo >&2 "	for FireHOL to find all the external programs it"
			echo >&2 "	needs. Check it yourself. Run:"
			echo >&2
			echo >&2 "	which $2"
			exit 1
		fi
		return 1
	fi

	eval $1=${cmd}
	return 0
}

# command on demand support.
require_cmd() {
	local block=1
	if [ "a$1" = "a-n" ]
	then
		local block=0
		shift
	fi
	
	# if one is found, return success
	for x in $1
	do
		eval var=`echo ${x} | tr 'a-z' 'A-Z'`_CMD
		eval val=\$\{${var}\}
		if [ -z "${val}" ]
		then
			which_cmd -n "${var}" "${x}"
			test $? -eq 0 && return 0
		fi
	done
	
	if [ $block -eq 1 ]
	then
		echo >&2
		echo >&2 "ERROR:	THE REQUESTED FEATURE REQUIRES THESE PROGRAMS:"
		echo >&2
		echo >&2 "	$*"
		echo >&2
		echo >&2 "	You have requested the use of an optional FireHOL"
		echo >&2 "	feature that requires certain external programs"
		echo >&2 "	to be installed in the running system."
		echo >&2
		echo >&2 "	Please consult your Linux distribution manual to"
		echo >&2 "	install the package(s) that provide these external"
		echo >&2 "	programs and retry."
		echo >&2
		echo >&2 "	Note that you need an operational 'which' command"
		echo >&2 "	for FireHOL to find all the external programs it"
		echo >&2 "	needs. Check it yourself. Run:"
		echo >&2
		for x in $1
		do
			echo >&2 "	which $x"
		done
		
		exit 1
	fi
	
	return 1
}

# Currently the following commands are required only when needed.
# (i.e. Command on Demand)
#
# wget or curl (either is fine)
# gzcat
# ip
# netstat
# egrep
# date
# hostname

# Commands that are mandatory for FireHOL operation:
which_cmd CAT_CMD cat
which_cmd CUT_CMD cut
which_cmd CHOWN_CMD chown
which_cmd CHMOD_CMD chmod
which_cmd EXPR_CMD expr
which_cmd GAWK_CMD awk
which_cmd GREP_CMD grep
which_cmd HEAD_CMD head
which_cmd IPTABLES_CMD iptables
which_cmd IPTABLES_SAVE_CMD iptables-save
which_cmd LESS_CMD pager
#which_cmd LSMOD_CMD lsmod
LSMOD_CMD=`which lsmod 2>/dev/null | head -n 1`
which_cmd MKDIR_CMD mkdir
which_cmd MV_CMD mv
#which_cmd MODPROBE_CMD modprobe
MODPROBE_CMD=`which modprobe 2>/dev/null | head -n 1`
which_cmd RENICE_CMD renice
which_cmd RM_CMD rm
which_cmd SED_CMD sed
which_cmd SORT_CMD sort
which_cmd SYSCTL_CMD sysctl
which_cmd TOUCH_CMD touch
which_cmd TR_CMD tr
which_cmd UNAME_CMD uname
which_cmd UNIQ_CMD uniq

# Make sure our generated files cannot be accessed by anyone else.
umask 077

# Find our minor version
firehol_minor_version() {
${CAT_CMD} <<"EOF" | ${CUT_CMD} -d ' ' -f 3 | ${CUT_CMD} -d '.' -f 2
$Id: firehol.sh,v 1.231 2005/04/03 21:48:04 ktsaou Exp $
EOF
}

FIREHOL_MINOR_VERSION=`firehol_minor_version`
${EXPR_CMD} ${FIREHOL_MINOR_VERSION} + 0 >/dev/null 2>&1
if [ $? -ne 0 ]
then
	FIREHOL_MINOR_VERSION=231
fi

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# GLOBAL DEFAULTS
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

# ----------------------------------------------------------------------
# Directories and files

# These files will be created and deleted during our run.
FIREHOL_DIR="/tmp/.firehol-tmp-$$-${RANDOM}-${RANDOM}"
FIREHOL_CHAINS_DIR="${FIREHOL_DIR}/chains"
FIREHOL_OUTPUT="${FIREHOL_DIR}/firehol-out.sh"
FIREHOL_SAVED="${FIREHOL_DIR}/firehol-save.sh"
FIREHOL_TMP="${FIREHOL_DIR}/firehol-tmp.sh"

FIREHOL_SPOOL_DIR="/var/spool/firehol"
 
# The default configuration file
# It can be changed on the command line
FIREHOL_CONFIG_DIR="/etc/firehol"
FIREHOL_CONFIG="${FIREHOL_CONFIG_DIR}/firehol.conf"

# Where /etc/init.d/iptables expects its configuration?
# Leave it empty for automatic detection
FIREHOL_AUTOSAVE=


# ------------------------------------------------------------------------------
# Make sure we automatically cleanup when we exit.
# WHY:
# Even a CTRL-C will call this and we will not leave temp files.
# Also, if a configuration file breaks, we will detect this too.

firehol_exit() {
	if [ -f "${FIREHOL_SAVED}" ]
	then
		echo
		echo -n $"FireHOL: Restoring old firewall:"
		iptables-restore <"${FIREHOL_SAVED}"
		if [ $? -eq 0 ]
		then
			success $"FireHOL: Restoring old firewall:"
		else
			failure $"FireHOL: Restoring old firewall:"
		fi
		echo
	fi
	
	test -d "${FIREHOL_DIR}" && ${RM_CMD} -rf "${FIREHOL_DIR}"
	return 0
}

# Run our exit even if we don't call exit.
trap firehol_exit EXIT
trap firehol_exit SIGHUP


# ------------------------------------------------------------------------------
# Create the directories we need.

if [ ! -d "${FIREHOL_CONFIG_DIR}" -a -f /etc/firehol.conf ]
then
	"${MKDIR_CMD}" "${FIREHOL_CONFIG_DIR}"			|| exit 1
	"${CHOWN_CMD}" root:root "${FIREHOL_CONFIG_DIR}"	|| exit 1
	"${CHMOD_CMD}" 700 "${FIREHOL_CONFIG_DIR}"		|| exit 1
	"${MV_CMD}" /etc/firehol.conf "${FIREHOL_CONFIG}"	|| exit 1
	
	echo >&2
	echo >&2
	echo >&2 "NOTICE: Your config file /etc/firehol.conf has been moved to ${FIREHOL_CONFIG}"
	echo >&2
	sleep 5
fi

# Externally defined services can be placed in "${FIREHOL_CONFIG_DIR}/services/"
if [ ! -d "${FIREHOL_CONFIG_DIR}/services" ]
then
	"${MKDIR_CMD}" "${FIREHOL_CONFIG_DIR}/services"
	if [ $? -ne 0 ]
	then
		echo >&2
		echo >&2
		echo >&2 "FireHOL needs to create the directory '${FIREHOL_CONFIG_DIR}/services', but it cannot."
		echo >&2 "Possibly you have a file with this name, or something else is happening."
		echo >&2 "Please solve this issue and retry".
		echo >&2
		exit 1
	fi
	"${CHOWN_CMD}" root:root "${FIREHOL_CONFIG_DIR}/services"
	"${CHMOD_CMD}" 700 "${FIREHOL_CONFIG_DIR}/services"
fi

# Remove any old directories that might be there.
if [ -d "${FIREHOL_DIR}" ]
then
	"${RM_CMD}" -rf "${FIREHOL_DIR}"
	if [ $? -ne 0 -o -e "${FIREHOL_DIR}" ]
	then
		echo >&2
		echo >&2
		echo >&2 "Cannot clean temporary directory '${FIREHOL_DIR}'."
		echo >&2
		exit 1
	fi
fi
"${MKDIR_CMD}" "${FIREHOL_DIR}"				|| exit 1
"${MKDIR_CMD}" "${FIREHOL_CHAINS_DIR}"			|| exit 1


# Make sure we have a directory for our data.
if [ ! -d "${FIREHOL_SPOOL_DIR}" ]
then
	"${MKDIR_CMD}" "${FIREHOL_SPOOL_DIR}"			|| exit 1
	"${CHOWN_CMD}" root:root "${FIREHOL_CONFIG_DIR}"	|| exit 1
	"${CHMOD_CMD}" 700 "${FIREHOL_CONFIG_DIR}"		|| exit 1
fi

# ------------------------------------------------------------------------------
# IP definitions

# IANA Reserved IPv4 address space
# Suggested by Fco.Felix Belmonte <ffelix@gescosoft.com>
# Optimized (CIDR) by Marc 'HE' Brockschmidt <marc@marcbrockschmidt.de>
# Further optimized and reduced by http://www.vergenet.net/linux/aggregate/
# The supplied get-iana.sh uses 'aggregate-flim' if it finds it in the path.
RESERVED_IPS="0.0.0.0/7 2.0.0.0/8 5.0.0.0/8 7.0.0.0/8 23.0.0.0/8 27.0.0.0/8 31.0.0.0/8 36.0.0.0/7 39.0.0.0/8 42.0.0.0/8 77.0.0.0/8 78.0.0.0/7 92.0.0.0/6 96.0.0.0/4 112.0.0.0/5 120.0.0.0/8 127.0.0.0/8 173.0.0.0/8 174.0.0.0/7 176.0.0.0/5 184.0.0.0/6 197.0.0.0/8 223.0.0.0/8 240.0.0.0/4 "

# Private IPv4 address space
# Suggested by Fco.Felix Belmonte <ffelix@gescosoft.com>
# Revised by me according to RFC 3330. Explanation:
# 10.0.0.0/8       => RFC 1918: IANA Private Use
# 169.254.0.0/16   => Link Local
# 192.0.2.0/24     => Test Net
# 192.88.99.0/24   => RFC 3068: 6to4 anycast & RFC 2544: Benchmarking addresses
# 192.168.0.0/16   => RFC 1918: Private use
PRIVATE_IPS="10.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.0.2.0/24 192.88.99.0/24 192.168.0.0/16"

# The multicast address space
MULTICAST_IPS="224.0.0.0/4"

# A shortcut to have all the Internet unroutable addresses in one
# variable
UNROUTABLE_IPS="${RESERVED_IPS} ${PRIVATE_IPS}"

# ----------------------------------------------------------------------

# The default policy for the interface commands of the firewall.
# This can be controlled on a per interface basis using the
# policy interface subscommand. 
DEFAULT_INTERFACE_POLICY="DROP"

# Which is the filter table chains policy during firewall activation?
FIREHOL_INPUT_ACTIVATION_POLICY="ACCEPT"
FIREHOL_OUTPUT_ACTIVATION_POLICY="ACCEPT"
FIREHOL_FORWARD_ACTIVATION_POLICY="ACCEPT"

# Should we drop all INVALID packets always?
FIREHOL_DROP_INVALID=0

# What to do with unmatched packets?
# To change these, simply define them the configuration file.
UNMATCHED_INPUT_POLICY="DROP"
UNMATCHED_OUTPUT_POLICY="DROP"
UNMATCHED_ROUTER_POLICY="DROP"

# Options for iptables LOG action.
# These options will be added to all LOG actions FireHOL will generate.
# To change them, type such a line in the configuration file.
# FIREHOL_LOG_OPTIONS="--log-tcp-sequence --log-tcp-options --log-ip-options"
FIREHOL_LOG_OPTIONS=""
FIREHOL_LOG_LEVEL="warning"
FIREHOL_LOG_MODE="LOG"
FIREHOL_LOG_FREQUENCY="1/second"
FIREHOL_LOG_BURST="5"

# The client ports to be used for "default" client ports when the
# client specified is a foreign host.
# We give all ports above 1000 because a few systems (like Solaris)
# use this range.
# Note that FireHOL will ask the kernel for default client ports of
# the local host. This only applies to client ports of remote hosts.
DEFAULT_CLIENT_PORTS="1000:65535"

# Get the default client ports from the kernel configuration.
# This is formed to a range of ports to be used for all "default"
# client ports when the client specified is the localhost.
LOCAL_CLIENT_PORTS_LOW=`${SYSCTL_CMD} net.ipv4.ip_local_port_range | ${CUT_CMD} -d '=' -f 2 | ${CUT_CMD} -f 1`
LOCAL_CLIENT_PORTS_HIGH=`${SYSCTL_CMD} net.ipv4.ip_local_port_range | ${CUT_CMD} -d '=' -f 2 | ${CUT_CMD} -f 2`
LOCAL_CLIENT_PORTS="${LOCAL_CLIENT_PORTS_LOW}:${LOCAL_CLIENT_PORTS_HIGH}"


# ----------------------------------------------------------------------
# This is our version number. It is increased when the configuration
# file commands and arguments change their meaning and usage, so that
# the user will have to review it more precisely.
FIREHOL_VERSION=5


# ----------------------------------------------------------------------
# The initial line number of the configuration file.
FIREHOL_LINEID="INIT"

# Variable kernel module requirements.
# Suggested by Fco.Felix Belmonte <ffelix@gescosoft.com>
# Note that each of the complex services
# may add to this variable the kernel modules it requires.
# See rules_ftp() bellow for an example.
FIREHOL_KERNEL_MODULES=""
#
# In the configuration file you can write:
#
#                     require_kernel_module <module_name>
# 
# to have FireHOL require a specific module for the configurarion.

# Set this to 1 in the configuration file to have FireHOL complex
# services' rules load NAT kernel modules too.
FIREHOL_NAT=0

# Set this to 1 in the configuration file if routing should be enabled
# in the kernel.
FIREHOL_ROUTING=0

# Services may add themeselves to this variable so that the service "all" will
# also call them.
# By default it is empty - only rules programmers should change this.
ALL_SHOULD_ALSO_RUN=


# ------------------------------------------------------------------------------
# Various Defaults

# If set to 1, we are just going to present the resulting firewall instead of
# installing it.
# It can be changed on the command line
FIREHOL_DEBUG=0

# If set to 1, the firewall will be saved for normal iptables processing.
# It can be changed on the command line
FIREHOL_SAVE=0

# If set to 1, the firewall will be restored if you don't commit it.
# It can be changed on the command line
FIREHOL_TRY=1

# If set to 1, FireHOL enters interactive mode to answer questions.
# It can be changed on the command line
FIREHOL_EXPLAIN=0

# If set to 1, FireHOL enters a wizard mode to help the user build a firewall.
# It can be changed on the command line
FIREHOL_WIZARD=0

# If set to 0, FireHOL will not try to load the required kernel modules.
# It can be set in the configuration file.
FIREHOL_LOAD_KERNEL_MODULES=1

# If set to 1, FireHOL will output the commands of the configuration file
# with variables expanded.
FIREHOL_CONF_SHOW=1

# ------------------------------------------------------------------------------
# Keep information about the current primary command
# Primary commands are: interface, router

work_counter=0
work_cmd=
work_realcmd=("(unset)")
work_name=
work_inface=
work_outface=
work_policy="${DEFAULT_INTERFACE_POLICY}"
work_error=0
work_function="Initializing"


# ------------------------------------------------------------------------------
# Keep status information

# 0 = no errors, 1 = there were errors in the script
work_final_status=0

# This variable is used for generating dynamic chains when needed for
# combined negative statements (AND) implied by the "not" parameter
# to many FireHOL directives.
# What FireHOL is doing to accomplish this, is to produce dynamically
# a linked list of iptables chains with just one condition each, making
# the packets to traverse from chain to chain when matched, to reach
# their final destination.
FIREHOL_DYNAMIC_CHAIN_COUNTER=1

# If set to 0, FireHOL will not trust interface lo for all traffic.
# This means the admin could setup a firewall on lo.
FIREHOL_TRUST_LOOPBACK=1

# Services API version
FIREHOL_SERVICES_API="1"

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# SIMPLE SERVICES DEFINITIONS
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
# The following are definitions for simple services.
# We define as "simple" the services that are implemented using a single socket,
# initiated by the client and used by the server.
#
# The following list is sorted by service name.

server_AH_ports="51/any"
client_AH_ports="any"

# Debian package proxy
server_aptproxy_ports="tcp/9999"
client_aptproxy_ports="default"

# APC UPS Server (these ports have to be accessible on all machines NOT
# directly connected to the UPS (e.g. the slaves)
server_apcupsd_ports="tcp/6544"
client_apcupsd_ports="default"

server_apcupsdnis_ports="tcp/3551"
client_apcupsdnis_ports="default"

server_asterisk_ports="tcp/5038"
client_asterisk_ports="default"

server_cups_ports="tcp/631 udp/631"
client_cups_ports="default 631"

server_cvspserver_ports="tcp/2401"
client_cvspserver_ports="default"

server_darkstat_ports="tcp/666"
client_darkstat_ports="default"

server_daytime_ports="tcp/13"
client_daytime_ports="default"

server_dcc_ports="udp/6277"
client_dcc_ports="default"

server_dcpp_ports="tcp/1412 udp/1412"
client_dcpp_ports="default"

server_dns_ports="udp/53 tcp/53"
client_dns_ports="any"

# DHCP Relaying (server is the relay server which behaves like a client
# towards the real DHCP Server); I'm not sure about this one...
server_dhcprelay_ports="udp/67"
client_dhcprelay_ports="67"

server_dict_ports="tcp/2628"
client_dict_ports="default"

# DISTCC is the distributed gcc for Gentoo
server_distcc_ports="tcp/3632"
client_distcc_ports="default"

server_eserver_ports="tcp/4661 udp/4661 udp/4665"
client_eserver_ports="any"

server_ESP_ports="50/any"
client_ESP_ports="any"

server_echo_ports="tcp/7"
client_echo_ports="default"

server_finger_ports="tcp/79"
client_finger_ports="default"

# giFT modules' ports
# Gnutella  = tcp/4302
# FastTrack = tcp/1214
# OpenFT    = tcp/2182 tcp/2472
server_gift_ports="tcp/4302 tcp/1214 tcp/2182 tcp/2472"
client_gift_ports="any"

# giFT User Interface connections
server_giftui_ports="tcp/1213"
client_giftui_ports="default"

# gkrellmd (from gkrellm.net)
server_gkrellmd_ports="tcp/19150"
client_gkrellmd_ports="default"

server_GRE_ports="47/any"
client_GRE_ports="any"

server_h323_ports="tcp/1720 tcp/1731"
client_h323_ports="default"

# We assume heartbeat uses ports in the range 690 to 699
server_heartbeat_ports="udp/690:699"
client_heartbeat_ports="default"

server_http_ports="tcp/80"
client_http_ports="default"

server_https_ports="tcp/443"
client_https_ports="default"

server_iax_ports="udp/5036"
client_iax_ports="default"

server_iax2_ports="udp/5469 udp/4569"
client_iax2_ports="default"

server_ICMP_ports="icmp/any"
client_ICMP_ports="any"

server_icmp_ports="icmp/any"
client_icmp_ports="any"
# ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} icmp"

# Squid' ICP port
server_icp_ports="udp/3130"
client_icp_ports="3130"

server_ident_ports="tcp/113"
client_ident_ports="default"

server_imap_ports="tcp/143"
client_imap_ports="default"

server_imaps_ports="tcp/993"
client_imaps_ports="default"

server_irc_ports="tcp/6667"
client_irc_ports="default"
require_irc_modules="ip_conntrack_irc"
require_irc_nat_modules="ip_nat_irc"
ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} irc"

# for IPSec Key negotiation
server_isakmp_ports="udp/500"
client_isakmp_ports="500"

server_jabber_ports="tcp/5222 tcp/5223"
client_jabber_ports="default"

server_jabberd_ports="tcp/5222 tcp/5223 tcp/5269"
client_jabberd_ports="default"

server_ldap_ports="tcp/389"
client_ldap_ports="default"

server_ldaps_ports="tcp/636"
client_ldaps_ports="default"

server_lpd_ports="tcp/515"
client_lpd_ports="721:731 default"

server_microsoft_ds_ports="tcp/445"
client_microsoft_ds_ports="default"

server_mms_ports="tcp/1755 udp/1755"
client_mms_ports="default"
require_mms_modules="ip_conntrack_mms"
require_mms_nat_modules="ip_nat_mms"
# this will produce warnings on most distribution
# because the mms module is not there:
# ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} mms"

server_msn_ports="tcp/6891"
client_msn_ports="default"

server_mysql_ports="tcp/3306"
client_mysql_ports="default"

# Veritas NetBackup
server_netbackup_ports="tcp/13701 tcp/13711 tcp/13720 tcp/13721 tcp/13724 tcp/13782 tcp/13783"
client_netbackup_ports="any"

server_netbios_ns_ports="udp/137"
client_netbios_ns_ports="default 137"

server_netbios_dgm_ports="udp/138"
client_netbios_dgm_ports="default 138"

server_netbios_ssn_ports="tcp/139"
client_netbios_ssn_ports="default"

server_nntp_ports="tcp/119"
client_nntp_ports="default"

server_nntps_ports="tcp/563"
client_nntps_ports="default"

server_ntp_ports="udp/123 tcp/123"
client_ntp_ports="123 default"

# Network UPS Tools
server_nut_ports="tcp/3493 udp/3493"
client_nut_ports="default"

# NoMachine's NX server
server_nxserver_ports="tcp/5000:5200"
client_nxserver_ports="default"

#openvpn 2.0
server_openvpn_ports="tcp/1194 udp/1194"
client_openvpn_ports="default"

# Oracle database
server_oracle_ports="tcp/1521"
client_oracle_ports="default"

server_OSPF_ports="89/any"
client_OSPF_ports="any"

server_pop3_ports="tcp/110"
client_pop3_ports="default"

server_pop3s_ports="tcp/995"
client_pop3s_ports="default"

# Portmap clients appear to use ports bellow 1024
server_portmap_ports="udp/111 tcp/111"
client_portmap_ports="500:65535"

server_postgres_ports="tcp/5432"
client_postgres_ports="default"

# Privacy Proxy
server_privoxy_ports="tcp/8118"
client_privoxy_ports="default"

server_radius_ports="udp/1812 udp/1813"
client_radius_ports="default"

server_radiusproxy_ports="udp/1814"
client_radiusproxy_ports="default"

server_radiusold_ports="udp/1645 udp/1646"
client_radiusold_ports="default"

server_radiusoldproxy_ports="udp/1647"
client_radiusoldproxy_ports="default"

server_rdp_ports="tcp/3389"
client_rdp_ports="default"

server_rndc_ports="tcp/953"
client_rndc_ports="default"

server_rsync_ports="tcp/873 udp/873"
client_rsync_ports="default"

server_rtp_ports="udp/10000:20000"
client_rtp_ports="any"

server_sip_ports="udp/5060"
client_sip_ports="5060 default"

server_socks_ports="tcp/1080 udp/1080"
client_socks_ports="default"

server_squid_ports="tcp/3128"
client_squid_ports="default"

server_smtp_ports="tcp/25"
client_smtp_ports="default"

server_smtps_ports="tcp/465"
client_smtps_ports="default"

server_snmp_ports="udp/161"
client_snmp_ports="default"

server_snmptrap_ports="udp/162"
client_snmptrap_ports="any"

server_ssh_ports="tcp/22"
client_ssh_ports="default"

server_stun_ports="udp/3478 udp/3479"
client_stun_ports="any"

# SMTP over SSL/TLS submission
server_submission_ports="tcp/587"
client_submission_ports="default"

# Sun RCP is an alias for service portmap
server_sunrpc_ports="${server_portmap_ports}"
client_sunrpc_ports="${client_portmap_ports}"

server_swat_ports="tcp/901"
client_swat_ports="default"

server_syslog_ports="udp/514"
client_syslog_ports="syslog default"

server_telnet_ports="tcp/23"
client_telnet_ports="default"

server_time_ports="tcp/37 udp/37"
client_time_ports="default"

server_upnp_ports="udp/1900 tcp/2869"
client_upnp_ports="default"

server_uucp_ports="tcp/540"
client_uucp_ports="default"

server_whois_ports="tcp/43"
client_whois_ports="default"

server_vmware_ports="tcp/902"
client_vmware_ports="default"

server_vmwareauth_ports="tcp/903"
client_vmwareauth_ports="default"

server_vmwareweb_ports="tcp/8222"
client_vmwareweb_ports="default"

server_vnc_ports="tcp/5900:5903"
client_vnc_ports="default"

server_webcache_ports="tcp/8080"
client_webcache_ports="default"

server_webmin_ports="tcp/10000"
client_webmin_ports="default"

server_xdmcp_ports="udp/177"
client_xdmcp_ports="default"


# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# COMPLEX SERVICES DEFINITIONS
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
# The following are definitions for complex services.
# We define as "complex" the services that are implemented using multiple sockets.

# Each function bellow is organized in three parts:
# 1) A Header, common to each and every function
# 2) The rules required for the INPUT of the server
# 3) The rules required for the OUTPUT of the server
#
# The Header part, together with the "reverse" keyword can reverse the rules so
# that if we are implementing a client the INPUT will become OUTPUT and vice versa.
#
# In most the cases the input and output rules are the same with the following
# differences:
#
# a) The output rules begin with the "reverse" keyword, which reverses:
#    inface/outface, src/dst, sport/dport
# b) The output rules use ${out}_${mychain} instead of ${in}_${mychain}
# c) The state rules match the client operation, not the server.


# --- DHCP --------------------------------------------------------------------

rules_dhcp() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	set_work_function "Setting up rules for DHCP (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport "68" dport "67" || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "68" dport "67" || return 1
	
	return 0
}

# --- EMULE --------------------------------------------------------------------

rules_emule() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow incomming to server tcp/4662
	set_work_function "Setting up rules for EMULE/client-to-server tcp/4662 (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport any dport 4662 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport any dport 4662 state ESTABLISHED     || return 1
	
	# allow outgoing to client tcp/4662
	set_work_function "Setting up rules for EMULE/server-to-client tcp/4662 (${type})"
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" dport any sport 4662 state NEW,ESTABLISHED || return 1
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" dport any sport 4662 state ESTABLISHED     || return 1
	
	# allow incomming to server udp/4672
	set_work_function "Setting up rules for EMULE/client-to-server udp/4672 (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport any dport 4672 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport any dport 4672 state ESTABLISHED     || return 1
	
	# allow outgoing to client udp/4672
	set_work_function "Setting up rules for EMULE/server-to-client udp/4672 (${type})"
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" dport any sport 4672 state NEW,ESTABLISHED || return 1
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" dport any sport 4672 state ESTABLISHED     || return 1
	
	# allow incomming to server tcp/4661
	set_work_function "Setting up rules for EMULE/client-to-server tcp/4661 (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport any dport 4661 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport any dport 4661 state ESTABLISHED     || return 1
	
	# allow incomming to server udp/4665
	set_work_function "Setting up rules for EMULE/client-to-server udp/4665 (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport any dport 4665 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport any dport 4665 state ESTABLISHED     || return 1
	
	return 0
}


# --- HYLAFAX ------------------------------------------------------------------
# Written by: Franscisco Javier Felix <ffelix@gescosoft.com>

rules_hylafax() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow incomming to server tcp/4559
	set_work_function "Setting up rules for HYLAFAX/client-to-server tcp/4559 (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport any dport 4559 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport any dport 4559 state ESTABLISHED     || return 1
	
	# allow outgoing to client from server tcp/4558
	set_work_function "Setting up rules for HYLAFAX/server-to-client from server tcp/4558 (${type})"
	rule ${out}        action "$@" chain "${out}_${mychain}" proto "tcp" sport 4558 dport any state NEW,ESTABLISHED || return 1
        rule ${in} reverse action "$@" chain "${in}_${mychain}"  proto "tcp" sport 4558 dport any state ESTABLISHED     || return 1
	
	return 0
}


# --- SAMBA --------------------------------------------------------------------

rules_samba() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	set_work_function "Setting up rules for SAMBA/NETBIOS-NS (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport "137 ${client_ports}"  dport 137 state NEW,ESTABLISHED || return 1
	
	# NETBIOS initiates based on the broadcast address of an interface
	# (request goes to broadcast address) but the server responds from
	# its own IP address. This makes the server samba accept statement
	# drop the server reply.
	# Bellow is a hack, that allows a linux samba server to respond
	# correctly, as it allows new outgoing connections from the well
	# known netbios-ns port to the clients high ports.
	# For clients and routers this hack is not applied because it
	# would be a huge security hole.
	if [ "${type}" = "server" -a "${work_cmd}" = "interface" ]
	then
		rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "137 ${client_ports}"  dport 137 state NEW,ESTABLISHED || return 1
	else
		rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "137 ${client_ports}"  dport 137 state ESTABLISHED     || return 1
	fi
	
	set_work_function "Setting up rules for SAMBA/NETBIOS-DGM (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport "138 ${client_ports}" dport 138 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "138 ${client_ports}" dport 138 state ESTABLISHED     || return 1
	
	set_work_function "Setting up rules for SAMBA/NETBIOS-SSN (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport "${client_ports}" dport 139 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport 139 state ESTABLISHED     || return 1
	
	set_work_function "Setting up rules for SAMBA/MICROSOFT_DS (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport "${client_ports}" dport 445 state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport 445 state ESTABLISHED     || return 1
	
	return 0
}


# --- PPTP --------------------------------------------------------------------

rules_pptp() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	set_work_function "Setting up rules for PPTP/initial connection (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp" sport "${client_ports}" dport "1723" state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp" sport "${client_ports}" dport "1723" state ESTABLISHED     || return 1
	
	set_work_function "Setting up rules for PPTP/tunnel GRE traffic (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "47"	|| return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "47"	|| return 1
	
	return 0
}


# --- NFS ----------------------------------------------------------------------

rules_nfs() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# This command requires in the client or route subcommands,
	# the first argument after the policy/action is a dst.
	
	local action="${1}"; shift
	local servers="localhost"
	
	if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ]
	then
		case "${1}" in
			dst|DST|destination|DESTINATION)
				shift
				local servers="${1}"
				shift
				;;
				
			*)
				error "Please re-phrase to: ${type} nfs ${action} dst <NFS_SERVER> [other rules]"
				return 1
				;;
		esac
	fi
	
	local x=
	for x in ${servers}
	do
		local tmp="${FIREHOL_DIR}/firehol.rpcinfo.$$.${RANDOM}"
		
		set_work_function "Getting RPC information from server '${x}'"
		
		rpcinfo -p ${x} >"${tmp}"
		if [ $? -gt 0 -o ! -s "${tmp}" ]
		then
			error "Cannot get rpcinfo from host '${x}' (using the previous firewall rules)"
			${RM_CMD} -f "${tmp}"
			return 1
		fi
		
		local server_rquotad_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " rquotad$"  | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		local server_mountd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " mountd$"  | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"

		local server_lockd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " nlockmgr$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		local server_statd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " status$" | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		local server_nfsd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " nfs$"       | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		
		test -z "${server_mountd_ports}" && error "Cannot find mountd ports for nfs server '${x}'" && return 1
		test -z "${server_lockd_ports}"  && error "Cannot find lockd ports for nfs server '${x}'" && return 1
		test -z "${server_nfsd_ports}"   && error "Cannot find nfsd ports for nfs server '${x}'" && return 1
		
		local dst=
		if [ ! "${x}" = "localhost" ]
		then
			dst="dst ${x}"
		fi
		
		if [ ! -z "${server_rquotad_ports}" ]
		then
			set_work_function "Processing rquotad rules for server '${x}'"
			rules_custom "${mychain}" "${type}" nfs-rquotad "${server_rquotad_ports}" "500:65535" "${action}" $dst "$@"
		fi
		
		set_work_function "Processing mountd rules for server '${x}'"
		rules_custom "${mychain}" "${type}" nfs-mountd "${server_mountd_ports}" "500:65535" "${action}" $dst "$@"
		
		set_work_function "Processing lockd rules for server '${x}'"
		rules_custom "${mychain}" "${type}" nfs-lockd "${server_lockd_ports}" "500:65535" "${action}" $dst "$@"
		
		set_work_function "Processing statd rules for server '${x}'"
		rules_custom "${mychain}" "${type}" nfs-statd "${server_statd_ports}" "500:65535" "${action}" $dst "$@"

		set_work_function "Processing nfsd rules for server '${x}'"
		rules_custom "${mychain}" "${type}" nfs-nfsd   "${server_nfsd_ports}"   "500:65535" "${action}" $dst "$@"
		
		${RM_CMD} -f "${tmp}"
		
		echo >&2 ""
		echo >&2 "WARNING:"
		echo >&2 "This firewall must be restarted if NFS server ${x} is restarted!"
		echo >&2 ""
	done
	
	return 0
}


# --- NIS ----------------------------------------------------------------------
# These rules work for client access only!
#
# Pushing changes to slave servers won't work if these rules are active
# somewhere between the master and its slaves, because it is impossible to
# predict the ports where "yppush" will be listening on each push.
#
# Pulling changes directly on the slaves will work, and could be improved
# performance-wise if these rules are modified to open "fypxfrd". This wasn't
# done because it doesn't make that much sense since pushing changes on the
# master server is the most common, and recommended, way to replicate maps.
#
# Created by Carlos Rodrigues <crlf@users.sourceforge.net>
# Feature Requests item #1050951 <https://sourceforge.net/tracker/?func=detail&atid=487695&aid=1050951&group_id=58425>

rules_nis() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# This command requires in the client or route subcommands,
	# the first argument after the policy/action is a dst.
	
	local action="${1}"; shift
	local servers="localhost"
	
	if [ "${type}" = "client" -o ! "${work_cmd}" = "interface" ]
	then
		case "${1}" in
			dst|DST|destination|DESTINATION)
				shift
				local servers="${1}"
				shift
				;;
				
			*)
				error "Please re-phrase to: ${type} nis ${action} dst <NIS_SERVER> [other rules]"
				return 1
				;;
		esac
	fi
	
	local x=
	for x in ${servers}
	do
		local tmp="${FIREHOL_DIR}/firehol.rpcinfo.$$.${RANDOM}"	
		set_work_function "Getting RPC information from server '${x}'"
		
		rpcinfo -p ${x} >"${tmp}"
		if [ $? -gt 0 -o ! -s "${tmp}" ]
		then
			error "Cannot get rpcinfo from host '${x}' (using the previous firewall rules)"
			${RM_CMD} -f "${tmp}"
			return 1
		fi
		
		local server_ypserv_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " ypserv$"  | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		local server_yppasswdd_ports="`${CAT_CMD} "${tmp}" | ${GREP_CMD} " yppasswdd$"  | ( while read a b proto port s; do echo "$proto/$port"; done ) | ${SORT_CMD} | ${UNIQ_CMD}`"
		
		test -z "${server_ypserv_ports}" && error "Cannot find ypserv ports for nis server '${x}'" && return 1
		
		local dst=
		if [ ! "${x}" = "localhost" ]
		then
			dst="dst ${x}"
		fi
		
		if [ ! -z "${server_yppasswd_ports}" ]
		then
			set_work_function "Processing yppasswd rules for server '${x}'"
			rules_custom "${mychain}" "${type}" nis-yppasswd "${server_yppasswdd_ports}" "500:65535" "${action}" $dst "$@"
		fi
		
		set_work_function "Processing ypserv rules for server '${x}'"
		rules_custom "${mychain}" "${type}" nis-ypserv "${server_ypserv_ports}" "500:65535" "${action}" $dst "$@"
		
		${RM_CMD} -f "${tmp}"
		
		echo >&2 ""
		echo >&2 "WARNING:"
		echo >&2 "This firewall must be restarted if NIS server ${x} is restarted!"
		echo >&2 ""
	done
	
	return 0
}


# --- AMANDA -------------------------------------------------------------------
FIREHOL_AMANDA_PORTS="850:859"

rules_amanda() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	set_work_function "*** AMANDA: See http://amanda.sourceforge.net/fom-serve/cache/139.html"
	
	
	set_work_function "Setting up rules for initial amanda server-to-client connection"
	rule ${out}        action "$@" chain "${out}_${mychain}" proto "udp" dport 10080 state NEW,ESTABLISHED || return 1
	rule ${in} reverse action "$@" chain "${in}_${mychain}"  proto "udp" dport 10080 state ESTABLISHED     || return 1
	
	
	set_work_function "Setting up rules for amanda data exchange client-to-server"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "tcp udp" dport "${FIREHOL_AMANDA_PORTS}" state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "tcp udp" dport "${FIREHOL_AMANDA_PORTS}" state ESTABLISHED     || return 1
	
	return 0
}

# --- FTP ----------------------------------------------------------------------

ALL_SHOULD_ALSO_RUN="${ALL_SHOULD_ALSO_RUN} ftp"

rules_ftp() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# For an explanation of how FTP connections work, see
	# http://slacksite.com/other/ftp.html
	
	# ----------------------------------------------------------------------
	
	# allow new and established incoming, and established outgoing
	# accept port ftp new connections
	set_work_function "Setting up rules for initial FTP connection ${type}"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto tcp sport "${client_ports}" dport ftp state NEW,ESTABLISHED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${client_ports}" dport ftp state ESTABLISHED     || return 1
	
	# Active FTP
	# send port ftp-data related connections
	set_work_function "Setting up rules for Active FTP ${type}"
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${client_ports}" dport ftp-data state ESTABLISHED,RELATED || return 1
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto tcp sport "${client_ports}" dport ftp-data state ESTABLISHED         || return 1
	
	# ----------------------------------------------------------------------
	
	# A hack for Passive FTP only
	local s_client_ports="${DEFAULT_CLIENT_PORTS}"
	local c_client_ports="${DEFAULT_CLIENT_PORTS}"
	
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		c_client_ports="${LOCAL_CLIENT_PORTS}"
	elif [ "${type}" = "server" -a "${work_cmd}" = "interface" ]
	then
		s_client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# Passive FTP
	# accept high-ports related connections
	set_work_function "Setting up rules for Passive FTP ${type}"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto tcp sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED,RELATED || return 1
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto tcp sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED         || return 1
	
	require_kernel_module ip_conntrack_ftp
	test ${FIREHOL_NAT} -eq 1 && require_kernel_module ip_nat_ftp
	
	return 0
}


# --- TFTP ---------------------------------------------------------------------
# Written by: Goetz Bock <bock@blacknet.de>

rules_tftp() {
	local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ---------------------------------------------------------------------
	# TFTP is a broken protokol. It works like this:
	#
	# 1. The client sends from a high port (a) to the server's tftp port an
	#    udp packet with "give me file 'bla'".
	#
	# 2. The server replies from a high port (b) to the highport the client
	#    used (a) with "this is part 0 if your file"
	#
	# 3. The client now has to send a reply (from his highport a) to the
	#    servers high port (b): "got part 0, send next part 1".
	#
	# 4. repeat 2. and 3. till file transmitted
	
	# allow the initial TFTP connection
	set_work_function "Setting up rules for initial TFTP connection (${type})"
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport "${client_ports}" dport tftp state NEW,ESTABLISHED || return 1
#	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "${client_ports}" dport tftp state ESTABLISHED     || return 1
	
	# We now need both server and client port ranges
	local s_client_ports="${DEFAULT_CLIENT_PORTS}"
	local c_client_ports="${DEFAULT_CLIENT_PORTS}"
	
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		c_client_ports="${LOCAL_CLIENT_PORTS}"
	elif [ "${type}" = "server" -a "${work_cmd}" = "interface" ]
	then
		s_client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# allow the TFTP server to establish a new connection to the client
	set_work_function "Setting up rules for server-to-client TFTP connection (${type})"
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "udp" sport "${c_client_ports}" dport "${s_client_ports}" state RELATED,ESTABLISHED || return 1
	rule ${in}          action "$@" chain "${in}_${mychain}"  proto "udp" sport "${c_client_ports}" dport "${s_client_ports}" state ESTABLISHED         || return 1
	
	require_kernel_module ip_conntrack_tftp
	test ${FIREHOL_NAT} -eq 1 && require_kernel_module ip_nat_tftp
	
	return 0
}


# --- PING ---------------------------------------------------------------------

rules_ping() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow incoming new and established PING packets
	rule ${in} action "$@" chain "${in}_${mychain}" proto icmp custom "--icmp-type echo-request" state NEW,ESTABLISHED || return 1
	
	# allow outgoing established packets
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto icmp custom "--icmp-type echo-reply" state ESTABLISHED || return 1
	
	return 0
}
 
# --- TIMESTAMP ----------------------------------------------------------------

rules_timestamp() {
	local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow incoming new and established PING packets
	rule ${in} action "$@" chain "${in}_${mychain}" proto icmp custom "--icmp-type timestamp-request" state NEW,ESTABLISHED || return 1
	
	# allow outgoing established packets
	rule ${out} reverse action "$@" chain "${out}_${mychain}" proto icmp custom "--icmp-type timestamp-reply" state ESTABLISHED || return 1
	
	return 0
}

 
# --- P2P ----------------------------------------------------------------------

rules_p2p() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# Remove the action from the arguments.
	shift
	
	do_in() {
		# allow new and established incoming packets
		rule ${in} action "$@" chain "${in}_${mychain}" state NEW,ESTABLISHED || return 1
	}
	
	do_out() {
		# allow outgoing established packets
		rule ${out} reverse action "$@" chain "${out}_${mychain}" state NEW,ESTABLISHED || return 1
	}
	
	# Kazaa
	# Check: http://www.derkeiler.com/Mailing-Lists/Firewall-Wizards/2003-06/0152.html
	# New clients will try to use port 80 - use a proxy to filter this too.
	set_work_function "Setting up rules for Kazaa (${type})"
	do_in  drop "$@" proto "tcp udp" sport 1214
	do_in  drop "$@" proto "tcp udp" dport 1214
	do_out drop "$@" proto "tcp udp" dport 1214
	do_out drop "$@" proto "tcp udp" sport 1214
	
	# Gnutella
	
	# Mldonkey
	
	# Emule
	
	# audiogalaxy
	
	# hotline
	
	
	return 0
}


# --- ALL ----------------------------------------------------------------------

rules_all() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow new and established incoming packets
	rule ${in} action "$@" chain "${in}_${mychain}" state NEW,ESTABLISHED || return 1
	
	# allow outgoing established packets
	rule ${out} reverse action "$@" chain "${out}_${mychain}" state ESTABLISHED || return 1
	
	local ser=
	for ser in ${ALL_SHOULD_ALSO_RUN}
	do
		"${type}" ${ser} "$@" || return 1
	done
	
	return 0
}


# --- ANY ----------------------------------------------------------------------

rules_any() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	local name="${1}"; shift # a special case: service any gets a name
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow new and established incoming packets
	rule ${in} action "$@" chain "${in}_${mychain}" state NEW,ESTABLISHED || return 1
	
	# allow outgoing established packets
	rule ${out} reverse action "$@" chain "${out}_${mychain}" state ESTABLISHED || return 1
	
	return 0
}


# --- ANYSTATELESS -------------------------------------------------------------

rules_anystateless() {
	local mychain="${1}"; shift
	local type="${1}"; shift
	local name="${1}"; shift # a special case: service any gets a name
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# allow new and established incoming packets
	rule ${in} action "$@" chain "${in}_${mychain}" || return 1
	
	# allow outgoing established packets
	rule ${out} reverse action "$@" chain "${out}_${mychain}" || return 1
	
	return 0
}


# --- MULTICAST ----------------------------------------------------------------

rules_multicast() {
        local mychain="${1}"; shift
	local type="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	# match multicast packets in both directions
	rule ${out} action "$@" chain "${out}_${mychain}" dst "224.0.0.0/8" proto 2 || return 1
	rule ${in} reverse action "$@" chain "${in}_${mychain}" src "224.0.0.0/8" proto 2 || return 1
	
	return 0
}


# --- CUSTOM -------------------------------------------------------------------

rules_custom() {
	local mychain="${1}"; shift
	local type="${1}"; shift
	
	local server="${1}"; shift
	local my_server_ports="${1}"; shift
	local my_client_ports="${1}"; shift
	
	local in=in
	local out=out
	if [ "${type}" = "client" ]
	then
		in=out
		out=in
	fi
	
	local client_ports="${DEFAULT_CLIENT_PORTS}"
	if [ "${type}" = "client" -a "${work_cmd}" = "interface" ]
	then
		client_ports="${LOCAL_CLIENT_PORTS}"
	fi
	
	# ----------------------------------------------------------------------
	
	local sp=
	for sp in ${my_server_ports}
	do
		local proto=
		local sport=
		
		IFS="/" read proto sport <<EOF
$sp
EOF
		
		local cp=
		for cp in ${my_client_ports}
		do
			local cport=
			case ${cp} in
				default)
					cport="${client_ports}"
					;;
					
				*)	cport="${cp}"
					;;
			esac
			
			# allow new and established incoming packets
			rule ${in} action "$@" chain "${in}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" state NEW,ESTABLISHED || return 1
			
			# allow outgoing established packets
			rule ${out} reverse action "$@" chain "${out}_${mychain}" proto "${proto}" sport "${cport}" dport "${sport}" state ESTABLISHED || return 1
		done
	done
	
	return 0
}

# ------------------------------------------------------------------------------

# The caller may need just our services definitions
if [ "$1" = "gimme-the-services-defs" ]
then
	return 0
	exit 1
fi


# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# SUPPORT FOR EXTERNAL DEFINITIONS OF SERVICES
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

# Load all the services.
# All these files should start with: #FHVER: 1
cd "${FIREHOL_CONFIG_DIR}/services" || exit 1
for f in `ls *.conf 2>/dev/null`
do
	cd "${FIREHOL_CONFIG_DIR}/services" || exit 1
	
	if [ ! -O "${f}" ]
	then
		echo >&2 " >>> Ignoring service in '${FIREHOL_CONFIG_DIR}/services/${f}' because it is not owned by root."
		continue
	fi
	
	n=`"${HEAD_CMD}" -n 1 "${f}" | "${CUT_CMD}" -d ':' -f 2`
	"${EXPR_CMD}" ${n} + 0 >/dev/null 2>&1
	if [ $? -ne 0 ]
	then
		echo >&2 " >>> Ignoring service in '${FIREHOL_CONFIG_DIR}/services/${f}' due to malformed header."
	elif [ ${n} -ne ${FIREHOL_SERVICES_API} ]
	then
		echo >&2 " >>> Ignoring service '${FIREHOL_CONFIG_DIR}/services/${f}' due to incompatible API version."
	else
		n=`"${HEAD_CMD}" -n 1 "${f}" | "${CUT_CMD}" -d ':' -f 3`
		"${EXPR_CMD}" ${n} + 0 >/dev/null 2>&1
		if [ $? -ne 0 ]
		then
			echo >&2 " >>> Ignoring service in '${FIREHOL_CONFIG_DIR}/services/${f}' due to malformed API minor number."
		else
			if [ ${n} -gt ${FIREHOL_MINOR_VERSION} ]
			then
				echo >&2 " >>> Ignoring service in '${FIREHOL_CONFIG_DIR}/services/${f}' because the required MINOR version (${n}) is higher than the one provided by FireHOL (${FIREHOL_MINOR_VERSION})."
			else
				source ${f}
				ret=$?
				if [ ${ret} -ne 0 ]
				then
					echo >&2 " >>> Service in '${FIREHOL_CONFIG_DIR}/services/${f}' returned code ${ret}."
					continue
				fi
			fi
		fi
	fi
done
cd "${FIREHOL_DEFAULT_WORKING_DIRECTORY}" || exit 1


# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# HELPER FUNCTIONS BELLOW THIS POINT
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

# Fetch a URL and output it to the standard output.
firehol_wget() {
	local url="${1}"
	
	require_cmd wget curl

	if [ ! -z "${WGET_CMD}" ]
	then
		${WGET_CMD} -O - "${url}" 2>/dev/null
		return $?
	elif [ ! -z "${CURL_CMD}" ]
	then
		${CURL_CMD} -s "${url}"
		return $?
	fi
	
	error "Cannot use either 'wget' or 'curl' to fetch '${url}'."
	return 1
}

FIREHOL_ECN_SHAME_URL="http://urchin.earth.li/cgi-bin/ecn.pl?output=ip"
ecn_shame() {
	work_realcmd_helper ${FUNCNAME} "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	if [ `${CAT_CMD} /proc/sys/net/ipv4/tcp_ecn` -eq 1 ]
	then
		set_work_function "Fetching '${FIREHOL_ECN_SHAME_URL}'."
		
		# Reads in list of ip address and makes iptables rules to drop ecn
		# from packets destined for those hosts.
		# http://urchin.earth.li/ecn/
		
		local tmp="${FIREHOL_DIR}/ecn_shame.ips"
		
		firehol_wget "${FIREHOL_ECN_SHAME_URL}" | ${SORT_CMD} | ${UNIQ_CMD} >"${tmp}"
		if [ $? -ne 0 -o ! -s "${tmp}" ]
		then
			softwarning "Cannot fetch '${FIREHOL_ECN_SHAME_URL}'."
		else
			${MV_CMD} -f "${tmp}" "${FIREHOL_SPOOL_DIR}/ecn_shame.ips"
		fi
		
		set_work_function "Removing ECN for all communication from/to SHAME ECN list."
		
		local count=0
		for ip in `${CAT_CMD} "${FIREHOL_SPOOL_DIR}/ecn_shame.ips"`
		do
			local count=$[count + 1]
			iptables -t mangle -A POSTROUTING -p tcp -d ${ip} -j ECN --ecn-tcp-remove
		done
		
		test ${count} -eq 0 && softwarning "No ECN SHAME IPs found." && return 1
	else
		softwarning "TCP_ECN is not enabled in the kernel. ECN_SHAME helper is ignored."
		return 0
	fi
	return 0
}

masquerade() {
	work_realcmd_helper ${FUNCNAME} "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	local f="${work_outface}"
	test "${1}" = "reverse" && f="${work_inface}" && shift
	
	test -z "${f}" && local f="${1}" && shift
	
	test -z "${f}" && error "masquerade requires an interface set or as argument" && return 1
	
	set_work_function "Initializing masquerade on interface '${f}'"
	
	rule noowner table nat chain POSTROUTING "$@" inface any outface "${f}" action MASQUERADE || return 1
	
	FIREHOL_NAT=1
	FIREHOL_ROUTING=1
	
	return 0
}

transparent_proxy_count=0
transparent_proxy() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	local ports="${1}"; shift
	local redirect="${1}"; shift
	local user="${1}"; shift
	
	test -z "${redirect}" && error "Proxy listening port is empty" && return 1
	
	transparent_proxy_count=$[transparent_proxy_count + 1]
	
	set_work_function "Setting up rules for catching routed tcp/${ports} traffic"
	
	create_chain nat "in_trproxy.${transparent_proxy_count}" PREROUTING noowner "$@" outface any proto tcp sport "${DEFAULT_CLIENT_PORTS}" dport "${ports}" || return 1
#	rule table nat chain "in_trproxy.${transparent_proxy_count}" proto tcp dport "${ports}" action REDIRECT to-port ${redirect} || return 1
	rule table nat chain "in_trproxy.${transparent_proxy_count}" proto tcp action REDIRECT to-port ${redirect} || return 1
	
	if [ ! -z "${user}" ]
	then
		set_work_function "Setting up rules for catching outgoing tcp/${ports} traffic"
		create_chain nat "out_trproxy.${transparent_proxy_count}" OUTPUT "$@" uid not "${user}" nosoftwarnings inface any outface any src any proto tcp sport "${LOCAL_CLIENT_PORTS}" dport "${ports}" || return 1
		
		# do not catch traffic for localhost servers
		rule table nat chain "out_trproxy.${transparent_proxy_count}" dst "127.0.0.1" action RETURN || return 1
		
#		rule table nat chain "out_trproxy.${transparent_proxy_count}" proto tcp dport "${ports}" action REDIRECT to-port ${redirect} || return 1
		rule table nat chain "out_trproxy.${transparent_proxy_count}" proto tcp action REDIRECT to-port ${redirect} || return 1
	fi
	
	FIREHOL_NAT=1
	FIREHOL_ROUTING=1
	
	return 0
}

transparent_squid() {
	transparent_proxy 80 "$@"
}

nat_count=0
nat_helper() {
#	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "NAT cannot be used in '${work_cmd}'. Put all NAT related commands before any '${work_cmd}' definition."; return 1 )
	
	local type="${1}"; shift
	local to="${1}";   shift
	
	nat_count=$[nat_count + 1]
	
	set_work_function -ne "Setting up rules for NAT type: '${type}'"
	
	case ${type} in
		to-source)
			create_chain nat "nat.${nat_count}" POSTROUTING nolog "$@" inface any || return 1
			local action=snat
			;;
		
		to-destination)
			create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1
			local action=dnat
			;;
			
		redirect-to)
			create_chain nat "nat.${nat_count}" PREROUTING noowner nolog "$@" outface any || return 1
			local action=redirect
			;;
			
		*)
			error "$FUNCNAME requires a type (i.e. to-source, to-destination, redirect-to, etc) as its first argument. '${type}' is not understood."
			return 1
			;;
	esac
	
	
	set_work_function "Taking the NAT action: '${action}'"
	
	# we now need to keep the protocol
	rule table nat chain "nat.${nat_count}" noowner "$@" action "${action}" to "${to}" nosoftwarnings src any dst any inface any outface any sport any dport any || return 1
	
	FIREHOL_NAT=1
	FIREHOL_ROUTING=1
	
	return 0
}
nat() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	nat_helper "$@"
}


snat() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	local to="${1}"; shift
	test "${to}" = "to" && local to="${1}" && shift
	
	nat_helper "to-source" "${to}" "$@"
}

dnat() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	local to="${1}"; shift
	test "${to}" = "to" && local to="${1}" && shift
	
	nat_helper "to-destination" "${to}" "$@"
}

redirect() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	local to="${1}"; shift
	test "${to}" = "to" -o "${to}" = "to-port" && local to="${1}" && shift
	
	nat_helper "redirect-to" "${to}" "$@"
}

wrongmac_chain=0
mac() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	if [ ${wrongmac_chain} -eq 0 ]
	then
		set_work_function "Creating the MAC-MISSMATCH chain (only once)"
		
		iptables -t filter -N WRONGMAC
		rule table filter chain WRONGMAC loglimit "MAC MISSMATCH" action DROP || return 1
		
		wrongmac_chain=1
	fi
	
	set_work_function "If the source IP ${1} does not match MAC ${2}, drop the packet"
	
	iptables -t filter -A INPUT   -s "${1}" -m mac --mac-source ! "${2}" -j WRONGMAC
	iptables -t filter -A FORWARD -s "${1}" -m mac --mac-source ! "${2}" -j WRONGMAC
	
	return 0
}


# blacklist creates two types of blacklists: unidirectional or bidirectional
blacklist_chain=0
blacklist() {
	work_realcmd_helper ${FUNCNAME} "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	local full=1
	if [ "${1}" = "them" -o "${1}" = "him" -o "${1}" = "her" -o "${1}" = "it" -o "${1}" = "this" -o "${1}" = "these"  -o "${1}" = "input" ]
	then
		shift
		full=0
	elif [ "${1}" = "all" -o "${1}" = "full" ]
	then
		shift
		full=1
	fi
	
	if [ ${blacklist_chain} -eq 0 ]
	then
		set_work_function "Generating blacklist chains"
		
		# Blacklist INPUT unidirectional
		iptables -t filter -N BL_IN_UNI	# INPUT
		iptables -A BL_IN_UNI -m state --state NEW -j DROP
		
		# No need for OUTPUT/FORWARD unidirectional
		
		# Blacklist INPUT bidirectional
		iptables -t filter -N BL_IN_BI	# INPUT
		iptables -A BL_IN_BI -j DROP
		
		# Blacklist OUTPUT/FORWARD bidirectional
		iptables -t filter -N BL_OUT_BI	# OUTPUT and FORWARD
		iptables -A BL_OUT_BI -p tcp -j REJECT --reject-with tcp-reset
		iptables -A BL_OUT_BI -j REJECT --reject-with icmp-host-unreachable
		
		blacklist_chain=1
	fi
	
	set_work_function "Generating blacklist rules"
	
	local z=
	for z in $@
	do
		local x=
		for x in ${z}
		do
			set_work_function "Blacklisting '${x}'"
			
			if [ ${full} -eq 1 ]
			then
				iptables -I INPUT   -s ${x} -j BL_IN_BI
				iptables -I FORWARD -s ${x} -j BL_IN_BI
				
				iptables -I OUTPUT  -d ${x} -j BL_OUT_BI
				iptables -I FORWARD -d ${x} -j BL_OUT_BI  
			else
				iptables -I INPUT   -s ${x} -j BL_IN_UNI
				iptables -I FORWARD -s ${x} -j BL_IN_UNI
			fi
		done
	done
	
	return 0
}

mark_count=0
mark() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	local num="${1}"; shift
	local where="${1}"; shift
	test -z "${where}" && where=OUTPUT
	
	mark_count=$[mark_count + 1]
	
	set_work_function "Setting up rules for MARK"
	
	create_chain mangle "mark.${mark_count}" "${where}" "$@" || return 1

	iptables -t mangle -A "mark.${mark_count}" -j MARK --set-mark ${num}
	
	return 0
}

tos_count=0
tos() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	local num="${1}"; shift
	local where="${1}"; shift
	test -z "${where}" && where=OUTPUT
	
	tos_count=$[tos_count + 1]
	
	set_work_function "Setting up rules for TOS"
	
	create_chain mangle "tos.${tos_count}" "${where}" "$@" || return 1
	iptables -t mangle -A "tos.${tos_count}" -j TOS --set-tos ${num}
	
	return 0
}

dscp_count=0
dscp() {
	work_realcmd=($FUNCNAME "$@")
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	require_work clear || ( error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition."; return 1 )
	
	local value="${1}"; shift
	local class=""
	
	if [ "${value}" = "class" ]
	then
		local value=""
		local class="${1}"; shift
	fi
	
	local where="${1}"; shift
	test -z "${where}" && where=OUTPUT
	
	dscp_count=$[dscp_count + 1]
	
	set_work_function "Setting up rules for setting DSCP"
	
	create_chain mangle "dscp.${dscp_count}" "${where}" "$@" || return 1
	
	if [ ! -z "${class}" ]
	then
		iptables -t mangle -A "dscp.${dscp_count}" -j DSCP --set-dscp-class ${class}
	else
		iptables -t mangle -A "dscp.${dscp_count}" -j DSCP --set-dscp ${value}
	fi
	
	return 0
}

tcpmss() {
	work_realcmd_helper $FUNCNAME "$@"
	
	set_work_function -ne "Initializing $FUNCNAME"
	
	# work only if this helper is called before any primary command
	# or within routers.
	if [ -z "${work_cmd}" -o "${work_cmd}" = "router" ]
	then
		local chains="FORWARD"
		
		test ! -z "${work_cmd}" && chains="in_${work_name} out_${work_name}"
		
		for tcmpmss_chain in ${chains}
		do
			case $1 in
				auto)
					iptables -A "${tcmpmss_chain}" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
					;;
					
				[0-9]*)
					iptables -A "${tcmpmss_chain}" -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss $1
					;;
				
				*)
					error "$FUNCNAME requires either the word 'auto' or a numeric argument."
					return 1
					;;
			esac
		done
	else
		error "$FUNCNAME cannot be used in '${work_cmd}'. Put it before any '${work_cmd}' definition or in 'router' definitions."
		return 1
	fi
	
	return 0
}

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# INTERNAL FUNCTIONS BELLOW THIS POINT - Primary commands
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------


# ------------------------------------------------------------------------------
# Check the version required by the configuration file
# WHY:
# We have to make sure the configuration file has been written for this version
# of FireHOL. Note that the version command does not actually check the version
# of firehol.sh. It checks only its release number (R5 currently).

version() {
        work_realcmd_helper ${FUNCNAME} "$@"
	
	if [ ${1} -gt ${FIREHOL_VERSION} ]
	then
		error "Wrong version. FireHOL is v${FIREHOL_VERSION}, your script requires v${1}."
	fi
}


# ------------------------------------------------------------------------------
# PRIMARY COMMAND: interface
# Setup rules specific to an interface (physical or logical)

interface() {
        work_realcmd_primary ${FUNCNAME} "$@"
	
	# --- close any open command ---
	
	close_cmd || return 1
	
	
	# --- test prerequisites ---
	
	require_work clear || return 1
	set_work_function -ne "Initializing $FUNCNAME"
	
	
	# --- get paramaters and validate them ---
	
	# Get the interface
	local inface="${1}"; shift
	test -z "${inface}" && error "real interface is not set" && return 1
	
	# Get the name for this interface
	local name="${1}"; shift
	test -z "${name}" && error "$FUNCNAME name is not set" && return 1
	
	
	# --- do the job ---
	
	work_cmd="${FUNCNAME}"
	work_name="${name}"
	work_realcmd=("(unset)")
	
	set_work_function -ne "Initializing $FUNCNAME '${work_name}'"
	
	create_chain filter "in_${work_name}" INPUT in set_work_inface "$@" inface "${inface}" outface any || return 1
	create_chain filter "out_${work_name}" OUTPUT out set_work_outface reverse "$@" inface "${inface}" outface any || return 1
	
	return 0
}


router() {
        work_realcmd_helper ${FUNCNAME} "$@"
	
	# --- close any open command ---
	
	close_cmd || return 1
	
	
	# --- test prerequisites ---
	
	require_work clear || return 1
	set_work_function -ne "Initializing $FUNCNAME"
	
	
	# --- get paramaters and validate them ---
	
	# Get the name for this router
	local name="${1}"; shift
	test -z "${name}" && error "$FUNCNAME name is not set" && return 1
	
	
	# --- do the job ---
	
	work_cmd="${FUNCNAME}"
	work_name="${name}"
	work_realcmd=("(unset)")
	
	set_work_function -ne "Initializing $FUNCNAME '${work_name}'"
	
	create_chain filter "in_${work_name}" FORWARD in set_work_inface set_work_outface "$@" || return 1
	create_chain filter "out_${work_name}" FORWARD out reverse "$@" || return 1
	
	FIREHOL_ROUTING=1
	
	return 0
}

postprocess() {
#       work_realcmd_helper ${FUNCNAME} "$@"
	
	local check="error"
	test "A${1}" = "A-ne"   && shift && local check="none"
	test "A${1}" = "A-warn" && shift && local check="warn"
	
	test ${FIREHOL_DEBUG}   -eq 1 && local check="none"
	test ${FIREHOL_EXPLAIN} -eq 1 && local check="none"
	
	if [ ! ${check} = "none" ]
	then
		printf "runcmd '${check}' '${FIREHOL_LINEID}' " >>${FIREHOL_OUTPUT}
	fi
	
	printf "%q " "$@" >>${FIREHOL_OUTPUT}
	printf "\n" >>${FIREHOL_OUTPUT}
	
	if [ ${FIREHOL_EXPLAIN} -eq 1 ]
	then
		${CAT_CMD} ${FIREHOL_OUTPUT}
		${RM_CMD} -f ${FIREHOL_OUTPUT}
	fi
	
	return 0
}

runcmd() {
	local check="${1}"; shift
	local line="${1}"; shift
	local cmd="${1}"; shift
	
	"${cmd}" "$@" >${FIREHOL_OUTPUT}.log 2>&1
	local r=$?
	test ${r} -gt 0 && runtime_error ${check} ${r} ${line} "${cmd}" "$@"
	
	return 0
}

FIREHOL_COMMAND_COUNTER=0
iptables() {
#       work_realcmd_helper ${FUNCNAME} "$@"
	
	postprocess "${IPTABLES_CMD}" "$@"
	FIREHOL_COMMAND_COUNTER=$[FIREHOL_COMMAND_COUNTER + 1]
	
	return 0
}

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# INTERNAL FUNCTIONS BELLOW THIS POINT - Sub-commands
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------


# ------------------------------------------------------------------------------
# Change the policy of an interface
# WHY:
# Not all interfaces have the same policy. The admin must have control over it.
# Here we just set what the admin wants. At the interface finalization we
# produce the iptables rules.

policy() {
        work_realcmd_secondary ${FUNCNAME} "$@"
	
	require_work set interface || return 1
	
	set_work_function "Setting interface '${work_inface}' (${work_name}) policy to ${1}"
	work_policy="$*"
	
	return 0
}

server() {
	work_realcmd_secondary ${FUNCNAME} "$@"
	
	require_work set any || return 1
	smart_function server "$@"
	return $?
}

client() {
        work_realcmd_secondary ${FUNCNAME} "$@"
	
	require_work set any || return 1
	smart_function client "$@"
	return $?
}

route() {
        work_realcmd_secondary ${FUNCNAME} "$@"
	
	require_work set router || return 1
	smart_function server "$@"
	return $?
}


# --- protection ---------------------------------------------------------------

protection() {
        work_realcmd_secondary ${FUNCNAME} "$@"
	
	require_work set any || return 1
	
	local in="in"
	local prface="${work_inface}"
	
	local pre="pr"
	local reverse=
	if [ "${1}" = "reverse" ]
	then
		local reverse="reverse"	# needed to recursion
		local pre="prr"		# in case a router has protections
					# both ways, the second needs to
					# have different chain names
					
		local in="out"		# reverse the interface
		
		prface="${work_outface}"
		shift
	fi
	
	local type="${1}"
	local rate="${2}"
	local burst="${3}"
	
	test -z "${rate}"  && rate="100/s"
	test -z "${burst}" && burst="50"
	
	set_work_function -ne "Generating protections on '${prface}' for ${work_cmd} '${work_name}'"
	
	local x=
	for x in ${type}
	do
		case "${x}" in
			none|NONE)
				return 0
				;;
			
			strong|STRONG|full|FULL|all|ALL)
				protection ${reverse} "invalid fragments new-tcp-w/o-syn icmp-floods syn-floods malformed-xmas malformed-null malformed-bad" "${rate}" "${burst}"
				return $?
				;;
				
			invalid|INVALID)
				iptables -A "${in}_${work_name}" -m state --state INVALID -j DROP				|| return 1
				;;
				
			fragments|FRAGMENTS)
				local mychain="${pre}_${work_name}_fragments"
				create_chain filter "${mychain}" "${in}_${work_name}" in custom "-f"				|| return 1
				
				set_work_function "Generating rules to be protected from packet fragments on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" loglimit "PACKET FRAGMENTS" action drop 				|| return 1
				;;
				
			new-tcp-w/o-syn|NEW-TCP-W/O-SYN)
				local mychain="${pre}_${work_name}_nosyn"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp state NEW custom "! --syn"	|| return 1
				
				set_work_function "Generating rules to be protected from new TCP connections without the SYN flag set on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" loglimit "NEW TCP w/o SYN" action drop				|| return 1
				;;
				
			icmp-floods|ICMP-FLOODS)
				local mychain="${pre}_${work_name}_icmpflood"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto icmp custom "--icmp-type echo-request"	|| return 1
				
				set_work_function "Generating rules to be protected from ICMP floods on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" limit "${rate}" "${burst}" action return				|| return 1
				rule in chain "${mychain}" loglimit "ICMP FLOOD" action drop					|| return 1
				;;
				
			syn-floods|SYN-FLOODS)
				local mychain="${pre}_${work_name}_synflood"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--syn"		|| return 1
				
				set_work_function "Generating rules to be protected from TCP SYN floods on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" limit "${rate}" "${burst}" action return				|| return 1
				rule in chain "${mychain}" loglimit "SYN FLOOD" action drop					|| return 1
				;;

			all-floods|ALL-FLOODS)
				local mychain="${pre}_${work_name}_allflood"
				create_chain filter "${mychain}" "${in}_${work_name}" in state NEW		|| return 1
				
				set_work_function "Generating rules to be protected from ALL floods on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" limit "${rate}" "${burst}" action return				|| return 1
				rule in chain "${mychain}" loglimit "ALL FLOOD" action drop					|| return 1
				;;
				
			malformed-xmas|MALFORMED-XMAS)
				local mychain="${pre}_${work_name}_malxmas"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL ALL"	|| return 1
				
				set_work_function "Generating rules to be protected from packets with all TCP flags set on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" loglimit "MALFORMED XMAS" action drop				|| return 1
				;;
				
			malformed-null|MALFORMED-NULL)
				local mychain="${pre}_${work_name}_malnull"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags ALL NONE" || return 1
				
				set_work_function "Generating rules to be protected from packets with all TCP flags unset on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${mychain}" loglimit "MALFORMED NULL" action drop				|| return 1
				;;
				
			malformed-bad|MALFORMED-BAD)
				local mychain="${pre}_${work_name}_malbad"
				create_chain filter "${mychain}" "${in}_${work_name}" in proto tcp custom "--tcp-flags SYN,FIN SYN,FIN"			|| return 1
				
				set_work_function "Generating rules to be protected from packets with illegal TCP flags on '${prface}' for ${work_cmd} '${work_name}'"
				
				rule in chain "${in}_${work_name}" action "${mychain}"   proto tcp custom "--tcp-flags SYN,RST SYN,RST"			|| return 1
				rule in chain "${in}_${work_name}" action "${mychain}"   proto tcp custom "--tcp-flags ALL     SYN,RST,ACK,FIN,URG"	|| return 1
				rule in chain "${in}_${work_name}" action "${mychain}"   proto tcp custom "--tcp-flags ALL     FIN,URG,PSH"		|| return 1
				
				rule in chain "${mychain}" loglimit "MALFORMED BAD" action drop								|| return 1
				;;
				
			*)
				error "Protection '${x}' does not exists."
				return 1
				;;
		esac
	done
	
	return 0
}


# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# KERNEL MODULE MANAGEMENT
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# Manage kernel modules
# WHY:
# We need to load a set of kernel modules during postprocessing, and after the
# new firewall has been activated. Here we just keep a list of the required
# kernel modules.

# optionaly require command gzcat
require_cmd -n gzcat

KERNEL_CONFIG=
if [ -f "/proc/config" ]
then
	KERNEL_CONFIG="/proc/config"
	${CAT_CMD} /proc/config >${FIREHOL_DIR}/kcfg
	source ${FIREHOL_DIR}/kcfg
	${RM_CMD} -f ${FIREHOL_DIR}/kcfg	
elif [ -f "/proc/config.gz" -a ! -z "${GZCAT_CMD}" ]
then
	KERNEL_CONFIG="/proc/config.gz"
	${GZCAT_CMD} /proc/config.gz >${FIREHOL_DIR}/kcfg
	source ${FIREHOL_DIR}/kcfg
	${RM_CMD} -f ${FIREHOL_DIR}/kcfg

elif [ -f "/lib/modules/`${UNAME_CMD} -r`/build/.config" ]
then
	KERNEL_CONFIG="/lib/modules/`${UNAME_CMD} -r`/build/.config"
	. "${KERNEL_CONFIG}"
	
elif [ -f "/boot/config-`${UNAME_CMD} -r`" ]
then
	KERNEL_CONFIG="/boot/config-`${UNAME_CMD} -r`"
	. "${KERNEL_CONFIG}"
	
elif [ -f "/usr/src/linux/.config" ]
then
	KERNEL_CONFIG="/usr/src/linux/.config"
	. "${KERNEL_CONFIG}"
else
	echo >&2 " "
	echo >&2 " IMPORTANT WARNING:"
	echo >&2 " ------------------"
	echo >&2 " FireHOL cannot find your current kernel configuration."
	echo >&2 " Please, either compile your kernel with /proc/config,"
	echo >&2 " or make sure there is a valid kernel config in"
	echo >&2 " /lib/modules/`${UNAME_CMD} -r`/build/.config,"
	echo >&2 " /usr/src/linux/.config or /boot/config-`${UNAME_CMD} -r`"
	echo >&2 " "
	echo >&2 " Because of this, FireHOL will simply attempt to load"
	echo >&2 " all kernel modules for the services used, without"
	echo >&2 " being able to detect failures."
	echo >&2 " "
	sleep 2
fi

# activation-phase command to check for the existance of
# a kernel configuration directive. It returns:
# 0 = module is already in the kernel
# 1 = module can be loaded with modprobe
# 2 = no info about this module in the kernel
check_kernel_config() {
	eval local kcfg="\$${1}"
	
	case ${kcfg} in
		y)	return 0
			;;
		
		m)	return 1
			;;
		
		*)	return 2
			;;
	esac
	
	return 2
}

# activation-phase command to check for the existance of
# a kernel module. It returns:
# 0 = module is already in the kernel
# 1 = module can be loaded with modprobe
# 2 = no info about this module in the kernel
check_kernel_module() {
	local mod="${1}"
	
	case ${mod} in
		ip_tables)
			test -f /proc/net/ip_tables_names && return 0
			check_kernel_config CONFIG_IP_NF_IPTABLES
			return $?
			;;
		
		ip_conntrack)
			test -f /proc/net/ip_conntrack && return 0
			check_kernel_config CONFIG_IP_NF_CONNTRACK
			return $?
			;;
			
		ip_conntrack_*)
			local mnam="CONFIG_IP_NF_`echo ${mod} | ${CUT_CMD} -d '_' -f 3- | ${TR_CMD} a-z A-Z`"
			check_kernel_config ${mnam}
			return $?
			;;
			
		ip_nat_*)
			local mnam="CONFIG_IP_NF_NAT_`echo ${mod} | ${CUT_CMD} -d '_' -f 3- | ${TR_CMD} a-z A-Z`"
			check_kernel_config ${mnam}
			return $?
			;;
			
		*)
			return 2
			;;
	esac
	
	return 2
}

# activation-phase command to load a kernel module.
load_kernel_module() {
	local mod="${1}"
	
	if [ ! ${FIREHOL_LOAD_KERNEL_MODULES} -eq 0 ]
	then
		if [ -x $MODPROBE_CMD ]; then
			check_kernel_module ${mod}
			if [ $? -gt 0 ]
			then
			runcmd warn ${FIREHOL_LINEID} ${MODPROBE_CMD} ${mod} -q
			fi
		else 
			echo >&2 "Can't load kernel module, can't execute $MODPROBE_CMD."
			echo >&2 "Please install modutils or module-init-tools."
		fi
	fi
	return 0
}

# Processing-phase command to tell FireHOL to find one or more
# kernel modules to load, during activation-phase.
require_kernel_module() {
	local new="${1}"
	
	local m=
	for m in ${FIREHOL_KERNEL_MODULES}
	do
		test "${m}" = "${new}" && return 0
	done
	
	FIREHOL_KERNEL_MODULES="${FIREHOL_KERNEL_MODULES} ${new}"
	
	return 0
}

# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------
#
# INTERNAL FUNCTIONS BELLOW THIS POINT - FireHOL internals
#
# ------------------------------------------------------------------------------
# XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ------------------------------------------------------------------------------

set_work_function() {
	local show_explain=1
	test "$1" = "-ne" && shift && local show_explain=0
	
	work_function="$*"
	
	if [ ${FIREHOL_EXPLAIN} -eq 1 ]
	then
		test ${show_explain} -eq 1 && printf "\n# %s\n" "$*"
	elif [ ${FIREHOL_CONF_SHOW} -eq 1 ]
	then
		test ${show_explain} -eq 1 && printf "\n# INFO>>> %s\n" "$*" >>${FIREHOL_OUTPUT}
	fi
}

# ------------------------------------------------------------------------------
# Check the status of the current primary command.
# WHY:
# Some sanity check for the order of commands in the configuration file.
# Each function has a "require_work type command" in order to check that it is
# placed in a valid point. This means that if you place a "route" command in an
# interface section (and many other combinations) it will fail.

require_work() {
	local type="${1}"
	local cmd="${2}"
	
	case "${type}" in
		clear)
			test ! -z "${work_cmd}" && error "Previous work was not applied." && return 1
			;;
		
		set)
			test -z "${work_cmd}" && error "The command used requires that a primary command is set." && return 1
			test ! "${work_cmd}" = "${cmd}" -a ! "${cmd}" = "any"  && error "Primary command is '${work_cmd}' but '${cmd}' is required." && return 1
			;;
			
		*)
			error "Unknown work status '${type}'."
			return 1
			;;
	esac
	
	return 0
}


# ------------------------------------------------------------------------------
# Finalizes the rules of the last primary command.
# WHY:
# At the end of an interface or router we need to add some code to apply its
# policy, accept all related packets, etc.
# Finalization occures automatically when a new primary command is executed and
# when the configuration file finishes.

close_cmd() {
	set_work_function -ne "Closing last open primary command (${work_cmd}/${work_name})"
	
	case "${work_cmd}" in
		interface)
			close_interface || return 1
			;;
		
		router)
			close_router || return 1
			;;
		
		'')
			;;
		
		*)
			error "Unknown work '${work_cmd}'."
			return 1
			;;
	esac
	
	# Reset the current status variables to empty/default
	work_counter=0
	work_cmd=
	work_realcmd=("(unset)")
	work_name=
	work_inface=
	work_outface=
	work_policy="${DEFAULT_INTERFACE_POLICY}"
	
	return 0
}

# ------------------------------------------------------------------------------
# close_interface
# WHY:
# Finalizes the rules for the last interface().

close_interface() {
	require_work set interface || return 1
	
	close_all_groups
	
	set_work_function "Finilizing interface '${work_name}'"
	
	case "${work_policy}" in
		return|RETURN)
			return 0
			;;
			
		accept|ACCEPT)
			;;
		
		*)
			local -a inlog=(loglimit "'IN-${work_name}'")
			local -a outlog=(loglimit "'OUT-${work_name}'")
			;;
	esac
	
	# Accept all related traffic to the established connections
	rule chain "in_${work_name}" state RELATED action ACCEPT || return 1
	rule chain "out_${work_name}" state RELATED action ACCEPT || return 1
	
	rule chain "in_${work_name}" "${inlog[@]}" action ${work_policy} || return 1
	rule reverse chain "out_${work_name}" "${outlog[@]}" action ${work_policy} || return 1
	
	return 0
}


# ------------------------------------------------------------------------------
# close_router
# WHY:
# Finalizes the rules for the last router().

close_router() {	
	require_work set router || return 1
	
	close_all_groups
	
	set_work_function "Finilizing router '${work_name}'"
	
	# Accept all related traffic to the established connections
	rule chain "in_${work_name}" state RELATED action ACCEPT || return 1
	rule chain "out_${work_name}" state RELATED action ACCEPT || return 1
	
	return 0
}


# ------------------------------------------------------------------------------
# close_master
# WHY:
# Finalizes the rules for the whole firewall.
# It assummes there is not primary command open.

close_master() {
	set_work_function "Finilizing firewall policies"
	
	# Accept all related traffic to the established connections
	rule chain INPUT state RELATED action ACCEPT || return 1
	rule chain OUTPUT state RELATED action ACCEPT || return 1
	rule chain FORWARD state RELATED action ACCEPT || return 1
	
	rule chain INPUT loglimit "IN-unknown" action ${UNMATCHED_INPUT_POLICY} || return 1
	rule chain OUTPUT loglimit "OUT-unknown" action ${UNMATCHED_OUTPUT_POLICY} || return 1
	rule chain FORWARD loglimit "PASS-unknown" action ${UNMATCHED_ROUTER_POLICY} || return 1
	return 0
}


FIREHOL_GROUP_COUNTER=0
FIREHOL_GROUP_DEPTH=0
FIREHOL_GROUP_STACK=()
group() {
        work_realcmd_primary ${FUNCNAME} "$@"
	
	require_work set any || return 1
	
	local type="${1}"; shift
	
	case $type in
		with|start|begin)
			# increase the counter
			FIREHOL_GROUP_COUNTER=$[FIREHOL_GROUP_COUNTER + 1]
			
			set_work_function "Starting new group No ${FIREHOL_GROUP_COUNTER}, under '${work_name}'"
			
			# put the current name in the stack
			FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]=${work_name}
			FIREHOL_GROUP_DEPTH=$[FIREHOL_GROUP_DEPTH + 1]
			
			# name for the new chain
			mychain="group${FIREHOL_GROUP_COUNTER}"
			
			# create the new chain
			create_chain filter "in_${mychain}" "in_${work_name}" in "$@" || return 1
			create_chain filter "out_${mychain}" "out_${work_name}" out reverse "$@" || return 1
			
			# set a new name for new rules
			work_name=${mychain}
			;;
		
		end|stop|close)
			if [ ${FIREHOL_GROUP_DEPTH} -eq 0 ]
			then
				error "There is no group open to close."
				return 1
			fi
			
			# pop one name from the stack
			FIREHOL_GROUP_DEPTH=$[FIREHOL_GROUP_DEPTH - 1]
			
			set_work_function "Closing group '${work_name}'. Now working under '${FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]}'"
			
			work_name=${FIREHOL_GROUP_STACK[$FIREHOL_GROUP_DEPTH]}
			;;
		
		*)
			error "Statement 'group' requires the first argument to be one of with, start, begin, end, stop, close."
			return 1
			;;
	esac
	
	return 0
}

close_all_groups() {
	while [ ${FIREHOL_GROUP_DEPTH} -gt 0 ]
	do
		group close || return 1
	done
	
	return 0
}


# ------------------------------------------------------------------------------
# rule - the heart of FireHOL - iptables commands generation
# WHY:
# This is the function that gives all the magic to FireHOL. Actually it is a
# wrapper for iptables, producing multiple iptables commands based on its
# arguments. The rest of FireHOL is simply a "driver" for this function.


# rule_action_param() is a function - part of rule() - to create the final iptables cmd
# taking into account the "action_param" parameter of the action.

# rule_action_param() should only be used within rule() - no other place

FIREHOL_ACCEPT_CHAIN_COUNT=0
rule_action_param() {
	local action="${1}"; shift
	local protocol="${1}"; shift
	local statenot="${1}"; shift
	local state="${1}"; shift
	local table="${1}"; shift
	local -a action_param=()
	
	# All arguments until the separator are the parameters of the action
	local count=0
	while [ ! -z "${1}" -a ! "A${1}" = "A--" ]
	do
		action_param[$count]="${1}"
		shift
		
		count=$[count + 1]
	done

	# If we don't have a seperator, generate an error
	local sep="${1}"; shift
	if [ ! "A${sep}" = "A--" ]
	then
		error "Internal Error, in parsing action_param parameters ($FUNCNAME '${action}' '${protocol}' '${statenot}' '${state}' '${table}' '${action_param[@]}' ${sep} '$@')."
		return 1
	fi
	
	# Do the rule
	case "${action}" in
		NONE)
			return 0
			;;

		ACCEPT)
			# do we have any options for this accept?
			if [ ! -z "${action_param[0]}" ]			
			then
				# find the options we have
				case "${action_param[0]}" in
					"limit")
						# limit NEW connections to the specified rate
						local freq="${action_param[1]}"
						local burst="${action_param[2]}"
						local overflow="REJECT"
						
						# if we have a custom overflow action, parse it.
						test "${action_param[3]}" = "overflow" && local overflow="`echo "${action_param[4]}" | tr "a-z" "A-Z"`"
						
						# unset the action_param, so that if this rule does not include NEW connections,
						# we will not append anything to the generated iptables statements.
						local -a action_param=()
						
						# find is this rule matches NEW connections
						local has_new=`echo "${state}" | grep -i NEW`
						local do_accept_limit=0
						if [ -z "${statenot}" ]
						then
							test ! -z "${has_new}" && local do_accept_limit=1
						else
							test -z "${has_new}" && local do_accept_limit=1
						fi
						
						# we have a match for NEW connections.
						# redirect the traffic to a new chain, which will control
						# the NEW connections while allowing all the other traffic
						# to pass.
						if [ "${do_accept_limit}" = "1" ]
						then
							local accept_limit_chain="`echo "ACCEPT LIMIT ${freq} ${burst} ${overflow}" | tr " /." "___"`"
							
							# does the chain we need already exist?
							if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}" ]
							then
								# the chain does not exist. create it.
								iptables ${table} -N "${accept_limit_chain}"
								touch "${FIREHOL_CHAINS_DIR}/${accept_limit_chain}"
								
								# first, if the traffic is not a NEW connection, allow it.
								# doing this first will speed up normal traffic.
								iptables ${table} -A "${accept_limit_chain}" -m state ! --state NEW -j ACCEPT
								
								# accept NEW connections within the given limits.
								iptables ${table} -A "${accept_limit_chain}" -m limit --limit "${freq}" --limit-burst "${burst}" -j ACCEPT
								
								# log the overflow NEW connections reaching this step within the new chain
								local -a logopts_arg=()
								if [ "${FIREHOL_LOG_MODE}" = "ULOG" ]
								then
									 local -a logopts_arg=("--ulog-prefix='${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:'")
								else
									local -a logopts_arg=("--log-level" "${FIREHOL_LOG_LEVEL}" "--log-prefix='${FIREHOL_LOG_PREFIX}LIMIT_OVERFLOW:'")
								fi
								iptables ${table} -A "${accept_limit_chain}" -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}"
								
								# if the overflow is to be rejected is tcp, reject it with TCP-RESET
								if [ "${overflow}" = "REJECT" ]
								then
									iptables ${table} -A "${accept_limit_chain}" -p tcp -j REJECT --reject-with tcp-reset
								fi
								
								# do the specified action on the overflow
								iptables ${table} -A "${accept_limit_chain}" -j ${overflow}
							fi
							
							# send the rule to be generated to this chain
							local action=${accept_limit_chain}
						fi
						;;
					"recent")
						# limit NEW connections to the specified rate
						local name="${action_param[1]}"
						local seconds="${action_param[2]}"
						local hits="${action_param[3]}"

						# unset the action_param, so that if this rule does not include NEW connections,
						# we will not append anything to the generated iptables statements.
						local -a action_param=()

						# find is this rule matches NEW connections
						local has_new=`echo "${state}" | grep -i NEW`
						local do_accept_recent=0
						if [ -z "${statenot}" ]
						then
							test ! -z "${has_new}" && local do_accept_recent=1
						else
							test -z "${has_new}" && local do_accept_recent=1
						fi

						# we have a match for NEW connections.
						# redirect the traffic to a new chain, which will control
						# the NEW connections while allowing all the other traffic
						# to pass.
						if [ "${do_accept_recent}" = "1" ]
						then
							local accept_recent_chain="`echo "ACCEPT RECENT $name $seconds $hits" | tr " /." "___"`"
							# does the chain we need already exist?
							if [ ! -f "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}" ]
							then
								# the chain does not exist. create it.
								iptables ${table} -N "${accept_recent_chain}"
								touch "${FIREHOL_CHAINS_DIR}/${accept_recent_chain}"

								# first, if the traffic is not a NEW connection, allow it.
								# doing this first will speed up normal traffic.
								iptables ${table} -A "${accept_recent_chain}" -m state ! --state NEW -j ACCEPT
								# accept NEW connections within the given limits.
								iptables ${table} -A "${accept_recent_chain}" -m recent --set --name "${name}"
								local t1=
								test ! -z $seconds && local t1="--seconds ${seconds}"
								local t2=
								test ! -z $hits && local t2="--hitcount ${hits}"
								iptables ${table} -A "${accept_recent_chain}" -m recent --update ${t1} ${t2} --name "${name}" -j RETURN
								iptables ${table} -A "${accept_recent_chain}" -j ACCEPT
							fi

							# send the rule to be generated to this chain
							local action=${accept_recent_chain}
						fi
						;;
					'knock')
						# the name of the knock
						local name="knock_${action_param[1]}"
						
						# unset the action_param, so that if this rule does not include NEW connections,
						# we will not append anything to the generated iptables statements.
						local -a action_param=()
						
						# does the knock chain exists?
						if [ ! -f "${FIREHOL_CHAINS_DIR}/${name}" ]
						then
							# the chain does not exist. create it.
							iptables ${table} -N "${name}"
							touch "${FIREHOL_CHAINS_DIR}/${name}"
							
							iptables -A "${name}" -m state --state ESTABLISHED -j ACCEPT
							
							# knockd (http://www.zeroflux.org/knock/)
							# will create more rules inside this chain to match NEW packets.
						fi
						
						# send the rule to be generated to this knock chain
						local action=${name}
						;;
						
					*)
						error "Internal error. Cannot understand action ${action} with parameter '${action_param[0]}'."
						return 1
						;;
				esac
			fi
			;;
			
		REJECT)
			if [ "${action_param[1]}" = "auto" ]
			then
				if [ "${protocol}" = "tcp" -o "${protocol}" = "TCP" ]
				then
					action_param=("--reject-with" "tcp-reset")
				else
					action_param=()
				fi
			fi
			;;
	esac
	
	iptables "$@" -j "${action}" "${action_param[@]}"
	local ret=$?
	
	test $ret -gt 0 && failed=$[failed + 1]
	
	return $ret
}

rule() {
	local table=
	local chain=
	
	local inface=any
	local infacenot=
	
	local outface=any
	local outfacenot=
	
	local physin=any
	local physinnot=
	
	local physout=any
	local physoutnot=
	
	local mac=any
	local macnot=
	
	local src=any
	local srcnot=
	
	local dst=any
	local dstnot=
	
	local sport=any
	local sportnot=
	
	local dport=any
	local dportnot=
	
	local proto=any
	local protonot=
	
	local uid=any
	local uidnot=
	
	local gid=any
	local gidnot=
	
	local pid=any
	local pidnot=
	
	local sid=any
	local sidnot=
	
	local cmd=any
	local cmdnot=
	
	local mark=any
	local marknot=
	
	local dscp=any
	local dscptype=
	local despnot=
	
	local tos=any
	local tosnot=
	
	local log=
	local logtxt=
	local loglevel=
	
	local limit=
	local burst=
	
	local iplimit=
	local iplimit_mask=
	
	local action=
	
	local state=
	local statenot=
	
	local failed=0
	local reverse=0
	
	local swi=0
	local swo=0
	
	local custom=
	
	# if set to 1, all owner module options will be ignored
	local noowner=0
	
	# if set to 1, all mac options will be ignored
	local nomac=0
	
	# if set to 1, MIRROR will be converted to REJECT
	local nomirror=0
	
	# if set to 1, log and loglimit are ignored.
	local nolog=0
	
	# if set to 1, detection algorithm about overwritting optional rule
	# parameters will take place.
	local softwarnings=1
	
	# set it, in order to be local
	local -a action_param=()
	
	while [ ! -z "${1}" ]
	do
		case "${1}" in
			reverse|REVERSE)
				reverse=1
				shift
				;;
				
			table|TABLE)
				test ${softwarnings} -eq 1 -a ! -z "${table}" && softwarning "Overwritting param: ${1} '${chain}' becomes '${2}'"
				table="-t ${2}"
				shift 2
				;;
				
			chain|CHAIN)
				test ${softwarnings} -eq 1 -a ! -z "${chain}" && softwarning "Overwritting param: ${1} '${chain}' becomes '${2}'"
				chain="${2}"
				shift 2
				;;
				
			inface|INFACE)
				shift
				if [ ${reverse} -eq 0 ]
				then
					infacenot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						infacenot="!"
					else
						if [ $swi -eq 1 ]
						then
							work_inface="${1}"
						fi
					fi
					test ${softwarnings} -eq 1 -a ! "${inface}" = "any" && softwarning "Overwritting param: inface '${inface}' becomes '${1}'"
					inface="${1}"
				else
					outfacenot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						outfacenot="!"
					else
						if [ ${swo} -eq 1 ]
						then
							work_outface="$1"
						fi
					fi
					test ${softwarnings} -eq 1 -a ! "${outface}" = "any" && softwarning "Overwritting param: outface '${outface}' becomes '${1}'"
					outface="${1}"
				fi
				shift
				;;
				
			outface|OUTFACE)
				shift
				if [ ${reverse} -eq 0 ]
				then
					outfacenot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						outfacenot="!"
					else
						if [ ${swo} -eq 1 ]
						then
							work_outface="${1}"
						fi
					fi
					test ${softwarnings} -eq 1 -a ! "${outface}" = "any" && softwarning "Overwritting param: outface '${outface}' becomes '${1}'"
					outface="${1}"
				else
					infacenot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						infacenot="!"
					else
						if [ ${swi} -eq 1 ]
						then
							work_inface="${1}"
						fi
					fi
					test ${softwarnings} -eq 1 -a ! "${inface}" = "any" && softwarning "Overwritting param: inface '${inface}' becomes '${1}'"
					inface="${1}"
				fi
				shift
				;;
				
			physin|PHYSIN)
				shift
				if [ ${reverse} -eq 0 ]
				then
					physinnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						physinnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${physin}" = "any" && softwarning "Overwritting param: physin '${physin}' becomes '${1}'"
					physin="${1}"
				else
					physoutnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						physoutnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${physout}" = "any" && softwarning "Overwritting param: physout '${physout}' becomes '${1}'"
					physout="${1}"
				fi
				shift
				;;
				
			physout|PHYSOUT)
				shift
				if [ ${reverse} -eq 0 ]
				then
					physoutnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						physoutnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${physout}" = "any" && softwarning "Overwritting param: physout '${physout}' becomes '${1}'"
					physout="${1}"
				else
					physinnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						physinnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${physin}" = "any" && softwarning "Overwritting param: physin '${physin}' becomes '${1}'"
					physin="${1}"
				fi
				shift
				;;
				
			mac|MAC)
				shift
				macnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					macnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${mac}" = "any" && softwarning "Overwritting param: mac '${mac}' becomes '${1}'"
				test ${nomac} -eq 0 && mac="${1}"
				shift
				;;
				
			src|SRC|source|SOURCE)
				shift
				if [ ${reverse} -eq 0 ]
				then
					srcnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						srcnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${src}" = "any" && softwarning "Overwritting param: src '${src}' becomes '${1}'"
					src="${1}"
				else
					dstnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						dstnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${dst}" = "any" && softwarning "Overwritting param: dst '${dst}' becomes '${1}'"
					dst="${1}"
				fi
				shift
				;;
				
			dst|DST|destination|DESTINATION)
				shift
				if [ ${reverse} -eq 0 ]
				then
					dstnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						dstnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${dst}" = "any" && softwarning "Overwritting param: dst '${dst}' becomes '${1}'"
					dst="${1}"
				else
					srcnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						srcnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${src}" = "any" && softwarning "Overwritting param: src '${src}' becomes '${1}'"
					src="${1}"
				fi
				shift
				;;
				
			sport|SPORT|sourceport|SOURCEPORT)
				shift
				if [ ${reverse} -eq 0 ]
				then
					sportnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						sportnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${sport}" = "any" && softwarning "Overwritting param: sport '${sport}' becomes '${1}'"
					sport="${1}"
				else
					dportnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						dportnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${dport}" = "any" && softwarning "Overwritting param: dport '${dport}' becomes '${1}'"
					dport="${1}"
				fi
				shift
				;;
				
			dport|DPORT|destinationport|DESTINATIONPORT)
				shift
				if [ ${reverse} -eq 0 ]
				then
					dportnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						dportnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${dport}" = "any" && softwarning "Overwritting param: dport '${dport}' becomes '${1}'"
					dport="${1}"
				else
					sportnot=
					if [ "${1}" = "not" -o "${1}" = "NOT" ]
					then
						shift
						sportnot="!"
					fi
					test ${softwarnings} -eq 1 -a ! "${sport}" = "any" && softwarning "Overwritting param: sport '${sport}' becomes '${1}'"
					sport="${1}"
				fi
				shift
				;;
				
			proto|PROTO|protocol|PROTOCOL)
				shift
				protonot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					protonot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${proto}" = "any" && softwarning "Overwritting param: proto '${proto}' becomes '${1}'"
				proto="${1}"
				shift
				;;
				
			mark|MARK)
				shift
				marknot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					marknot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${mark}" = "any" && softwarning "Overwritting param: mark '${mark}' becomes '${1}'"
				mark="${1}"
				shift
				;;
				
			tos|TOS)
				shift
				tosnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					tosnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${tos}" = "any" && softwarning "Overwritting param: tos '${tos}' becomes '${1}'"
				tos="${1}"
				shift
				;;
				
			dscp|DSCP)
				shift
				dscpnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					dscpnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${dscp}" = "any" && softwarning "Overwritting param: dscp '${dscp}' becomes '${1}'"
				dscp="${1}"
				shift
				
				if [ "${dscp}" = "class" ]
				then
					dscptype="-class"
					dscp="${1}"
					shift
				fi
				;;
				
			action|ACTION)
				test ${softwarnings} -eq 1 -a ! -z "${action}" && softwarning "Overwritting param: action '${action}' becomes '${2}'"
				action="${2}"
				shift 2
				
				local -a action_param=()
				local action_is_chain=0
				case "${action}" in
					accept|ACCEPT)
						action="ACCEPT"
						
						if [ "${1}" = "with" ]
						then
							shift
							
							case "${1}" in
								limit|LIMIT)
									local -a action_param=("limit" "${2}" "${3}")
									shift 3
									
									if [ "${1}" = "overflow" ]
									then
										action_param[3]="overflow"
										action_param[4]="${2}"
										shift 2
									fi
									;;

								recent|RECENT)
									local -a action_param=("recent" "${2}" "${3}" "${4}")
									shift 4
									;;

								knock|KNOCK)
									local -a action_param=("knock" "${2}")
									shift 2
									;;

								*)
									error "Cannot understand action's '${action}' directive '${1}'"
									return 1
									;;
							esac
						fi
						;;
						
					deny|DENY|drop|DROP)
						action="DROP"
						;;
						
					reject|REJECT)
						action="REJECT"
						if [ "${1}" = "with" ]
						then
							local -a action_param=("--reject-with" "${2}")
							shift 2
						else
							local -a action_param=("--reject-with" "auto")
						fi
						;;
						
					return|RETURN)
						action="RETURN"
						;;
						
					mirror|MIRROR)
						action="MIRROR"
						test $nomirror -eq 1 && action="REJECT"
						;;
						
					none|NONE)
						action="NONE"
						;;
						
					snat|SNAT)
						action="SNAT"
						if [ "${1}" = "to" ]
						then
							local -a action_param=()
							local x=
							for x in ${2}
							do
								action_param=(${action_param[@]} "--to-source" "${x}")
							done
							shift 2
						else
							error "${action} requires a 'to' argument."
							return 1
						fi
						if [ ! "A${table}" = "A-t nat" ]
						then
							error "${action} must on a the 'nat' table."
							return 1
						fi
						;;
						
					dnat|DNAT)
						action="DNAT"
						if [ "${1}" = "to" ]
						then
							local -a action_param=()
							local x=
							for x in ${2}
							do
								action_param=(${action_param[@]} "--to-destination" "${x}")
							done
							shift 2
						else
							error "${action} requires a 'to' argument"
							return 1
						fi
						if [ ! "A${table}" = "A-t nat" ]
						then
							error "${action} must on a the 'nat' table."
							return 1
						fi
						;;
						
					redirect|REDIRECT)
						action="REDIRECT"
						if [ "${1}" = "to-port" -o "${1}" = "to" ]
						then
							local -a action_param=("--to-ports" "${2}")
							shift 2
						else
							error "${action} requires a 'to-port' or 'to' argument."
							return 1
						fi
						if [ ! "A${table}" = "A-t nat" ]
						then
							error "${action} must on a the 'nat' table."
							return 1
						fi
						;;
						
					tos|TOS)
						action="TOS"
						if [ "${1}" = "to" ]
						then
							local -a action_param=("--set-tos" "${2}")
							shift 2
						else
							error "${action} requires a 'to' argument"
							return 1
						fi
						if [ ! "A${table}" = "A-t mangle" ]
						then
							error "${action} must on a the 'mangle' table."
							return 1
						fi
						;;
						
					mark|MARK)
						action="MARK"
						if [ "${1}" = "to" ]
						then
							local -a action_param=("--set-mark" "${2}")
							shift 2
						else
							error "${action} requires a 'to' argument"
							return 1
						fi
						if [ ! "A${table}" = "A-t mangle" ]
						then
							error "${action} must on a the 'mangle' table."
							return 1
						fi
						;;
						
					dscp|DSCP)
						action="DSCP"
						if [ "${1}" = "to" ]
						then
							if [ "${2}" = "class" ]
							then
								local -a action_param=("--set-dscp-class" "${2}")
								shift
							else
								local -a action_param=("--set-dscp" "${2}")
							fi
							shift 2
						else
							error "${action} requires a 'to' argument"
							return 1
						fi
						if [ ! "A${table}" = "A-t mangle" ]
						then
							error "${action} must on a the 'mangle' table."
							return 1
						fi
						;;
						
					*)
						chain_exists "${action}"
						local action_is_chain=$?
						;;
				esac
				;;
			
			state|STATE)
				shift
				statenot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					statenot="!"
				fi
				test ${softwarnings} -eq 1 -a ! -z "${state}" && softwarning "Overwritting param: state '${state}' becomes '${1}'"
				state="${1}"
				shift
				;;
				
			user|USER|uid|UID)
				shift
				uidnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					test ${noowner} -eq 0 && uidnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${uid}" = "any" && softwarning "Overwritting param: uid '${uid}' becomes '${1}'"
				test ${noowner} -eq 0 && uid="${1}"
				shift
				;;
				
			group|GROUP|gid|GID)
				shift
				gidnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					test ${noowner} -eq 0 && gidnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${gid}" = "any" && softwarning "Overwritting param: gid '${gid}' becomes '${1}'"
				test ${noowner} -eq 0 && gid="${1}"
				shift
				;;
				
			process|PROCESS|pid|PID)
				shift
				pidnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					test ${noowner} -eq 0 && pidnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${pid}" = "any" && softwarning "Overwritting param: pid '${pid}' becomes '${1}'"
				test ${noowner} -eq 0 && pid="${1}"
				shift
				;;
				
			session|SESSION|sid|SID)
				shift
				sidnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					test ${noowner} -eq 0 && sidnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${sid}" = "any" && softwarning "Overwritting param: sid '${sid}' becomes '${1}'"
				test ${noowner} -eq 0 && sid="${1}"
				shift
				;;
				
			command|COMMAND|cmd|CMD)
				shift
				cmdnot=
				if [ "${1}" = "not" -o "${1}" = "NOT" ]
				then
					shift
					test ${noowner} -eq 0 && cmdnot="!"
				fi
				test ${softwarnings} -eq 1 -a ! "${cmd}" = "any" && softwarning "Overwritting param: cmd '${cmd}' becomes '${1}'"
				test ${noowner} -eq 0 && cmd="${1}"
				shift
				;;
				
			custom|CUSTOM)
				test ${softwarnings} -eq 1 -a ! -z "${custom}" && softwarning "Overwritting param: custom '${custom}' becomes '${2}'"
				custom="${2}"
				shift 2
				;;
				
			log|LOG)
				if [ ${nolog} -eq 0 ]
				then
					test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwritting param: log '${log}/${logtxt}' becomes 'normal/${2}'"
					log=normal
					logtxt="${2}"
				fi
				shift 2
				if [ "${1}" = "level" ]
				then
					loglevel="${2}"
					shift 2
				else
					loglevel="${FIREHOL_LOG_LEVEL}"
				fi
				;;
				
			loglimit|LOGLIMIT)
				if [ ${nolog} -eq 0 ]
				then
					test ${softwarnings} -eq 1 -a ! -z "${log}" && softwarning "Overwritting param: log '${log}/${logtxt}' becomes 'limit/${2}'"
					log=limit
					logtxt="${2}"
				fi
				shift 2
				if [ "${1}" = "level" ]
				then
					loglevel="${2}"
					shift 2
				else
					loglevel="${FIREHOL_LOG_LEVEL}"
				fi
				;;
				
			limit|LIMIT)
				test ${softwarnings} -eq 1 -a ! -z "${limit}" && softwarning "Overwritting param: limit '${limit}' becomes '${2}'"
				limit="${2}"
				burst="${3}"
				shift 3
				;;
				
			iplimit|IPLIMIT)
				test ${softwarnings} -eq 1 -a ! -z "${iplimit}" && softwarning "Overwritting param: iplimit '${iplimit}' becomes '${2}'"
				iplimit="${2}"
				iplimit_mask="${3}"
				shift 3
				;;
				
			in)	# this is incoming traffic - ignore packet ownership
				local noowner=1
				local nomirror=0
				local nomac=0
				shift
				;;
				
			out)	# this is outgoing traffic - ignore packet ownership if not in an interface
				if [ ! "${work_cmd}" = "interface" ]
				then
					local noowner=1
				else
					local nomirror=1
				fi
				local nomac=1
				shift
				;;
				
			nolog)
				local nolog=1
				shift
				;;
				
			noowner)
				local noowner=1
				shift
				;;
				
			softwarnings)
				local softwarnings=1
				shift
				;;
				
			nosoftwarnings)
				local softwarnings=0
				shift
				;;
				
			set_work_inface|SET_WORK_INFACE)
				swi=1
				shift
				;;
				
			set_work_outface|SET_WORK_OUTFACE)
				swo=1
				shift
				;;
				
			*)
				error "Cannot understand directive '${1}'."
				return 1
				;;
		esac
	done
	
	test -z "${table}" && table="-t filter"
	
	# If the user did not specified a rejection message,
	# we have to be smart and produce a tcp-reset if the protocol
	# is TCP and an ICMP port unreachable in all other cases.
	# The special case here is the protocol "any".
	# To accomplish the differentiation based on protocol we have
	# to change the protocol "any" to "tcp any"
	
	test "${action}" = "REJECT" -a "${action_param[1]}" = "auto" -a "${proto}" = "any" && proto="tcp any"
	
	
	# we cannot accept empty strings to a few parameters, since this
	# will prevent us from generating a rule (due to nested BASH loops).
	test -z "${inface}"	&& error "Cannot accept an empty 'inface'."	&& return 1
	test -z "${outface}"	&& error "Cannot accept an empty 'outface'."	&& return 1
	test -z "${physin}"	&& error "Cannot accept an empty 'physin'."	&& return 1
	test -z "${physout}"	&& error "Cannot accept an empty 'physout'."	&& return 1
	test -z "${mac}"	&& error "Cannot accept an empty 'mac'."	&& return 1
	test -z "${src}"	&& error "Cannot accept an empty 'src'."	&& return 1
	test -z "${dst}"	&& error "Cannot accept an empty 'dst'."	&& return 1
	test -z "${sport}"	&& error "Cannot accept an empty 'sport'."	&& return 1
	test -z "${dport}"	&& error "Cannot accept an empty 'dport'."	&& return 1
	test -z "${proto}"	&& error "Cannot accept an empty 'proto'."	&& return 1
	test -z "${uid}"	&& error "Cannot accept an empty 'uid'."	&& return 1
	test -z "${gid}"	&& error "Cannot accept an empty 'gid'."	&& return 1
	test -z "${pid}"	&& error "Cannot accept an empty 'pid'."	&& return 1
	test -z "${sid}"	&& error "Cannot accept an empty 'sid'."	&& return 1
	test -z "${cmd}"	&& error "Cannot accept an empty 'cmd'."	&& return 1
	
	
	# ----------------------------------------------------------------------------------
	# Do we have negative contitions?
	# If yes, we have to:
	#
	# case 1: If the action is a chain.
	#         Add to this chain positive RETURN statements matching all the negatives.
	#         The positive rules will be added bellow to the same chain and will be
	#         matched only if all RETURNs have not been matched.
	#
	# case 2: If the action is not a chain.
	#         Create a temporary chain, then add to this chain positive RETURN rules
	#         matching the negatives, and append at its end the final action (which is
	#         not a chain), then change the action of the positive rules to jump to
	#         this temporary chain.
	
	
	# ignore 'statenot' since it is negated in the positive rules
	if [ ! -z "${infacenot}${outfacenot}${physinnot}${physoutnot}${macnot}${srcnot}${dstnot}${sportnot}${dportnot}${protonot}${uidnot}${gidnot}${pidnot}${sidnot}${cmdnot}${marknot}${tosnot}${dscpnot}" ]
	then
		if [ ${action_is_chain} -eq 1 ]
		then
			# if the action is a chain name, then just add the negative
			# expressions to this chain. Nothing more.
			
			local negative_chain="${action}"
			local negative_action=
		else
			# if the action is a native iptables action, then create
			# an intermidiate chain to store the negative expression,
			# and change the action of the rule to point to this action.
			
			# In this case, bellow we add after all negatives, the original
			# action of the rule.
			
			local negative_chain="${chain}.${FIREHOL_DYNAMIC_CHAIN_COUNTER}"
			FIREHOL_DYNAMIC_CHAIN_COUNTER="$[FIREHOL_DYNAMIC_CHAIN_COUNTER + 1]"
			
			iptables ${table} -N "${negative_chain}"
			local negative_action="${action}"
			local action="${negative_chain}"
		fi
		
		
		if [ ! -z "${infacenot}" ]
		then
			local inf=
			for inf in ${inface}
			do
				iptables ${table} -A "${negative_chain}" -i "${inf}" -j RETURN
			done
			infacenot=
			inface=any
		fi
	
		if [ ! -z "${outfacenot}" ]
		then
			local outf=
			for outf in ${outface}
			do
				iptables ${table} -A "${negative_chain}" -o "${outf}" -j RETURN
			done
			outfacenot=
			outface=any
		fi
		
		if [ ! -z "${physinnot}" ]
		then
			local inph=
			for inph in ${physin}
			do
				iptables ${table} -A "${negative_chain}" -m physdev --physdev-in "${inph}" -j RETURN
			done
			physinnot=
			physin=any
		fi
	
		if [ ! -z "${physoutnot}" ]
		then
			local outph=
			for outph in ${physout}
			do
				iptables ${table} -A "${negative_chain}" -m physdev --physdev-out "${outph}" -j RETURN
			done
			physoutnot=
			physout=any
		fi
		
		if [ ! -z "${macnot}" ]
		then
			local m=
			for m in ${mac}
			do
				iptables ${table} -A "${negative_chain}" -m mac --mac-source "${m}" -j RETURN
			done
			macnot=
			mac=any
		fi
		
		if [ ! -z "${srcnot}" ]
		then
			local s=
			for s in ${src}
			do
				iptables ${table} -A "${negative_chain}" -s "${s}" -j RETURN
			done
			srcnot=
			src=any
		fi
		
		if [ ! -z "${dstnot}" ]
		then
			local d=
			for d in ${dst}
			do
				iptables ${table} -A "${negative_chain}" -d "${d}" -j RETURN
			done
			dstnot=
			dst=any
		fi
		
		if [ ! -z "${protonot}" ]
		then
			if [ ! -z "${sportnot}" -o ! -z "${dportnot}" ]
			then
				error "Cannot have negative protocol(s) and source/destination port(s)."
				return 1
			fi
			
			local pr=
			for pr in ${proto}
			do
				iptables ${table} -A "${negative_chain}" -p "${pr}" -j RETURN
			done
			protonot=
			proto=any
		fi
		
		if [ ! -z "${sportnot}" ]
		then
			if [ "${proto}" = "any" ]
			then
				error "Cannot have negative source port specification without protocol."
				return 1
			fi
			
			local sp=
			for sp in ${sport}
			do
				local pr=
				for pr in ${proto}
				do
					iptables ${table} -A "${negative_chain}" -p "${pr}" --sport "${sp}" -j RETURN
				done
			done
			sportnot=
			sport=any
		fi
		
		if [ ! -z "${dportnot}" ]
		then
			if [ "${proto}" = "any" ]
			then
				error "Cannot have negative destination port specification without protocol."
				return 1
			fi
			
			local dp=
			for dp in ${dport}
			do
				local pr=
				for pr in ${proto}
				do
					iptables ${table} -A "${negative_chain}" -p "${pr}" --dport "${dp}" -j RETURN
				done
			done
			dportnot=
			dport=any
		fi
		
		if [ ! -z "${uidnot}" ]
		then
			local tuid=
			for tuid in ${uid}
			do
				iptables ${table} -A "${negative_chain}" -m owner --uid-owner "${tuid}" -j RETURN
			done
			uidnot=
			uid=any
		fi
		
		if [ ! -z "${gidnot}" ]
		then
			local tgid=
			for tgid in ${gid}
			do
				iptables ${table} -A "${negative_chain}" -m owner --gid-owner "${tgid}" -j RETURN
			done
			gidnot=
			gid=any
		fi
		
		if [ ! -z "${pidnot}" ]
		then
			local tpid=
			for tpid in ${pid}
			do
				iptables ${table} -A "${negative_chain}" -m owner --pid-owner "${tpid}" -j RETURN
			done
			pidnot=
			pid=any
		fi
		
		if [ ! -z "${sidnot}" ]
		then
			local tsid=
			for tsid in ${sid}
			do
				iptables ${table} -A "${negative_chain}" -m owner --sid-owner "${tsid}" -j RETURN
			done
			sidnot=
			sid=any
		fi
		
		if [ ! -z "${cmdnot}" ]
		then
			local tcmd=
			for tcmd in ${cmd}
			do
				iptables ${table} -A "${negative_chain}" -m owner --cmd-owner "${tcmd}" -j RETURN
			done
			cmdnot=
			cmd=any
		fi
		
		if [ ! -z "${marknot}" ]
		then
			local tmark=
			for tmark in ${mark}
			do
				iptables ${table} -A "${negative_chain}" -m mark --mark "${tmark}" -j RETURN
			done
			marknot=
			mark=any
		fi
		
		if [ ! -z "${tosnot}" ]
		then
			local ttos=
			for ttos in ${tos}
			do
				iptables ${table} -A "${negative_chain}" -m tos --tos "${ttos}" -j RETURN
			done
			tosnot=
			tos=any
		fi
		
		if [ ! -z "${dscpnot}" ]
		then
			local tdscp=
			for tdscp in ${dscp}
			do
				iptables ${table} -A "${negative_chain}" -m dscp --dscp${dscptype} "${tdscp}" -j RETURN
			done
			dscp=any
			dscpnot=
		fi
		
		
		# in case this is temporary chain we created for the negative expression,
		# just make it have the final action of the rule.
		if [ ! -z "${negative_action}" ]
		then
			local pr=
			for pr in ${proto}
			do
				local -a proto_arg=()
				
				case ${pr} in
					any|ANY)
						;;
					
					*)
						local -a proto_arg=("-p" "${pr}")
						;;
				esac
				
				rule_action_param "${negative_action}" "${pr}" "" "" "${table}" "${action_param[@]}" -- ${table} -A "${negative_chain}" "${proto_arg[@]}"
				local -a action_param=()
			done
		fi
	fi
	
	
	# ----------------------------------------------------------------------------------
	# Process the positive rules
	
	# uid
	local tuid=
	for tuid in ${uid}
	do
		local -a uid_arg=()
		local -a owner_arg=()
		
		case ${tuid} in
			any|ANY)
				;;
			
			*)
				local -a owner_arg=("-m" "owner")
				local -a uid_arg=("--uid-owner" "${tuid}")
				;;
		esac
	
	# gid
	local tgid=
	for tgid in ${gid}
	do
		local -a gid_arg=()
		
		case ${tgid} in
			any|ANY)
				;;
			
			*)
				local -a owner_arg=("-m" "owner")
				local -a gid_arg=("--gid-owner" "${tgid}")
				;;
		esac
	
	# pid
	local tpid=
	for tpid in ${pid}
	do
		local -a pid_arg=()
		
		case ${tpid} in
			any|ANY)
				;;
			
			*)
				local -a owner_arg=("-m" "owner")
				local -a pid_arg=("--pid-owner" "${tpid}")
				;;
		esac
	
	# sid
	local tsid=
	for tsid in ${sid}
	do
		local -a sid_arg=()
		
		case ${tsid} in
			any|ANY)
				;;
			
			*)
				local -a owner_arg=("-m" "owner")
				local -a sid_arg=("--sid-owner" "${tsid}")
				;;
		esac
	
	# cmd
	local tcmd=
	for tcmd in ${cmd}
	do
		local -a cmd_arg=()
		
		case ${tcmd} in
			any|ANY)
				;;
			
			*)
				local -a owner_arg=("-m" "owner")
				local -a cmd_arg=("--cmd-owner" "${tcmd}")
				;;
		esac
	
	# mark
	local tmark=
	for tmark in ${mark}
	do
		local -a mark_arg=()
		
		case ${tmark} in
			any|ANY)
				;;
			
			*)
				local -a mark_arg=("-m" "mark" "--mark" "${tmark}")
				;;
		esac
	
	# tos
	local ttos=
	for ttos in ${tos}
	do
		local -a tos_arg=()
		
		case ${ttos} in
			any|ANY)
				;;
			
			*)
				local -a tos_arg=("-m" "tos" "--tos" "${ttos}")
				;;
		esac
	
	# dscp
	local tdscp=
	for tdscp in ${dscp}
	do
		local -a dscp_arg=()
		
		case ${tdscp} in
			any|ANY)
				;;
			
			*)
				local -a dscp_arg=("-m" "dscp" "--dscp${dscptype}" "${tdscp}")
				;;
		esac
	
	# proto
	local pr=
	for pr in ${proto}
	do
		local -a proto_arg=()
		
		case ${pr} in
			any|ANY)
				;;
			
			*)
				local -a proto_arg=("-p" "${pr}")
				;;
		esac
	
	# inface
	local inf=
	for inf in ${inface}
	do
		local -a inf_arg=()
		case ${inf} in
			any|ANY)
				;;
			
			*)
				local -a inf_arg=("-i" "${inf}")
				;;
		esac
	
	# outface
	local outf=
	for outf in ${outface}
	do
		local -a outf_arg=()
		case ${outf} in
			any|ANY)
				;;
			
			*)
				local -a outf_arg=("-o" "${outf}")
				;;
		esac
	
	# physin
	local inph=
	for inph in ${physin}
	do
		local -a inph_arg=()
		case ${inph} in
			any|ANY)
				;;
			
			*)
				local -a physdev_arg=("-m" "physdev")
				local -a inph_arg=("--physdev-in" "${inph}")
				;;
		esac
	
	# physout
	local outph=
	for outph in ${physout}
	do
		local -a outph_arg=()
		case ${outph} in
			any|ANY)
				;;
			
			*)
				local -a physdev_arg=("-m" "physdev")
				local -a outph_arg=("--physdev-out" "${outph}")
				;;
		esac
	
	# sport
	local sp=
	for sp in ${sport}
	do
		local -a sp_arg=()
		case ${sp} in
			any|ANY)
				;;
			
			*)
				local -a sp_arg=("--sport" "${sp}")
				;;
		esac
	
	# dport
	local dp=
	for dp in ${dport}
	do
		local -a dp_arg=()
		case ${dp} in
			any|ANY)
				;;
			
			*)
				local -a dp_arg=("--dport" "${dp}")
				;;
		esac
	
	# mac
	local mc=
	for mc in ${mac}
	do
		local -a mc_arg=()
		case ${mc} in
			any|ANY)
				;;
			
			*)
				local -a mc_arg=("-m" "mac" "--mac-source" "${mc}")
				;;
		esac
	
	# src
	local s=
	for s in ${src}
	do
		local -a s_arg=()
		case ${s} in
			any|ANY)
				;;
			
			*)
				local -a s_arg=("-s" "${s}")
				;;
		esac
	
	# dst
	local d=
	for d in ${dst}
	do
		local -a d_arg=()
		case ${d} in
			any|ANY)
				;;
			
			*)
				local -a d_arg=("-d" "${d}")
				;;
		esac
	
	# state
	local -a state_arg=()
	if [ ! -z "${state}" ]
	then
		local -a state_arg=("-m" "state" "${statenot}" "--state" "${state}")
	fi
	
	# limit
	local -a limit_arg=()
	if [ ! -z "${limit}" ]
	then
		local -a limit_arg=("-m" "limit" "--limit" "${limit}" "--limit-burst" "${burst}")
	fi
	
	# iplimit
	local -a iplimit_arg=()
	if [ ! -z "${iplimit}" ]
	then
		local -a iplimit_arg=("-m" "iplimit" "--iplimit-above" "${iplimit}" "--iplimit-mask" "${iplimit_mask}")
	fi
	
	# build the command
	declare -a basecmd=("${inf_arg[@]}" "${outf_arg[@]}" "${physdev_arg[@]}" "${inph_arg[@]}" "${outph_arg[@]}" "${limit_arg[@]}" "${iplimit_arg[@]}" "${proto_arg[@]}" "${s_arg[@]}" "${sp_arg[@]}" "${d_arg[@]}" "${dp_arg[@]}" "${owner_arg[@]}" "${uid_arg[@]}" "${gid_arg[@]}" "${pid_arg[@]}" "${sid_arg[@]}" "${cmd_arg[@]}" "${state_arg[@]}" "${mc_arg[@]}" "${mark_arg[@]}" "${tos_arg[@]}" "${dscp_arg[@]}")
	
	# log mode selection
	local -a logopts_arg=()
	if [ "${FIREHOL_LOG_MODE}" = "ULOG" ]
	then
		local -a logopts_arg=("--ulog-prefix='${logtxt}:'")
	else
		local -a logopts_arg=("--log-level" "${loglevel}" "--log-prefix='${logtxt}:'")
	fi
	
	# log / loglimit
	case "${log}" in
		'')
			;;
		
		limit)
			iptables ${table} -A "${chain}" "${basecmd[@]}" ${custom} -m limit --limit "${FIREHOL_LOG_FREQUENCY}" --limit-burst "${FIREHOL_LOG_BURST}" -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}"
			;;
			
		normal)
			iptables ${table} -A "${chain}" "${basecmd[@]}" ${custom}  -j ${FIREHOL_LOG_MODE} ${FIREHOL_LOG_OPTIONS} "${logopts_arg[@]}"
			;;
			
		*)
			error "Unknown log value '${log}'."
			;;
	esac
	
	# do it!
	rule_action_param "${action}" "${pr}" "${statenot}" "${state}" "${table}" "${action_param[@]}" -- ${table} -A "${chain}" "${basecmd[@]}" ${custom}
	
	done # dst
	done # src
	done # mac
	done # dport
	done # sport
	done # physout
	done # physin
	done # outface
	done # inface
	done # proto
	done # dscp
	done # tos
	done # mark
	done # cmd
	done # sid
	done # pid
	done # gid
	done # uid
	
	test ${failed} -gt 0 && error "There are ${failed} failed commands." && return 1
	return 0
}

softwarning() {
	echo >&2
	echo >&2 "--------------------------------------------------------------------------------"
	echo >&2 "WARNING"
	echo >&2 "WHAT   : ${work_function}"
	echo >&2 "WHY    :" "$@"
	printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2
	echo >&2 "SOURCE : line ${FIREHOL_LINEID} of ${FIREHOL_CONFIG}"
	echo >&2
	
	return 0
}

# ------------------------------------------------------------------------------
# error - error reporting while still parsing the configuration file
# WHY:
# This is the error handler that presents to the user detected errors during
# processing FireHOL's configuration file.
# This command is directly called by other functions of FireHOL.

error() {
	work_error=$[work_error + 1]
	echo >&2
	echo >&2 "--------------------------------------------------------------------------------"
	echo >&2 "ERROR #: ${work_error}"
	echo >&2 "WHAT   : ${work_function}"
	echo >&2 "WHY    :" "$@"
	printf >&2 "COMMAND: "; printf >&2 "%q " "${work_realcmd[@]}"; echo >&2
	echo >&2 "SOURCE : line ${FIREHOL_LINEID} of ${FIREHOL_CONFIG}"
	echo >&2
	
	return 0
}


# ------------------------------------------------------------------------------
# runtime_error - postprocessing evaluation of commands run
# WHY:
# The generated iptables commands must be checked for errors in case they fail.
# This command is executed after every postprocessing command to find out
# if it has been successfull or failed.

runtime_error() {
	local type="ERROR"
	local id=
	
	case "${1}" in
		error)
			local type="ERROR  "
			work_final_status=$[work_final_status + 1]
			local id="# ${work_final_status}."
			;;
			
		warn)
			local type="WARNING"
			local id="This might or might not affect the operation of your firewall."
			;;
		
		*)
			work_final_status=$[work_final_status + 1]
			local id="# ${work_final_status}."
			
			echo >&2
			echo >&2
			echo >&2 "*** unsupported final status type '${1}'. Assuming it is 'ERROR'"
			echo >&2
			echo >&2
			;;
	esac
	shift
	
	local ret="${1}"; shift
	local line="${1}"; shift
	
	echo >&2
	echo >&2
	echo >&2 "--------------------------------------------------------------------------------"
	echo >&2 "${type} : ${id}"
	echo >&2 "WHAT    : A runtime command failed to execute (returned error ${ret})."
	echo >&2 "SOURCE  : line ${line} of ${FIREHOL_CONFIG}"
	printf >&2 "COMMAND : "
	printf >&2 "%q " "$@"
	printf >&2 "\n"
	echo >&2 "OUTPUT  : "
	echo >&2
	${CAT_CMD} ${FIREHOL_OUTPUT}.log
	echo >&2
	
	return 0
}


# ------------------------------------------------------------------------------
# chain_exists - find if chain name has already being specified
# WHY:
# We have to make sure each service gets its own chain.
# Although FireHOL chain naming makes chains with unique names, this is just
# an extra sanity check.

chain_exists() {
	local chain="${1}"
	
	test -f "${FIREHOL_CHAINS_DIR}/${chain}" && return 1
	return 0
}


# ------------------------------------------------------------------------------
# create_chain - create a chain and link it to the firewall
# WHY:
# When a chain is created it must somehow to be linked to the rest of the
# firewall apropriately. This function first creates the chain and then
# it links it to its final position within the generated firewall.

create_chain() {
	local table="${1}"
	local newchain="${2}"
	local oldchain="${3}"
	shift 3
	
	set_work_function "Creating chain '${newchain}' under '${oldchain}' in table '${table}'"
	
	chain_exists "${newchain}"
	test $? -eq 1 && error "Chain '${newchain}' already exists." && return 1
	
	iptables -t ${table} -N "${newchain}" || return 1
	${TOUCH_CMD} "${FIREHOL_CHAINS_DIR}/${newchain}"
	
	rule table ${table} chain "${oldchain}" action "${newchain}" "$@" || return 1
	
	return 0
}


# ------------------------------------------------------------------------------
# smart_function - find the valid service definition for a service
# WHY:
# FireHOL supports simple and complex services. This function first tries to
# detect if there are the proper variables set for a simple service, and if
# they do not exist, it then tries to find the complex function definition for
# the service.
#
# Additionally, it creates a chain for the subcommand.

smart_function() {
	local type="${1}"	# The current subcommand: server/client/route
	local services="${2}"	# The services to implement
	shift 2
	
	local service=
	for service in $services
	do
		local servname="${service}"
		test "${service}" = "custom" && local servname="${1}"
		
		set_work_function "Preparing for service '${service}' of type '${type}' under interface '${work_name}'"
		
		# Increase the command counter, to make all chains within a primary
		# command, unique.
		work_counter=$[work_counter + 1]
		
		local suffix="u${work_counter}"
		case "${type}" in
			client)
				suffix="c${work_counter}"
				;;
			
			server)
				suffix="s${work_counter}"
				;;
			
			route)
				suffix="r${work_counter}"
				;;
			
			*)	error "Cannot understand type '${type}'."
				return 1
				;;
		esac
		
		local mychain="${work_name}_${servname}_${suffix}"
		
		create_chain filter "in_${mychain}" "in_${work_name}" || return 1
		create_chain filter "out_${mychain}" "out_${work_name}" || return 1
		
		# Try the simple services first
		simple_service "${mychain}" "${type}" "${service}" "$@"
		local ret=$?
		
		# simple service completed succesfully.
		test $ret -eq 0 && continue
		
		# simple service exists but failed.
		if [ $ret -ne 127 ]
		then
			error "Simple service '${service}' returned an error ($ret)."
			return 1
		fi
		
		
		# Try the custom services
		local fn="rules_${service}"
		
		set_work_function "Running complex rules function ${fn}() for ${type} '${service}'"
		
		"${fn}" "${mychain}" "${type}" "$@"
		local ret=$?
		test $ret -eq 0 && continue
		
		if [ $ret -eq 127 ]
		then
			error "There is no service '${service}' defined."
		else
			error "Complex service '${service}' returned an error ($ret)."
		fi
		return 1
	done
	
	return 0
}

# ------------------------------------------------------------------------------
# simple_service - convert a service definition to an inline service definition
# WHY:
# When a simple service is detected, there must be someone to call
# rules_custom() with the appropriate service definition parameters.

simple_service() {
	local mychain="${1}"; shift
	local type="${1}"; shift
	local server="${1}"; shift
	
	local server_varname="server_${server}_ports"
	eval local server_ports="\$${server_varname}"
	
	local client_varname="client_${server}_ports"
	eval local client_ports="\$${client_varname}"
	
	test -z "${server_ports}" -o -z "${client_ports}" && return 127
	
	local x=
	local varname="require_${server}_modules"
	eval local value="\$${varname}"
	for x in ${value}
	do
		require_kernel_module $x || return 1
	done
	
	if [ ${FIREHOL_NAT} -eq 1 ]
	then
		local varname="require_${server}_nat_modules"
		eval local value="\$${varname}"
		for x in ${value}
		do
			require_kernel_module $x || return 1
		done
	fi
	
	set_work_function "Running simple rules for  ${type} '${service}'"
	
	rules_custom "${mychain}" "${type}" "${server}" "${server_ports}" "${client_ports}" "$@"
	return $?
}

show_work_realcmd() {
	test ${FIREHOL_EXPLAIN} -eq 1 && return 0
	
	(
		printf "\n\n"
		printf "# === CONFIGURATION STATEMENT =================================================\n"
		printf "# CONF:%3s>>>	" ${FIREHOL_LINEID}
		
		case $1 in
			2)	printf "	"
				;;
			*)	;;
		esac
		
		printf "%q " "${work_realcmd[@]}"
		printf "\n\n"
	) >>${FIREHOL_OUTPUT}
}

work_realcmd_primary() {
	work_realcmd=("$@")
	test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 1
}

work_realcmd_secondary() {
	work_realcmd=("$@")
	test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 2
}

work_realcmd_helper() {	
	work_realcmd=("$@")
	test ${FIREHOL_CONF_SHOW} -eq 1 && show_work_realcmd 3
}
