+### -*-m4-*-
+###
+### Utility functions for firewall scripts
+###
+### (c) 2008 Mark Wooding
+###
+
+###----- Licensing notice ---------------------------------------------------
+###
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+###
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software Foundation,
+### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+m4_divert(20)m4_dnl
+###--------------------------------------------------------------------------
+### Utility functions.
+
+## doit COMMAND ARGS...
+##
+## If debugging, print the COMMAND and ARGS. If serious, execute them.
+run () {
+ set -e
+ if [ "$FW_DEBUG" ]; then echo "* $*"; fi
+ if ! [ "$FW_NOACT" ]; then "$@"; fi
+}
+
+## trace MESSAGE...
+##
+## If debugging, print the MESSAGE.
+trace () {
+ set -e
+ if [ "$FW_DEBUG" ]; then echo "$*"; fi
+}
+
+## defport NAME NUMBER
+##
+## Define $port_NAME to be NUMBER.
+defport () {
+ name=$1 number=$2
+ eval port_$name=$number
+}
+
+m4_divert(22)m4_dnl
+###--------------------------------------------------------------------------
+### Basic chain constructions.
+
+## clearchain CHAIN CHAIN ...
+##
+## Ensure that the named chains exist and are empty.
+clearchain () {
+ set -e
+ for chain; do
+ case $chain in
+ *:*) table=${chain%:*} chain=${chain#*:} ;;
+ *) table=filter ;;
+ esac
+ run iptables -t $table -N $chain
+ done
+}
+
+## errorchain CHAIN ACTION ARGS ...
+##
+## Make a chain which logs a message and then invokes some other action,
+## typically REJECT. Log messages are prefixed by `fw: CHAIN'.
+errorchain () {
+ set -e
+ chain=$1; shift
+ case $chain in
+ *:*) table=${chain%:*} chain=${chain#*:} ;;
+ *) table=filter ;;
+ esac
+ clearchain $table:$chain
+ run iptables -t $table -A $chain -j LOG \
+ -m limit --limit 3/minute --limit-burst 10 \
+ --log-prefix "new fw: $chain " --log-level notice
+ run iptables -t $table -A $chain -j "$@"
+}
+
+m4_divert(24)m4_dnl
+###--------------------------------------------------------------------------
+### Basic option setting.
+
+## setopt OPTION VALUE
+##
+## Set an IP sysctl.
+setopt () {
+ set -e
+ opt=$1; shift; val=$*
+ run sysctl -q net/ipv4/$opt="$val"
+}
+
+## setdevopt OPTION VALUE
+##
+## Set an IP interface-level sysctl.
+setdevopt () {
+ set -e
+ opt=$1; shift; val=$*
+ for i in /proc/sys/net/ipv4/conf/*; do
+ [ -f $i/$opt ] &&
+ run sysctl -q net/ipv4/conf/${i#/proc/sys/net/ipv4/conf/}/$opt="$val"
+ done
+}
+
+m4_divert(26)m4_dnl
+###--------------------------------------------------------------------------
+### Packet filter construction.
+
+## conntrack CHAIN
+##
+## Add connection tracking to CHAIN, and allow obvious stuff.
+conntrack () {
+ set -e
+ chain=$1
+ run iptables -A $chain -p tcp -m state \
+ --state ESTABLISHED,RELATED -j ACCEPT
+ run iptables -A $chain -p tcp ! --syn -g bad-tcp
+}
+
+## allowservices CHAIN PROTO SERVICE ...
+##
+## Add rules to allow the SERVICES on the CHAIN.
+allowservices () {
+ set -e
+ chain=$1 proto=$2; shift 2
+ count=0
+ list=
+ for svc; do
+ case $svc in
+ *:*)
+ n=2
+ left=${svc%:*} right=${svc#*:}
+ case $left in *[!0-9]*) eval left=\$port_$left ;; esac
+ case $right in *[!0-9]*) eval right=\$port_$right ;; esac
+ svc=$left:$right
+ ;;
+ *)
+ n=1
+ case $svc in *[!0-9]*) eval svc=\$port_$svc ;; esac
+ ;;
+ esac
+ case $svc in
+ *: | :* | "" | *[!0-9:]*)
+ echo >&2 "Bad service name"
+ exit 1
+ ;;
+ esac
+ count=$(( $count + $n ))
+ if [ $count -gt 15 ]; then
+ run iptables -A $chain -p $proto -m multiport -j ACCEPT \
+ --destination-ports ${list#,}
+ list= count=$n
+ fi
+ list=$list,$svc
+ done
+ case $list in
+ "")
+ ;;
+ ,*,*)
+ run iptables -A $chain -p $proto -m multiport -j ACCEPT \
+ --destination-ports ${list#,}
+ ;;
+ *)
+ run iptables -A $chain -p $proto -j ACCEPT \
+ --destination-port ${list#,}
+ ;;
+ esac
+}
+
+## ntpclient CHAIN NTPSERVER ...
+##
+## Add rules to CHAIN to allow NTP with NTPSERVERs.
+ntpclient () {
+ set -e
+ chain=$1; shift
+ for ntp; do
+ run iptables -A $chain -s $ntp -j ACCEPT \
+ -p udp --source-port 123 --destination-port 123
+ done
+}
+
+## dnsresolver CHAIN
+##
+## Add rules to allow CHAIN to be a DNS resolver.
+dnsresolver () {
+ set -e
+ chain=$1
+ for p in tcp udp; do
+ run iptables -A $chain -j ACCEPT \
+ -m state --state ESTABLISHED \
+ -p $p --source-port 53
+ done
+}
+
+## openports CHAIN [MIN MAX]
+##
+## Add rules to CHAIN to allow the open ports.
+openports () {
+ set -e
+ chain=$1; shift
+ [ $# -eq 0 ] && set -- $open_port_min $open_port_max
+ run iptables -A $chain -p tcp -g interesting --destination-port $1:$2
+ run iptables -A $chain -p udp -g interesting --destination-port $1:$2
+}
+
+m4_divert(28)m4_dnl
+###--------------------------------------------------------------------------
+### Packet classification.
+
+## defbitfield NAME WIDTH
+##
+## Defines MASK_NAME and BIT_NAME symbolic constants for dealing with
+## bitfields: x << BIT_NAME yields the value x in the correct position, and
+## ff & MASK_NAME extracts the corresponding value.
+defbitfield () {
+ set -e
+ name=$1 width=$2
+ eval MASK_$name=$(( (1 << $width) - 1 << $bitindex ))
+ eval BIT_$name=$bitindex
+ bitindex=$(( $bitindex + $width ))
+}
+
+## Define the layout of the bitfield.
+bitindex=0
+defbitfield MASK 16
+defbitfield FROM 4
+defbitfield TO 4
+
+## defnetclass NAME FORWARD-TO...
+##
+## Defines a netclass called NAME, which is allowed to forward to the
+## FORWARD-TO netclasses.
+##
+## For each netclass, constants from_NAME and to_NAME are defined as the
+## appropriate values in the FROM and TO fields (i.e., not including any mask
+## bits).
+##
+## This function also establishes mangle chains mark-from-NAME and
+## mark-to-NAME for applying the appropriate mark bits to the packet.
+##
+## Because it needs to resolve forward references, netclasses must be defined
+## in a two-pass manner, using a loop of the form
+##
+## for pass in 1 2; do netclassindex=0; ...; done
+netclassess=
+defnetclass () {
+ set -e
+ name=$1; shift
+ case $pass in
+ 1)
+
+ ## Pass 1. Establish the from_NAME and to_NAME constants, and the
+ ## netclass's mask bit.
+ eval from_$name=$(( $netclassindex << $BIT_FROM ))
+ eval to_$name=$(( $netclassindex << $BIT_TO ))
+ eval _mask_$name=$(( 1 << ($netclassindex + $BIT_MASK) ))
+ nets="$nets $name"
+ ;;
+ 2)
+
+ ## Pass 2. Compute the actual from and to values. We're a little bit
+ ## clever during source classification, and set the TO field to
+ ## all-bits-one, so that destination classification needs only a single
+ ## AND operation.
+ from=$(( ($netclassindex << $BIT_FROM) + (0xf << $BIT_TO) ))
+ for net; do
+ eval bit=\$_mask_$net
+ from=$(( $from + $bit ))
+ done
+ to=$(( ($netclassindex << $BIT_TO) + \
+ (0xf << $BIT_FROM) + \
+ (1 << ($netclassindex + $BIT_MASK)) ))
+ trace "from $name --> set $(printf %x $from)"
+ trace " to $name --> and $(printf %x $from)"
+
+ ## Now establish the mark-from-NAME and mark-to-NAME chains.
+ clearchain mangle:mark-from-$name mangle:mark-to-$name
+ run iptables -t mangle -A mark-from-$name -j MARK --set-mark $from
+ run iptables -t mangle -A mark-to-$name -j MARK --and-mark $to
+ ;;
+ esac
+ netclassindex=$(( $netclassindex + 1 ))
+}
+
+## defiface NAME NETCLASS:NETWORK/MASK...
+##
+## Declares a network interface NAME and associates with it a number of
+## reachable networks. During source classification, a packet arriving on
+## interface NAME from an address in NETWORK/MASK is classified as coming
+## from to NETCLASS. During destination classification, all packets going to
+## NETWORK/MASK are classified as going to NETCLASS, regardless of interface
+## (which is good, because the outgoing interface hasn't been determined
+## yet).
+##
+## As a special case, the NETWORK/MASK can be the string `default', which
+## indicates that all addresses not matched elsewhere should be considered.
+ifaces=:
+defaultiface=none
+allnets=
+defiface () {
+ set -e
+ name=$1; shift
+ case $ifaces in
+ *:"$name":*) ;;
+ *)
+ clearchain mangle:in-$name
+ run iptables -t mangle -A in-classify -i $name -g in-$name
+ ;;
+ esac
+ ifaces=$ifaces$name:
+ for item; do
+ netclass=${item%:*} addr=${item#*:}
+ case $addr in
+ default)
+ defaultiface=$name
+ defaultclass=$netclass
+ run iptables -t mangle -A out-classify -g mark-to-$netclass
+ ;;
+ *)
+ run iptables -t mangle -A in-$name -s $addr -g mark-from-$netclass
+ run iptables -t mangle -A out-classify -d $addr -g mark-to-$netclass
+ allnets="$allnets $name:$addr"
+ ;;
+ esac
+ done
+}
+
+## defvpn IFACE CLASS NET HOST:ADDR ...
+##
+## Defines a VPN interface. If the interface has the form `ROOT+' (i.e., a
+## netfilter wildcard) then define a separate interface ROOTHOST routing to
+## ADDR; otherwise just write a blanket rule allowing the whole NET. All
+## addresses concerned are put in the named CLASS.
+defvpn () {
+ set -e
+ iface=$1 class=$2 net=$3; shift 3
+ case $iface in
+ *-+)
+ root=${iface%+}
+ for host; do
+ name=${host%:*} addr=${host#*:}
+ defiface $root$name $class:$addr
+ done
+ ;;
+ *)
+ defiface $iface $class:$net
+ ;;
+ esac
+}
+
+m4_divert(-1)
+###----- That's all, folks --------------------------------------------------