Scott M. Mcdermott

UNIX Systems & Network Administrator
available for contract or salaried positions

mkrules.sh

#!/bin/bash
#
# Firewall/Traffic script for Corporation FictCorp for Site A.
#
# The script implements:
#
#    - packet filtering rules via iptables for all nets
#    - address table lookup using symbolic host names
#    - separate filter chains for each attached network
#    - 1:many NAT maps, routable IPs -> RFC1918 addresses
#    - 1:1 NAT maps, used for inbound translations (holes)
#    - configurable logging based on either ulog or syslog
#    - priority queuing of traffic using linux classifier
#    - three flow bands: voice, video, and other data
#    - multicast routing -> GRE -> IP -> ESP -> IP
#
# The hypothetical system:
#
#    - 5 directly attached LAN segments
#    - 2 WAN links, (1) Internet and (2) branch office
#    - /30 connections to edge router with ISP uplink
#    - PIM-SM router using Xorp
#    - multicast routing to peers across IPSEC VPN
#    - 5 different VPN tunnels
#
# How to use safely on a live firewall when you are remote:
#
#    bash mkrules.sh iptables > test-iptables.sh
#    bash mkrules.sh traffic > test-traffic.sh
#    bash mkrules.sh multicast > test-multicast.sh
#    atnum=$(echo reboot -f | at now + 5 minutes | awk '{print $2}')
#    bash test-whatever.sh
#
# If the rules didn't work, wait for the reboot and log back
# in; if it worked:
#
#    atrm $atnum
#    mv test-whatever.sh rules-whatever.sh
#
# Make sure rules-*.sh are executed on boot.
#
# Further comments describing the site go here.  Note: this
# script has been sanitized so as not to leak information
# about any of the companies it was used for.  It is,
# however, based on a real deployment script.  Fictional
# networks, systems, and IPs have been created.
#
# * THIS SCRIPT MUST ECHO COMMANDS ONLY, NOT EXECUTE THEM.
# * Its output will be executed directly.  This build script
# * does not do ANYTHING AT ALL other than write another
# * script to stdout.
#
# All interfaces should be plumbed before using this script.
#
##############################################################################

function setenv  ()             { __vnm__=$1; shift; IFS= eval $__vnm__='"$*"';}
function esetenv ()             { setenv "$@"; export $1; }

##############################################################################
# VARIABLES
##############################################################################

# use which add_log_rule_$LOGTYPE function to log events?
setenv LOGTYPE                  syslog
setenv LOGRATELIM               yes
setenv LOGRATEPERSEC            5
setenv LOGRATEBURST             60

# this controls the number of messages that will be buffered
# before dumping to userspace, if LOGTYPE is set to "ulog"
setenv LOGULOGQDEPTH            32

# we use this arg to REJECT module for ICMP error when
# responding to packets that are oriented on the wrong side
# of the RFC1918 horizon
setenv REJECT_HORIZ             "--reject-with icmp-net-prohibited"

# for services where we restrict how frequently new
# connections can be made (per source), what is the number
# of seconds that will elapse before counter is reset and
# connections are again allowed from that IP?
setenv CONNLIMIT_ONLYEVERY      60

# physical interfaces
setenv LAN_GROUPWARE_IF         eth0
setenv LAN_USERPCS_IF           eth3
setenv LAN_DMZ_IF               eth2
setenv LAN_SALESDEMO_IF         eth3
setenv LAN_PROTOTYPE_IF         eth4

setenv WAN_BRANCHXYZ_IF         eth1
setenv WAN_INET_IF              eth5

# we need to stream multicast packets to BRANCHXYZ over a
# GRE tunnel so they can be routed over IPSEC
setenv MCASTIF                  mtun0
setenv MCASTPEER                10.70.4.1
setenv MCASTLOCAL               10.60.8.1
setenv MCASTTUNADDR             192.168.150.2/30

# routable address space used for NAT mappings by branch office
setenv XYZ_ADDR_SPACE_ROUTABLE  123.45.6.128/26
setenv XYZ_ADDR_SPACE_RFC1918   10.70.0.0/16

# hide internal hosts behind this address when they make new
# connections
setenv MASQIP                   99.22.47.1

# we need also to be aware of what our WAN partner uses for
# that purpose
setenv BRANCHXYZ_MASQIP         108.2.147.177

# sales demo network routable range
setenv SALESDEMO_NET            99.22.47.32/28

# locally attached networks this system arbitrates access to
# and from.
setenv PROTOTYPE_INTRANET       10.60.4.0/22
setenv THISBRANCH_INTRANET      10.60.8.0/22
setenv GROUPWARE_INTRANET       10.60.12.0/22
setenv BRANCHXYZ_INTRANET       10.70.4.0/22
setenv XYZ_GWARE_INTRANET       10.70.12.0/22
setenv ADMIN_INTRANET           10.70.8.0/22

# encryption domain for VPNs with other corporations
setenv CORPABC_INTRANET         192.168.101.0/24
setenv CORPDEF_INTRANET         192.168.80.0/24

##############################################################################
# PROGRAMS
##############################################################################

# use our local patched versions of the software that
# implements these rules
setenv _tc                      'echo /usr/local/sbin/tc'
setenv _mrt                     'echo /usr/local/sbin/smcroute'

setenv _iptpath                 '/usr/local/sbin/iptables'
setenv _ipt                     "echo $_iptpath"

setenv _iptnataddpre            "$_ipt "                        \
                                '-t nat '                       \
                                '-A PREROUTING '

setenv _iptnataddpost           "$_ipt "                        \
                                '-t nat '                       \
                                '-A POSTROUTING '

# this is used to start the PIM-SM routing daemon.  It
# doesn't seem to have the ability to daemonize on its own
# so we do it ourself using the shell
setenv _xorp                    'echo '                         \
                                'nohup '                        \
                                'nice -n 19 '                   \
                                'setsid '                       \
                                '/usr/local/sbin/xorp '         \
                                '</dev/null '                   \
                                '&>/dev/null &'

##############################################################################
# CHAINS
##############################################################################
#
# here we classify and enumerate all the chains we will use,
# for later reference (particularly as iterators).
#

# will ultimately terminate
user_chains_end=(
        accept
        drop
        invalid
        to_vpn
        reject_horiz
)

# nonterminating
user_chains_return=(
        to_dmz
        to_userlan
        to_demolan
        to_prototype
        to_groupware
        to_userlan_from_abc
)

user_chains=(
        ${user_chains_end[@]}
        ${user_chains_return[@]}
)

builtin_chains=(
        INPUT
        OUTPUT
        FORWARD
)

all_chains=(
        ${user_chains[@]}
        ${builtin_chains[@]}
)


##############################################################################
# ADDRESS MAP
##############################################################################
#
#       key                     routable        rfc1918
#
ADDRS=(                                                         \
        dmz_ns1                 99.22.47.2      no_internal     \
        dmz_ns2                 99.22.47.3      no_internal     \
                                                                \
        demo_zxx                99.22.47.40     no_internal     \
        demo_zxx_pa             99.22.47.41     no_internal     \
        demo_zxx_pb             99.22.47.42     no_internal     \
        demo_custb              99.22.47.43     no_internal     \
        demo_custc              99.22.47.44     no_internal     \
                                                                \
        support                 88.192.63.200   no_internal     \
        zxx_app2                88.192.63.105   no_internal     \
        zxx_db2                 88.192.63.104   no_internal     \
                                                                \
        sfo_mailsrv             99.22.47.50     10.60.8.25      \
        sfo_vpn                 99.22.47.52     10.60.9.223     \
        sfo_imap                99.22.47.53     10.60.8.19      \
        sfo_ldap                99.22.47.54     10.60.8.29      \
        sfo_smtp                99.22.47.55     10.60.8.31      \
        web_directory           99.22.47.56     10.60.9.15      \
        web_mail                99.22.47.57     10.60.9.13      \
        web_info                99.22.47.58     10.60.9.22      \
        sfo_ddns_app_ps_app48   99.22.47.59     10.60.10.95     \
        sfo_mail                99.22.47.61     10.60.12.71     \
                                                                \
        web_bug                 no_external     10.60.9.7       \
        web_test                no_external     10.60.9.19      \
        web_timesheet           no_external     10.60.9.20      \
                                                                \
        sfo_app_sol8_web_pa     no_external     10.60.8.194     \
        sfo_app_sol8_web_pb     no_external     10.60.8.195     \
        sfo_app_sol8_web        no_external     10.60.8.196     \
        sfo_app_sol8_db         no_external     10.60.8.197     \
        sfo_app_sol8_app        no_external     10.60.8.198     \
        sfo_app_sol8_app2       no_external     10.60.8.200     \
        sfo_app_sol9_web_pa     no_external     10.60.8.204     \
        sfo_app_sol9_web_pb     no_external     10.60.8.205     \
        sfo_app_sol9_web        no_external     10.60.8.206     \
        sfo_app_sol9_db         no_external     10.60.8.210     \
        sfo_app_sol9_db0        no_external     10.60.8.207     \
        sfo_app_sol9_db1        no_external     10.60.8.208     \
        sfo_app_sol9_app        no_external     10.60.8.208     \
        sfo_app_sol9_analytics  no_external     10.60.8.209     \
                                                                \
        sfo_qa_sol9_analytics   no_external     10.60.8.199     \
        sfo_qa_sol9_app0        no_external     10.60.8.217     \
        sfo_qa_sol9_app1        no_external     10.60.8.218     \
                                                                \
        sfo_hpux1               no_external     10.60.8.186     \
        sfo_hpux1_pa            no_external     10.60.8.182     \
        sfo_hpux1_pb            no_external     10.60.8.183     \
        sfo_hpux2               no_external     10.60.8.187     \
                                                                \
        sfo_app_db0             no_external     10.60.8.140     \
        sfo_app_db1             no_external     10.60.8.135     \
        sfo_app_staging         no_external     10.60.8.138     \
        sfo_app_apps            no_external     10.60.8.170     \
        sfo_app_apps0           no_external     10.60.8.171     \
        sfo_aix51               no_external     10.60.8.188     \
        sfo_aix43               no_external     10.60.8.189     \
        sfo_printer3            no_external     10.60.8.43      \
        sfo_printer2            no_external     10.60.8.40      \
        sfo_persona             no_external     10.60.8.121     \
        sfo_file0               99.22.47.51     10.60.8.30      \
        sfo_printer1            no_external     10.60.8.44      \
        sfo_phone               no_external     10.60.8.4       \
                                                                \
        sfo_app_perf4           no_external     10.60.8.144     \
                                                                \
        sfo_ddns_win2k3_test    no_external     10.60.10.250    \
                                                                \
        sfo_app_redhat71        no_external     10.60.8.136     \
        sfo_app_redhat9         no_external     10.60.8.145     \
)

##############################################################################
# LIBRARY
##############################################################################
#

# We need this error routine because stdout is being
# captured, so the user will not know about errors reported
# via stdout unless he inspects the build output.  It's a
# little weird to make functions like this because of the
# indirection introduced by the fact that it won't actually
# be run until the rules script is executed.  But each
# occurence in the rules script will have hardcoded the
# expansion of the arguments to the wrapper func, so it
# doesn't actually need to be passed arguments when it's
# invoked at runtime.  See also the implementation of
# wait_for_death() for another example.
#
# Note, we want this routine to be available to the build
# script too.  To support this, the function is defined in
# the build script, and its definition via "type" builtin is
# embedded in the rules output.  Then a helper function is
# provided to the build script which will create a callsite
# for the routine in rules output.  So it can be invoked for
# the build script with a simple "error" invocation, while
# "make_error_callsite" will produce the text of an "error"
# call for dumping into the output script.
#
function error () { echo "${@}" > /dev/stderr; }
function \
make_error_callsite ()
{
        #
        # We overwrite any previous definition because the
        # function will contain expanded arguments embedded
        # right in the declaration.  Since these change each
        # time we call it from the build script, the only
        # way to get different output as desired is to
        # redefine the entire function each time it is
        # called, overwriting the previous definition.
        #

        cat <<- HERE
        unset error;
        $(type error | grep -v "is a function");
        error ${@};
        HERE
}

#
# This procedure outputs a single line that contains an
# iptables command, along with a branch clause linked to the
# return code of iptables.  At runtime, a failure
# code causes the branch to execute; error() is redefined
# inline, along with its already-expanded arguments that
# were put into the rules file at build-time (but the
# arguments should still be correct, as they are known
# before interpretation).  The newly hardcoded copy of
# error() is called, writes its diagnostics, the program is
# exits.
#
# XXX TODO for a firewall script such as this, it might be
# better to try to continue and basically never exit, so we
# get the chance for at least some of the rules to
# take...perhaps even the essential ones required to login
# and fix the problem
#
function \
exec_ipt_bomb_on_failure ()
{
        local numiptargs=$1; shift
        exec_string=("$_iptpath $(
                for ((n = 1; n <= $numiptargs; n++)); do
                        echo $1
                        shift
                done
        ) || {
                $(make_error_callsite ${@})
                exit 1
        }")
        echo $exec_string
}

function \
toupper ()
{
        echo -n $1 |
        tr '[:lower:]' '[:upper:]'
}

# extract IP from table
function \
getip ()
{
        local addrtype=$1
        local keyname=$2

        local thiskey external internal

        for ((n = 0; $n < ${#ADDRS[@]}; n++)); do
                thiskey=${ADDRS[$n]}
                external=${ADDRS[$((++n))]}
                internal=${ADDRS[$((++n))]}
                if test $thiskey == $keyname; then
                        echo ${!addrtype}
                        return
                fi
        done

        error $0: NOTHING FOUND FOR HOST $keyname
        exit 1
}
function gip () { getip internal $1; }
function gep () { getip external $1; }

function \
conditional_print_ratelimit_argset ()
{
        # if this variable is not set then this function is a no-op
        test -z "$LOGRATELIMIT" && return

        # and same if it's set to a discernable "no" value
        if test                                         \
                $LOGRATELIMIT == no     -o              \
                $LOGRATELIMIT == NO     -o              \
                $LOGRATELIMIT == No     -o              \
                $LOGRATELIMIT == off    -o              \
                $LOGRATELIMIT == Off    -o              \
                $LOGRATELIMIT == OFF    -o              \
                $LOGRATELIMIT == 0
        then
                return
        fi

        # otherwise we assume "yes" and we have set the
        # related variables that will specify rates to the
        # limit match module
        printf --                                       \
                -m limit                                \
                --limit ${LOGRATEPERSEC:-5}/sec         \
                --limit-burst ${LOGRATEBURST:-60}
}

function \
make_log_args_ulog ()
{
        local log_prefix="$1"

        printf --                                       \
                -j LOG                                  \
                --ulog-qthreshold ${LOGULOGQDEPTH:-32}  \
                --ulog-prefix \""$log_prefix"\"
}

function \
make_log_args_syslog ()
{
        local log_prefix="$1"

        printf --                                       \
                `conditional_print_ratelimit_argset`    \
                -j LOG                                  \
                --log-tcp-options                       \
                --log-ip-options                        \
                --log-prefix \""$log_prefix"\"
}

function \
add_log_rule ()
{
        local chain_name=$1
        local event_name=$2

        $_ipt -A $chain_name $(make_log_args $chain_name $event_name)
}


# this is where we control which log backend we are using
function \
make_log_args ()
{
        local chain_name=$1
        local event_name=$2
        local log_prefix=$(                             \
                printf "chain %s: %s: "                 \
                        $chain_name                     \
                        "$(toupper $event_name)"        \
        )

        make_log_args_$LOGTYPE "$log_prefix"
}

# Add new hole for host on current $CHAIN with TCP or UDP
# use CHAIN when entering a chain to set the current chain
# these functions will manipulate.  I like using a global
# better than an argument here because it's sort of a
# system-wide state (i.e. which chain we're in) and we do
# not often add rules to multiple chains in proximity
# (except builtin ones and those won't use these convenience
# functions anyways).  Plus it makes the invocation shorter.
# NEW: optional fourth argument can be used to narrow the
# match by IP source.  This is actuallythe third argument as
# the user would invoke it (using an ipta* wrapper function)
function \
ipta ()
{
        local dest=$1
        local port=$2
        local proto=$3
        local src=$4

        test -z "$CHAIN" && {
                error $0: CHAIN must be set
                exit 1
        }
        iptargs=(
                -A $CHAIN
                -p $proto --dport $port
                $(test -n "$4" && printf -- -s $4)
                -d $dest
                -m state --state NEW
                -j accept
        )
        exec_ipt_bomb_on_failure                        \
                ${#iptargs[@]}                          \
                ${iptargs[@]}                           \
                $0:                                     \
                dest    $dest                           \
                proto   $proto                          \
                chain   $CHAIN                          \
                port    $port
}
function iptatcp        () { ipta $1 $2 tcp "$3";       }
function iptaudp        () { ipta $1 $2 udp "$3";       }
function iptaboth       () { ipta $1 $2 tcp "$3";
                             ipta $1 $2 udp "$3";       }

#
# adds a new hole on the given chain for a service that will
# restrict connection rates, avoiding things like ssh
# vulnerability scans from executing too fast.  Optional
# destination argument, but chain is required (note: unlike
# semantics of ipta* functions, we do not use the global
# $CHAIN here because we are used with INPUT which doesn't
# set CHAIN.  If INPUT is used, the destination argument
# will not be meangingful.
#
function \
limconn ()
{
        local chain=$1

        # we want the parameter sequence to be the same as
        # for ipta* functions but since we don't always have
        # a destination (as we are used for input) we will
        # have to have different calling conventions for the
        # two cases with/without destination
        if test $# -eq 4; then
                local dest=$2
                local proto=$3
                local port=$4
        else
                unset dest
                local proto=$2
                local port=$3
        fi

        # This is basically a hash function that uses all
        # the inputs as perturbations; should be a good key
        # unique to a chain/dest/port/proto combo but
        # reproducibly generated given that same combo.
        # This is used to tag the "recent" counter for a
        # particular packet and we definitely don't want
        # collisions or we might inadvertantly prevent
        # logins.
        #
        local name=$(
                echo "${chain}${dest:-INPUT}${port}${proto}"            |
                md5sum                                                  |
                awk '{print $1}'
        )

        # Three different rules are needed to implement
        # connection limiting.  Each rule is somewhat
        # different, but also shares arguments with the
        # other rules.  An array of args for each of the
        # three required iterations is defined here; to be
        # expanded using variable name indirection.  Thus we
        # can implement a "fake" two dimentional array with
        # one dimension as iteration number and the other
        # dimension as the arguments therefor.
        #

        recent_args=(   -m recent --name $name                          )
        update_args=(   --update --seconds ${CONNLIMIT_ONLYEVERY:-60}   )
           set_args=(   --set                                           )
        accept_args=(   -j accept                                       )
          drop_args=(   -j drop                                         )

        ruleargs0=(${recent_args[@]} ${update_args[@]} ${drop_args[@]}  )
        ruleargs1=(${recent_args[@]} ${set_args[@]}                     )
        ruleargs2=(${accept_args[@]}                                    )

        for ((n = 0; n < 3; n++)); do
                arg_array=ruleargs$n
                iptargs=(
                        -A $chain
                        -p $proto --dport $port
                        $(test -n "$dest" && printf -- -d $dest)
                        -m state --state NEW
                        ${!arg_array[@]}
                )
                exec_ipt_bomb_on_failure                                \
                        ${#iptargs[@]}                                  \
                        ${iptargs[@]}                                   \
                        $0:                                             \
                        proto   $proto                                  \
                        chain   $chain                                  \
                        port    $port
        done
}
function limtcp () { limconn $1 $2 tcp "$3";                            }
function limudp () { limconn $1 $2 udp "$3";                            }

##############################################################################
# TRAFFIC CONTROL
##############################################################################

# Note: end to end latency should not exceed 100ms for voice
# or 250 or so ms for video.  For video with synchronized
# audio, it should not exceed 100ms.

# It is essential that video, voice, and data are
# prioritized correctly LAN-wide (done by enabling
# ToS-determined switching in our LAN meshes) and WAN-wide
# (done by prioritizing traffic with the Linux classifier).
# To support this all the routers along the way need to
# maintain the ToS bits, *including those of the ISP*.  In
# fact, to properly implement queueing, traffic policies
# must be implemented on *all* egress points.  This means
# the ISP needs to have bandwidth classes and allocations
# based on the ToS bits we set.  Fortunately, the Linux ESP
# code copies the ToS bits from the encapsulated packets to
# the outer tunnel packets.  Doubly fortunately, our ISP has
# just begun offering a service whereby they actually use
# priority queues in the Cisco edge equipment.  I basically
# sent them IOS snippets from our own edge equipment and
# they duplicated in theirs.
#
# Remember: it is the part that can fill the pipe, that
# needs to do so intelligently.  The ISP has full control of
# how much data it will stuff in our receive queues at the
# border, so it is the party that makes sure our important
# classes have enough bandwidth.  After that, our own edge
# equipment at the VPN endpoints (i.e. this system)
# determines how much data is sent to the remote system.
# Since we control both VPN endpoints, and the ISP is
# guaranteeing us enough bandwidth for our classes, it
# becomes our job to make sure that we don't waste our
# egress pipes with data that is just going to be dropped by
# sprint.  We also have to send according to policy because
# *we* control site-to-site traffic.  ISP controls
# everyone-else-to-us, and we control us-to-us.
#
# Remember: egress queue is the one that has control! For a
# given IP flow the egress queue at the sender *must* be
# prioritized or competing traffic will not leave enough in
# the way of slots for our important data.  For each side,
# ISP's border router is the "sender" that can decide what
# packets to drop.  But we also have to prioritize packets
# we give to ISP because *we* control egress queues for
# site-to-site! ISP will just start dropping packets to
# guarantee bandwidth, so we need to make sure we aren't
# overlimit when sending to the remote site.
#
# This actually works! Without it voice and video get drops
# when we fill the interoffice pipe with FTP or HTTP data.
# It took experimentation to find the right levels...it
# would work for a while but when too many people got on the
# phone, voice would start getting choppy if
# videoconferencing was going.
#
# Note: we are using PPP multilink to aggregate DS1s to our
# upstream.  This is preferred to Cisco Express Forwarding
# because we can guarantee packet delivery *in order* !!
# This is important for streaming media like voice and
# video.  Out of order is basically the same as dropped for
# realtime digital media streams.  Research reveals that
# phone do simply drop packets with sequence numbers they
# aren't expecting.
#
# Note that these numbers are specifically designed for a
# 2xDS1 pipe, aggregate 3072 kilobits.  Fortunately this is
# just enough for two 1152 kilobit streams to be guaranteed
# (one for voice, the other for video) with enough room for
# other data.
#
# Note that idle higher-priority bandwidth is (obviously)
# borrowed by other applications if it isn't needed.
#
function \
do_htb_traffic_queue ()
{
        local iface=$1
        local maxkbit=$2
        local fixedvoicekbit=1152
        local fixedvideokbit=1152

        # remove all existing queues and classifiers on the
        # interface
        $_tc qdisc del dev $iface root '2>/dev/null' # for first run

        # the root queueing discipline will use only the
        # maximum given bandwidth of the interface; all
        # traffic will flow through this queue
        $_tc qdisc add dev $iface root                                  \
                handle 1:                                               \
                htb                                                     \
                default 30

                # we leave it intentionally short of the
                # true link bandwidth (this is done by the
                # caller passing in the $maxkbit parameter)
                # so we don't fill any upstream queues and
                # can guarantee that we have full control to
                # shape the traffic
                $_tc class add dev $iface                               \
                        parent 1:                                       \
                        classid 1:1                                     \
                        htb                                             \
                        rate ${maxkbit}kbit

                        # VOIP traffic gets highest priority
                        $_tc class add dev $iface                       \
                                parent 1:1                              \
                                classid 1:10                            \
                                htb                                     \
                                rate ${fixedvoicekbit}kbit              \
                                ceil ${maxkbit}kbit                     \
                                prio 0

                                $_tc qdisc add dev $iface               \
                                        parent 1:10                     \
                                        pfifo

                        # videoconferencing gets next best priority
                        $_tc class add dev $iface                       \
                                parent 1:1                              \
                                classid 1:20                            \
                                htb                                     \
                                rate ${fixedvideokbit}kbit              \
                                ceil ${maxkbit}kbit                     \
                                prio 1

                                $_tc qdisc add dev $iface               \
                                        parent 1:20                     \
                                        pfifo

                        # half of remaining bandwidth for
                        # anything sent from remote branch
                        # office net
                        $_tc class add dev $iface                       \
                                parent 1:1                              \
                                classid 1:25                            \
                                htb                                     \
                                rate $((
                                        $((  $maxkbit
                                           - $fixedvoicekbit
                                           - $fixedvideokbit
                                        )) / 2
                                     ))kbit                             \
                                ceil ${maxkbit}kbit                     \
                                prio 2

                                $_tc qdisc add dev $iface               \
                                        parent 1:25                     \
                                        pfifo

                        # all remaining traffic can consume
                        # the rest of the bandwidth, ending
                        # up in a stochastic fairness queue
                        $_tc class add dev $iface                       \
                                parent 1:1                              \
                                classid 1:30                            \
                                htb                                     \
                                rate $((
                                        $((  $maxkbit
                                           - $fixedvoicekbit
                                           - $fixedvideokbit
                                        )) / 2
                                     ))kbit                             \
                                ceil ${maxkbit}kbit                     \
                                prio 3

                                $_tc qdisc add dev $iface               \
                                        parent 1:30                     \
                                        sfq

        # shunt traffic classified as voice (IP Precedence 5)
        # to queue 1
        $_tc filter add dev $iface parent 1: protocol ip pref 1 u32 \
                match ip tos 0xa0 0xe0 flowid 1:10

        # shunt traffic classified as video (IP Precedence 4)
        # to queue 2
        $_tc filter add dev $iface parent 1: protocol ip pref 2 u32 \
                match ip tos 0x80 0xe0 flowid 1:20

        # traffic from remote branch office network gets put
        # into its own class; note that this includes all
        # tunneled traffic since we match on the gateway
        # address
        $_tc filter add dev $iface parent 1: protocol ip pref 3 u32 \
                match ip src $XYZ_ADDR_SPACE_ROUTABLE flowid 1:25
        $_tc filter add dev $iface parent 1: protocol ip pref 3 u32 \
                match ip src $XYZ_ADDR_SPACE_RFC1918 flowid 1:25
}

#
# This alternative implementation does work, but has
# problems like utter starvation and the possibility for
# someone crafting packets to shut down the whole network.
# Using a uid=0 ping with flood and manually set ToS bits, I
# was able to completely block remote SSH logins to the
# firewall.
#
# Note that in any case, since we don't filter out
# higher-precedence ToS in headers and just assume the
# validity of any client to set those, the network is
# vulnerable at least to being slowed down.  But that would
# soon be detected by the SNMP monitors as excessive traffic
# on some switch ports that shouldn't generate that.  At
# least we can log into machines and do stuff :)
#
function \
do_prio_traffic_queue ()
{
        local iface=$1

        # remove all existing queues and classifiers
        $_tc qdisc del dev $iface root '2>/dev/null' # for first run

        # make three queues for priority scheduling
        $_tc qdisc add dev $iface root handle 1: prio
        $_tc qdisc add dev $iface parent 1:1 handle 10: sfq
        $_tc qdisc add dev $iface parent 1:2 handle 20: sfq
        $_tc qdisc add dev $iface parent 1:3 handle 30: sfq

        # shunt traffic classified as voice (IP Precedence 5) to queue 1
        $_tc filter add dev $iface parent 1:0 protocol ip pref 1 u32 \
                match ip tos 0xa0 0xe0 flowid 1:1

        # shunt traffic classified as video (IP Precedence 4) to queue 2
        $_tc filter add dev $iface parent 1:0 protocol ip pref 1 u32 \
                match ip tos 0x80 0xe0 flowid 1:2

        # shunt all other traffic to queue three
        $_tc filter add dev $iface parent 1:0 protocol ip pref 2 u32 \
                match ip src 0.0.0.0/0 flowid 1:3
}

function \
configure_traffic_queues ()
{
        do_htb_traffic_queue $LAN_USERPCS_IF    $((95 * 1024))
        do_htb_traffic_queue $LAN_GROUPWARE_IF  $((95 * 1024))
        do_htb_traffic_queue $LAN_DMZ_IF        $((95 * 1024))
        do_htb_traffic_queue $WAN_INET_IF       2850
        do_htb_traffic_queue $MCASTIF           2700 # allow some room for GRE
                                                     # overhead - XXX TODO how
                                                     # much is actually
                                                     # required?

        # legacy link uses strict priority queuing with no shaping
        do_prio_traffic_queue $WAN_BRANCHXYZ_IF
}

##############################################################################
# MULTICAST
##############################################################################

#
# Remember that our output is shell script; if we have a
# function to wait for process death, we can't just call it
# from the build script; we have to embed it in the rules
# file that we are generating in such a way that it will be
# available to the script for execution at run-time.
# We do this by introducing a level of indirection: the
# function, instead of containing real code, outputs shell
# commands that define the desired function, and finally the
# command that will invoke it.  We are basically creating a
# string buffer with (1) the definition of a function (i.e.
# it would be suitable for "eval"uation); (2) the arguments
# expanded already at build time; (3) the invocation text
# that, when evaluated, will execute the procedure.
#
function \
wait_for_death ()
{
        cat <<- HERE
        function \
        do_wait_for_death ()
        {
                for ((n = 0; n <= 25; n++)); do
                        if pgrep -lf $1 &>/dev/null; then
                                sleep 1
                        else
                                return
                        fi
                done
                echo killing xorp failed, aborting
                exit
        }
        do_wait_for_death
        HERE
}

#
# This is one alternative implementation to get multicast
# across the IPSEC WAN, which uses the xorp daemon to
# participate in PIM-SM.  This should allow clients on one
# side of our IPSEC tunnel to advertise Multicast groups,
# and clients on the other side will get those
# advertisements and potentially join.  Multicast packets
# are forwarded across a GRE tunnel, itself tunneled in ESP.
# When they come out the other side they are available to
# the xorp daemon and arrive in the INPUT chain.
#
# See the shell script entry code to see how to use this
# particular alternative.  The other implementation sets up
# static multicast group memberships and static routes.  It
# didn't work very well.  It became clear that a real PIM-SM
# implementation was necessary and would be less headache
# because the actual devices already know what groups they
# need.  This just lets them do their thing.
#
# Multicast is used by the IP phones for (1) conferencing;
# (2) broadcast pages; (3) to light certain LEDs; (4) to
# play "music on hold."  It may also be used by multipoint
# videoconferencing, but we are strictly point to point.
#
function \
configure_multicast_tunnel ()
{
        echo pkill      smcroute                '&>/dev/null'
        echo pkill -f   xorp                    '&>/dev/null'

        # is there such thing as a C++ application that
        # starts and stops quickly?
        wait_for_death  xorp

        echo ip link    set dev $MCASTIF down   '&>/dev/null'
        echo ip tunnel  del     $MCASTIF        '&>/dev/null'
        echo ip tunnel  add     $MCASTIF        \
                        mode    gre             \
                        remote  $MCASTPEER      \
                        local   $MCASTLOCAL
        echo ip addr    add     $MCASTTUNADDR   dev $MCASTIF
        echo ip link    set dev $MCASTIF        up
        echo ip link    set dev $MCASTIF        multicast on

        $_xorp
}

function \
configure_multicast_routing ()
{
        # purge existing routes
        echo pkill smcroute                     '&>/dev/null'
        echo pkill -lf xorp                     '&>/dev/null'

        # start new static mcast routing daemon
        $_mrt -d

        # these are the Multicast groups I've identified so
        # far -- by sniffing -- that are used by the VOIP
        # PBX.  XXX TODO try Asterisk, then we don't have to
        # use trial and error all the time to figure out how
        # things work.
        groups=(                                \
                224.0.1.55                      \
                224.0.1.22                      \
                224.0.1.18                      \
                224.0.1.26                      \
                224.0.1.30                      \
                224.0.1.40                      \
                224.0.1.44                      \
                224.0.1.36                      \
                224.0.1.59
        )

        # signal to the LANs in this branch office that we
        # want their Multicast messages
        for group in ${groups[@]}; do
                $_mrt -j $MCASTIF $group
        done

        # tell the kernel to forward the packets we get from them
        for group in ${groups[@]}; do
                $_mrt -a $MCASTIF `gip sfo_phone` $group $LAN_USERPCS_IF
        done
}

#############################################################################
# ENTRY
##############################################################################

# this script generates rules for one of: iptables, tc, and
# multicast tunneling, depending on how we were invoked
case "$1" in
        (traffic)               configure_traffic_queues;       exit $?;;
        (multicast-tunnel)      configure_multicast_tunnel;     exit $?;;
        (multicast-static)      configure_multicast_routing;    exit $?;;
        (multicast)             configure_multicast_tunnel;     exit $?;;
        (iptables)              :;;                             # continue on
        (*)                     echo    unsupported invocation  \
                                        type or no type         \
                                        specified;              exit 1;;
esac

# the first thing we do is stop routing while we make changes
echo 'echo 0 >/proc/sys/net/ipv4/ip_forward'

# we'll need the FTP tracking module installed forcefully
# XXX do we need this anymore?
#echo modprobe -a ip_conntrack_ftp
#echo modprobe -a ip_nat_ftp

# start with all builtin tables and chains having a default
# DROP policy so as to catch any mistakes
$_ipt           -t filter       -P INPUT                DROP
$_ipt           -t filter       -P FORWARD              DROP
$_ipt           -t filter       -P OUTPUT               DROP
$_ipt           -t nat          -P PREROUTING           DROP
$_ipt           -t nat          -P POSTROUTING          DROP
$_ipt           -t nat          -P OUTPUT               DROP
$_ipt           -t mangle       -P PREROUTING           DROP
$_ipt           -t mangle       -P OUTPUT               DROP
$_ipt           -t mangle       -P INPUT                DROP
$_ipt           -t mangle       -P FORWARD              DROP
$_ipt           -t mangle       -P POSTROUTING          DROP

# ...but don't let any tables besides filter actually
# control the default policy.  We want the routing engine to
# let everything through, leaving the filter code to make
# decisions on what to allow.
$_ipt           -t nat          -P PREROUTING           ACCEPT
$_ipt           -t nat          -P POSTROUTING          ACCEPT
$_ipt           -t nat          -P OUTPUT               ACCEPT
$_ipt           -t mangle       -P PREROUTING           ACCEPT
$_ipt           -t mangle       -P OUTPUT               ACCEPT
$_ipt           -t mangle       -P INPUT                ACCEPT
$_ipt           -t mangle       -P FORWARD              ACCEPT
$_ipt           -t mangle       -P POSTROUTING          ACCEPT

# now flush any and all existing rules for all default
# chains
$_ipt           -t filter       -F                      '2>/dev/null'
$_ipt           -t nat          -F                      '2>/dev/null'
$_ipt           -t mangle       -F                      '2>/dev/null'

# flush all user chains; these won't exist at bootup so
# stifle stderr
for chain in ${user_chains[@]}; do
        $_ipt -F $chain                                 '2>/dev/null'
done

# now delete all non builtin chains; they must be flushed
# first or deletion routines will balk
$_ipt -X

# we must define chains before adding rules to them
# note: builtin chains don't require this step because they
# are always defined
for chain in ${user_chains[@]}; do
        $_ipt -N $chain
done

# first rule when any chain is hit is to log that fact so we
# can trace packets' traversal of the ruleset.
for chain in                                            \
        ${user_chains[@]}                               \
        ${builtin_chains[@]}
do
        add_log_rule $chain entered
done

##############################################################################
# ADDRESS TRANSLATION
##############################################################################

# 1:1 (static) NAT holes; for this to work we need NAT in both directions.
for ((n = 0; n < ${#ADDRS[@]}; n++)); do
        ext=${ADDRS[$((++n))]}
        int=${ADDRS[$((++n))]}
        test $ext == no_external                        && continue
        test $int == no_internal                        && continue
        $_iptnataddpre  -i $WAN_INET_IF -d $ext         -j DNAT --to $int
        $_iptnataddpre  -i $LAN_DMZ_IF  -d $ext         -j DNAT --to $int
        $_iptnataddpost -o $WAN_INET_IF -s $int         -j SNAT --to $ext
        $_iptnataddpost -o $LAN_DMZ_IF  -s $int         -j SNAT --to $ext
done

# 1:N (dynamic) NAT hole; everyone from the inside through
# the masquerade IP.  Note that it is necessary to use IP
# layer source address as match clause since we can't use -i
# with POSTROUTING and -j SNAT isn't available with
# PREROUTING.  And if we just use -o we end up getting
# Internet sources translated to our MASQIP, which works but
# is very broken.
$_iptnataddpost -o $WAN_INET_IF -s $THISBRANCH_INTRANET -j SNAT --to $MASQIP
$_iptnataddpost -o $LAN_DMZ_IF  -s $THISBRANCH_INTRANET -j SNAT --to $MASQIP

# Groupware and Prototype nets will only need to reach
# Internet, (mostly for updates) not DMZ or DEMO networks.
$_iptnataddpost -o $WAN_INET_IF -s $GROUPWARE_INTRANET  -j SNAT --to $MASQIP
$_iptnataddpost -o $WAN_INET_IF -s $PROTOTYPE_INTRANET  -j SNAT --to $MASQIP
$_iptnataddpost -o $WAN_INET_IF -s $XYZ_GWARE_INTRANET  -j SNAT --to $MASQIP



##############################################################################
# SETUP
##############################################################################

# we are a multicast router for the LAN and as such need to
# get those packets...the do NOT arrive on INPUT chain.
for chain in FORWARD INPUT; do
        $_ipt   -A $chain                                       \
                -i $LAN_USERPCS_IF                              \
                -m pkttype --pkt-type multicast                 \
                -j accept
done

# or to arrive for processing (e.g. IGMP) if they arrived
# from the local subnet
$_ipt -A INPUT          -i $LAN_USERPCS_IF      -p igmp         -j accept

# multicast packets (for PIM) arrive to us from RWC out of a
# GRE tunnel
$_ipt -A INPUT          -s $MCASTPEER           -p gre          -j accept
$_ipt -A INPUT          -i $MCASTIF                             -j accept
$_ipt -A FORWARD        -i $MCASTIF                             -j accept

# force packets to NAT routables from their mapped
# unroutable source IPs to be rejected: they shouldn't be
# using those IPs, they are using the wrong DNS server
# XXX TODO this might not actually work as it's supposed to;
# needs to be traced and verified
$_ipt -A FORWARD        -i $LAN_USERPCS_IF -d $MAPPED_NET       -j reject_horiz

# disallow all invalid traffic
$_ipt -A INPUT          -m state --state INVALID                -j invalid
$_ipt -A FORWARD        -m state --state INVALID                -j invalid

# allow valid, statefully tracked return traffic
$_ipt -A INPUT          -m state --state RELATED,ESTABLISHED    -j accept
$_ipt -A FORWARD        -m state --state RELATED,ESTABLISHED    -j accept

# handle ping here since we want it for everyone, note we
# already need states and have dumped bogus ones so it
# shouldn't be a security hole, but this should be verified
$_ipt -A INPUT          -p icmp -m state --state NEW            -j accept
$_ipt -A FORWARD        -p icmp -m state --state NEW            -j accept

##############################################################################
CHAIN=forward
##############################################################################

# let users from inside our LANs do anything they want,
# without exception
#
# ---> XXX TODO LOOKHERE FIXME WARNING XXX <---
#
# NOTE: BIG GAPING SECURITY HOLE.  This block was
# implemented while doing a network cutover.  Must be
# removed! Note, does not expose to outside attack, but
# makes all nets in our entire WAN bypass the firewall rules
# of the destination network!
#
#$_ipt   -A FORWARD      -i $LAN_USERPCS_IF                      -j accept
#$_ipt   -A FORWARD      -i $LAN_PROTOTYPE_IF                    -j accept
#$_ipt   -A FORWARD      -i $LAN_GROUPWARE_IF                    -j accept
#$_ipt   -A FORWARD      -i $WAN_BRANCHXYZ_IF                    -j accept
#$_ipt   -A FORWARD      -o $WAN_BRANCHXYZ_IF                    -j accept
#$_ipt   -A FORWARD      -s $BRANCHXYZ_INTRANET                  -j accept
#$_ipt   -A FORWARD      -s $XYZ_GWARE_INTRANET                  -j accept

# let DMZ traffic outbound to the Internet proceed without
# restriction as well
$_ipt   -A FORWARD      -i $LAN_DMZ_IF  -o $WAN_INET_IF         -j accept

# vpn server is the only thing that should be getting IP/GRE or TCP/PPTP
$_ipt   -A FORWARD      -i $WAN_INET_IF -d `gip sfo_vpn`        \
                                        -p  gre                 -j accept
$_ipt   -A FORWARD      -i $WAN_INET_IF -d `gip sfo_vpn`        \
                                        -p  tcp --dport 1723    -j accept

# Send traffic for each logical network to its own rule
# chain.  At this point any RELATED, ESTABLISHED or INVALID
# packets have already been dealt with so we can add the NEW
# state as a condition for jumping.  This means we don't
# have to match on the NEW state in the individual chains
# because it's assumed.
$_ipt -A FORWARD -d $SALESDEMO_NET      -m state --state NEW -j to_demolan
$_ipt -A FORWARD -o $LAN_USERPCS_IF     -m state --state NEW -j to_userlan
$_ipt -A FORWARD -o $LAN_PROTOTYPE_IF   -m state --state NEW -j to_prototype
$_ipt -A FORWARD -o $LAN_GROUPWARE_IF   -m state --state NEW -j to_groupware
$_ipt -A FORWARD -o $LAN_DMZ_IF         -m state --state NEW -j to_dmz

##############################################################################
# LOCAL CHAINS
##############################################################################

# detect accidental use of ipta* functions
unset CHAIN

# let us go outbound for everything
$_ipt -A OUTPUT -j accept

# all loopback traffic allowed (note rp_filter should take
# care of spoofing)
$_ipt -A INPUT -i lo -j accept

# we want ping, mroute daemons, etc from BRANCHXYZ to work
# without effort after they emerge from the tunnel
$_ipt -A INPUT  -s      $BRANCHXYZ_INTRANET                     -j accept
$_ipt -A INPUT  -s      $XYZ_GWARE_INTRANET                     -j accept

# handle the fact that we are an IPSec gateway.
# note: protocol 50 (esp) not in /etc/services
$_ipt -A INPUT  -p tcp  --dport isakmp -m state --state NEW     -j accept
$_ipt -A INPUT  -p udp  --dport isakmp -m state --state NEW     -j accept
$_ipt -A INPUT  -p 50                                           -j accept
$_ipt -A OUTPUT -p tcp  --dport isakmp -m state --state NEW     -j accept
$_ipt -A OUTPUT -p udp  --dport isakmp -m state --state NEW     -j accept
$_ipt -A OUTPUT -p 50                                           -j accept

# allow new incoming ssh connections to the firewal's ssh
# port from anywhere.  Note that we limit connections
# because of widespread distributed ssh vulnerability
# scanning.  I was getting very annoyed at the thousands
# plus failed connect attempts per day in the logs, many
# times just trying easy credentials like "root/password"
limtcp INPUT ssh

# allow intranet to get SNMP monitor values.  I don't know
# why I can't just use a single UDP NEW accept because it
# should hit the RELATED,ESTABLISHED rule at the top of the
# rules, but it doesn't seem to be working with UDP for some
# reason.
#
# XXX TODO see comments about NTP conntracking.  UDP
# conntrack apparently is not getting the information it
# needs.  Since UDP is connectionless I'm not sure how they
# do it anwyays...must just be am heuristic.
#
$_ipt -A INPUT  -p udp -s $THISBRANCH_INTRANET  --dport snmp -j accept
$_ipt -A INPUT  -p udp -s $BRANCHXYZ_INTRANET   --dport snmp -j accept
$_ipt -A OUTPUT -p udp -d $THISBRANCH_INTRANET  --sport snmp -j accept
$_ipt -A OUTPUT -p udp -d $BRANCHXYZ_INTRANET   --sport snmp -j accept

##############################################################################
CHAIN=to_dmz
##############################################################################
#
# A NOTE ABOUT "DMZ": most DMZs are a bastion LAN between
# two firewalls, one on the edge side and one closer to the
# core.  Servers that run only the front-end parts of the
# services they offer are put in this net and access their
# backends through the inner firewall, which allows only
# these bastion hosts to access the backend services, and only
# those services.  The second firewall is not necessary in
# our topology because logically, we can implement the exact
# same thing with different rulesets for each pair of
# networks that's talking.  E.g. outside -> bastion has one
# set of access rules and bastion -> inside has another,
# while outside -> inside has no possible communications
# path.
#
# This accomplishes the exact same thing as having a second
# physical firewall; the only difference is that the second
# firewall is on the same host as the first one.  Since two
# firewalls are likely to be running the exact same firewall
# software anyways, they are both vulnerable to the same
# attack that can take over the edge firewall, so I don't
# see any advantage to the two firewall deployment.  Once
# the edge firewall is cracked, the LAN allows communication
# with the inner firewall, which presumably can be cracked
# in the same fasion.
#
# For our topology, the firewall acts as the "switch" at the
# nexus of all the networks we want access to allows us to
# have a fully self-contained ruleset for any (fromnet,
# tonet) pair.
#

#
# Name service; for external queries ONLY.  These do NOT
# have any RFC1918 data and should never be queried by any
# machines from inside.  In particular, the caching DNS
# servers on the LAN are recursive and maintain their own
# caches, they do NOT rely on the DMZ name servers.  The
# sole purpose of those is to (1) do recursive lookup for
# other clients in the DMZ and (2) maintain authoritative
# name data for ourcompany.com and the in-addr.arpa PTRs.
#
iptaboth        `gep dmz_ns1`   domain
iptaboth        `gep dmz_ns2`   domain

# ssh logins (rate limited)
limtcp to_dmz   `gep dmz_ns1`   ssh
limtcp to_dmz   `gep dmz_ns2`   ssh

# MX hosts and ftp.mycompany.com are also in the DMZ
iptatcp         `gep dmz_ns1`   smtp
iptatcp         `gep dmz_ns2`   smtp
iptatcp         `gep dmz_ns2`   ftp

# colo uses us for NTP synchronization
#
# XXX TODO iptables still is not maintaining state
# consistently for NTP sessions, and currently whether two
# peers will sync through the firewall is a crapshoot.  I
# think it needs a helper module to be written because
# standard udp conntrack does NOT work reliably.  I have to
# keep restarting over and over and eventually, and
# seemingly at random, it will get conntracked and allow the
# peer synchronization messages through.  But wait, you're
# not done there: next you have to cross your fingers and
# hope it will get into 1024s poll mode (i.e. steady state)
# before conntrack decides it isn't RELATED anymore.  This
# *should* work but doesn't, and I don't know why.
#
# Note: if we could somehow use TCP for NTP, that would
# solve the problem.  To my knowledge, NTPv4 does not ever
# use NTP.  I know that I've never seen tcp packets sent
# while listening to the excahnge with tcpdump (and I've
# done that a *number* of times trying to figure out what
# varies to cause udp conntrack not to recognize the packets
# anymore)
#
iptaboth        `gep dmz_ns1`   ntp
iptaboth        `gep dmz_ns2`   ntp

##############################################################################
CHAIN=to_demolan
##############################################################################
#
# these machine are in use by sales or services teams and
# must be accessible remotely.  Since they aren't patched
# and IT has no jurisdiction we cordon it off in a separate
# network.  It is expected that these machines will be
# cracked.  At least we can limit the exposure to those
# ports needed for the demonstration purposes.

# everything on this chain is TCP
$_ipt -A to_demolan -p ! tcp -j drop

iptatcp `gep demo_zxx`          http
iptatcp `gep demo_zxx`          webcache
iptatcp `gep demo_zxx`          8180
iptatcp `gep demo_zxx`          4443
iptatcp `gep demo_zxx`          https
iptatcp `gep demo_custb`        https
iptatcp `gep demo_zxx_pa`       https
iptatcp `gep demo_zxx_pb`       https

##############################################################################
CHAIN=to_prototype
##############################################################################
#
# This network is used as a prototyping/test network for IT
# systems.  It needs to have more restriction, possibly only
# allowing access from designated admin station IPs, or
# requiring authentication first or something of that
# nature.

$_ipt -A to_prototype -s $ADMIN_INTRANET        -j accept
$_ipt -A to_prototype -s $THISBRANCH_INTRANET   -j accept
$_ipt -A to_prototype -s $BRANCHXYZ_INTRANET    -j accept

##############################################################################
CHAIN=to_groupware
##############################################################################
#
# After the MX hosts get the mail and it is transferred to
# the mail hub for canonicalization via LDAP.  The LDAP
# schema used by the hub MTA can specify reception
# addresses, SMTP routing, and delivery methods if local, on
# a per-user basis.  It also explodes ou=groups entries into
# its members, for groupOfUniqueNames object types that have
# mail address attributes.  This implements one level of
# distrubution list.
#
# For most users, their LDAP entry tells the MTA to route
# the mail to their branch office MTA if this isn't it.
# Once in hand, that MTA then will typically route it into
# the big honking groupware system that has its own LAN and
# 4 servers per office just to make it work.  The groupware
# handles the mail from there; typically it is fetched with
# some strange non-SMTP protocol by Outlook, and cached on
# the client.
#
# The groupware system is partitioned from everything else
# and it is assumed that it will be cracked.  A frontend
# server in the DMZ proxies HTTP to a web access client in
# the backend.  This puts one more HTTP stack in front of a
# potential attacker to deal with.
#
# The groupware LANs in each branch office are fairly
# dependent on communication with one another.  For
# debugging purposes all traffic is being allowed right now.
#
# XXX TODO restrict to only what's required
#
$_ipt -A to_groupware -s $ADMIN_INTRANET        -j accept
$_ipt -A to_groupware -s $THISBRANCH_INTRANET   -j accept
$_ipt -A to_groupware -s $CORPABC_INTRANET      -j accept
$_ipt -A to_groupware -s $CORPDEF_INTRANET      -j accept
$_ipt -A to_groupware -s $BRANCHXYZ_INTRANET    -j accept

# webmail from Internet to sfo-based users
iptatcp `gip sfo_mail` https

##############################################################################
CHAIN=to_userlan
##############################################################################
#
# The user LAN also is home to servers at the present time.
# Some services in the user LAN are available externally via
# firewall holes on the corresponding service port.  A
# separate server network is planned but resources are not
# available to implement this.  In the meantime there are a
# lot of holes in this chain.

# Let admin network (which is me over VPN :) come in
# unrestricted.  Note that this adds another border to the
# company LANs which must be protected with the same
# diligence as the corporate network itself.
$_ipt -A to_userlan -s $ADMIN_INTRANET -j accept

# Since there are so many holes for the offshore developers,
# we move them to their own chain; this way there's not so
# many match clauses that have to be given for every rule.
# Note that they typically have the same rule specified for
# sources in both their "dmz" and their "client lan" and in
# any case we may have to accept from either on the abc
# chain, so we need to match on both of their networks.
for sourcenet in                                        \
        $CORPABC_INTRANET                               \
        $CORPDEF_INTRANET
do
        $_ipt   -A to_userlan                           \
                -s $sourcenet                           \
                -m state --state NEW                    \
                -j userlan_from_abc
done

# demo network needs LDAP records because some machines
# there have our software configured to do LDAP auth and our
# test DIT for the application is in the company-wide DIT
iptatcp `gip sfo_ldap`          ldaps   $SALESDEMO_NET

# logins to the support portal auth against DNs with
# userType=customer in ou=people
iptatcp `gip sfo_ldap`          ldaps   `gep support`

# also for a test of db cluster RAC node operational I need
# to get at the colo
iptatcp `gip sfo_app_sol9_db0`  1521    `gep zxx_db2`
iptatcp `gip sfo_app_sol9_db1`  1521    `gep zxx_db2`

# let customer B demo use internal SQL instance for its data
iptatcp `gip sfo_app_db0`       ms-sql-s `gep demo_custb`

# internal .web hosts get HTTP and HTTPS, although HTTP is
# just a jump page
for webhost in                          \
        mail                            \
        directory                       \
        info
do
        iptatcp `gip web_$webhost`      https
done

# MX hosts (which run on the name servers) can relay to
# either branch SMTP hub.  allow unencrypted connections for
# debugging; we control whether it's used at any given time.
iptatcp         `gip sfo_smtp`         smtp     `gep dmz_ns1`
iptatcp         `gip sfo_smtp`         smtps    `gep dmz_ns1`
iptatcp         `gip sfo_smtp`         smtp     `gep dmz_ns2`
iptatcp         `gip sfo_smtp`         smtps    `gep dmz_ns2`

# NTP stratum 3 hosts to internal stratum 4 distribution nodes
# XXX TODO conntrack doesn't seem to work for UDP NTP, see
# comments elsewhere in script
iptaboth        `gip sfo_mailsrv`       ntp     `gep dmz_ns1`
iptaboth        `gip sfo_mailsrv`       ntp     `gep dmz_ns2`

##############################################################################
CHAIN=to_userlan_from_abc
##############################################################################
#
# This chain is arrived at if source is any of the two
# offshore devel nets.  Initially protocol is not matched
# but we RETURN early in this chain (after inserting rules
# for all non-TCP matches we want to deal with) if the
# packet isn't TCP.  There's so many holes for these guys it
# makes it a lot simpler to have a special subchain without
# having to have so many matches in each individual rule
# when we're just on the to_intranet chain.
#

# Offshore developers coming in over VPN are using our DNS.
iptaboth        `gip sfo_mailsrv`               domain

# We sent them one of our IP phones so we could have free
# calls to India over our VPN...this actually worked before
# they changed ISPs and we ended up having totally
# asymmetric routes 800ms one way and 250ms the other.
iptaudp         `gip sfo_phone`                2093:2096
iptatcp         `gip sfo_phone`                1040:1044
iptatcp         `gip sfo_phone`                http
iptatcp         `gip sfo_phone`                imap

# The phones establish direct connections once the PBX sets
# the call up.  The phones get addresses from our DHCP
# server in pools specifically for phones, based on their
# MAC.  The below allows the ABC nets to access these ranges
# (which are discontiguous) on the UDP ports used by the
# system to implement point to point calls.
$_ipt   -A userlan_fromabc                                              \
        -p udp          --dport                 2093:2096               \
        -m iprange      --dst-range             10.60.7.2-10.60.7.100   \
                        --dst-range             10.60.9.137-10.60.9.222 \
        -j accept

# Ensure all further matches on this chain are TCP
# protocols.  Nothing else should be coming from them on this
# chain so we already know this packet should not be
# forwarded.  Therefore we drop now rather than RETURN.
$_ipt           -A userlan_fromabc              -p ! tcp -j drop

# staging host which runs the current devel build.
iptatcp         `gip sfo_app_staging`           http
iptatcp         `gip sfo_app_staging`           https

# they of course source code repository access
iptatcp         `gip sfo_file0`                 cvspserver

# login to build servers to test fresh builds for QA purposes
iptatcp         `gip sfo_aix43`                 ssh
iptatcp         `gip sfo_aix51`                 ssh
iptatcp         `gip sfo_aix51`                 telnet  # ick
iptatcp         `gip sfo_aix51`                 5901    # ick
iptatcp         `gip sfo_app_redhat71`          accept  # wide open
iptatcp         `gip sfo_app_redhat9`           accept  # by decree

# access developer test machine
iptatcp         `gip sfo_persona`               ssh
iptatcp         `gip sfo_persona`               sftp
iptatcp         `gip sfo_persona`               3000

# access any of our databases heh nice
$_ipt           -A abc --dport 1521             -j accept

# this host apparently needs to have unrestricted access!
$_ipt           -A abc -d `gip sfo_app_db1`     -j accept

# haha just kidding...
#$_ipt          -A abc                  -j accept

# instance of version 1.2.3 of the app
iptatcp         `gip sfo_ddns_app_ps_app48`     3389
iptatcp         `gip sfo_ddns_app_ps_app48`     9001
iptatcp         `gip sfo_ddns_app_ps_app48`     9003
iptatcp         `gip sfo_ddns_app_ps_app48`     9005
iptatcp         `gip sfo_ddns_app_ps_app48`     9010

# one developer's Project server
iptatcp         `gip sfo_ddns_win2k3_test`      https
iptatcp         `gip sfo_ddns_win2k3_test`      microsoft-ds

# offshore team tracks hours spent on customer projects,
# which ultimate is billed to the customer
iptatcp         `gip web_timesheet`             https

# Access Solaris 9 QA machines unrestricted
# this loop makes me feel all warm and fuzzy inside.  It's
# so great to just leave your machines wide open.  Everyone
# should try it!!
#
for host in                             \
        sfo_app_sol9_web_pa             \
        sfo_app_sol9_web_pb             \
        sfo_app_sol9_web                \
        sfo_app_sol9_db                 \
        sfo_app_sol9_db0                \
        sfo_app_sol9_db1                \
        sfo_app_sol9_app                \
        sfo_app_sol9_analytics          \
        sfo_app_sol8_web_pa             \
        sfo_app_sol8_web_pb             \
        sfo_app_sol8_web                \
        sfo_app_sol8_db                 \
        sfo_app_sol8_app                \
        sfo_app_sol8_app2               \
        sfo_qa_sol9_analytics           \
        sfo_qa_sol9_app0                \
        sfo_qa_sol9_app1                \
        sfo_hpux1                       \
        sfo_hpux1_pa                    \
        sfo_hpux1_pb                    \
        sfo_hpux2
do
        $_ipt                           \
                -A to_userlan           \
                -d `gip $host`          \
                -j accept
done

# external IMAP and POP service
iptatcp         `gip sfo_imap`          imap
iptatcp         `gip sfo_imap`          imaps

# Offshore guys use us for relay of their FictCompany mail.
# They should, but don't always configure TLS/SSL for SMTP.
# Again the offshore teams has a heavenly mandate to ignore
# common sense.
iptatcp         `gip sfo_smtp`          smtp
iptatcp         `gip sfo_smtp`          smtps

# offshore devel team needs access to more instances of the
# application since they can't afford UNIX machines even
# though they're one of the biggest companies in all of India
iptatcp         `gip sfo_app_db0`       ms-sql-s
iptatcp         `gip sfo_app_apps`      3389
iptatcp         `gip sfo_app_apps`      http
iptatcp         `gip sfo_app_apps`      https
iptatcp         `gip sfo_app_apps`      9053
iptatcp         `gip sfo_app_apps`      9999
iptatcp         `gip sfo_app_apps0`     3389
iptatcp         `gip sfo_app_apps0`     http
iptatcp         `gip sfo_app_apps0`     https
iptatcp         `gip sfo_app_apps0`     9053

# offshore team needs access to SMB file services in this branch
iptatcp         `gip sfo_file0`         netbios-ns
iptatcp         `gip sfo_file0`         netbios-dgm
iptatcp         `gip sfo_file0`         netbios-ssn
iptatcp         `gip sfo_file0`         netbios-ns
iptatcp         `gip sfo_file0`         netbios-dgm
iptatcp         `gip sfo_file0`         netbios-ssn

# also needs access in to LDAP server order to do some
# testing of our app's LDAP auth code;  I guess they can't
# be bothered to set one up themselves
iptatcp         `gip sfo_ldap`          ldap
iptatcp         `gip sfo_ldap`          ldaps

# offshore team also needs access to file server via ssh but
# it's further restricted by groups in sshd_config; we can
# deny them login by making the system use ldap as a source
# for groups data and then making sure they are not in the
# specified group (perhaps users?)
iptatcp         `gip sfo_file0`         ssh

# defect tracking used by our software development team
iptatcp         `gip web_bug`           https

# QA tool to document test procedures and gather metrics
iptatcp         `gip web_test`          https

# Offshore team apparently does not have printers either.
# They are working on an SNMP module for the software and
# need to print and test SNMP interface.  So we have to open
# up the printers in this regard.
iptaboth        `gip sfo_printer1`      snmp
iptaboth        `gip sfo_printer2`      snmp
iptaboth        `gip sfo_printer3`      snmp
iptatcp         `gip sfo_printer3`      jetdirect

##############################################################################
# END-OF-CHAIN RULES
##############################################################################

$_ipt           -A accept               -j ACCEPT
$_ipt           -A drop                 -j DROP

add_log_rule    reject_horiz            rejecting
$_ipt           -A reject_horiz         -j REJECT $REJECT_HORIZ

# Now we install defaults to drop, on all chains.  Be
# certain that if this is a chain that should *not* end in a
# drop (i.e. "accept" chain), the terminating rule is
# already installed.  That means any subsequent rules (like
# these drops below) will never actually execute.  We are
# covered in two ways: default policy of chains are already
# set to DROP.  Since that won't log we prefer a jump to the
# user "drop" chain, which logs and *then* DROPs.  Setting
# all drops in the end in one location and at once removes
# the requirement for each chain to do a drop at the end of
# its rules installations.
for chain in ${all_chains[@]}; do
        $_ipt   -A $chain               -j drop
done

##############################################################################
# START ROUTING
##############################################################################

# When sysctl -p was run by the boot scripts, some of the
# interfaces may not yet have been up...let's do it again
# now that we are sure they are up: most important are proxy
# arp and rp_filter.  Without the former there is no one
# that claims to be the hardware address for translated IPs.
# Without the later, we are vulnerable to forged packet
# sources.
echo 'sysctl -p &>/dev/null'

# Now that everything is configured, let's reverse the very
# first thing we did when entering the script: tell the
# kernel that it can route packets again.
echo 'echo 1 > /proc/sys/net/ipv4/ip_forward'