From a1c9d60d78d9a53bd0d06c897d5de8633e7bd672 Mon Sep 17 00:00:00 2001 From: teastep Date: Sat, 10 Mar 2007 01:58:40 +0000 Subject: [PATCH] Add some more components to the new implementation git-svn-id: https://shorewall.svn.sourceforge.net/svnroot/shorewall/trunk@5477 fbd18981-670d-0410-9b5c-8dc0c1a9a2bb --- New/compiler | 127 +++ New/lib.base | 1422 +++++++++++++++++++++++++++++++ New/lib.config | 2184 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 3733 insertions(+) create mode 100755 New/compiler create mode 100644 New/lib.base create mode 100644 New/lib.config diff --git a/New/compiler b/New/compiler new file mode 100755 index 000000000..2498d02f6 --- /dev/null +++ b/New/compiler @@ -0,0 +1,127 @@ +#!/bin/sh +# +# The Shoreline Firewall (Shorewall) Packet Filtering Firewall Compiler - V3.4 +# +# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] +# +# (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) +# +# Complete documentation is available at http://shorewall.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of Version 2 of the GNU General Public License +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA +# +# If an error occurs while starting or restarting the firewall, the +# firewall is automatically stopped. +# +# Commands are: +# +# compile check Verify the configuration files. +# compile compile Compile into +# +# Environmental Variables: +# +# EXPORT=Yes -e option specified to /sbin/shorewall +# SHOREWALL_DIR A directory name was passed to /sbin/shorewall +# VERBOSE Standard Shorewall verbosity control. + +# +# Fatal error -- stops the compiler after issuing the error message +# +fatal_error() # $* = Error Message +{ + echo " ERROR: $@" >&2 + [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR + [ -n "$OUTPUT" ] && rm -f $OUTPUT + kill $$ + exit 2 +} + +# +# We include this for compatibility with the 'firewall' script. That script distinguishes between +# Fatal Errors (stop or restore required) and Startup Errors (errors detected before the firewall +# state has been changed. This allows us to use common parsing routines in both programs. +# +startup_error() +{ + echo " ERROR: $@" >&2 + [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR + [ -n "$OUTPUT" ] && rm -f $OUTPUT + kill $$ + exit 2 +} + +# +# +# E X E C U T I O N B E G I N S H E R E +# +# +# Start trace if first arg is "debug" +# +[ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; } + +NOLOCK= + +[ $# -gt 1 ] && [ "$1" = "nolock" ] && { NOLOCK=Yes; shift ; } + +trap "exit 2" 1 2 3 4 5 6 9 + +SHAREDIR=/usr/share/shorewall +VARDIR=/var/lib/shorewall +[ -z "$EXPORT" ] && CONFDIR=/etc/shorewall || CONFDIR=${SHAREDIR}/configfiles + +[ -n "${VERBOSE:=2}" ] + +for library in lib.base lib.config; do + FUNCTIONS=${SHAREDIR}/${library} + + if [ -f $FUNCTIONS ]; then + [ $VERBOSE -ge 2 ] && echo "Loading $FUNCTIONS..." + . $FUNCTIONS + else + fatal_error "Installation Error: $FUNCTIONS does not exist!" + fi +done + +PROGRAM=compiler + +COMMAND="$1" + +case "$COMMAND" in + + check) + [ $# -ne 1 ] && usage + do_initialize + exec perl /usr/share/shorewall/compiler.perl $1 + ;; + + compile) + [ $# -ne 2 ] && usage + do_initialize + exec perl /usr/share/shorewall/compiler.perl $1 + ;; + call) + # + # Undocumented way to call functions in ${SHAREDIR}/compiler directly + # + shift + do_initialize + EMPTY= + $@ + ;; + + *) + usage + ;; + +esac diff --git a/New/lib.base b/New/lib.base new file mode 100644 index 000000000..9c3fd7d02 --- /dev/null +++ b/New/lib.base @@ -0,0 +1,1422 @@ +#!/bin/sh +# +# Shorewall 3.4 -- /usr/share/shorewall/lib.base +# +# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] +# +# (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) +# +# Complete documentation is available at http://shorewall.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of Version 2 of the GNU General Public License +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA +# +# This library contains the code common to all Shorewall components. It is copied into +# the compiled script with the -e compiler flag is specified and is loaded by +# /sbin/shorewall, /usr/share/shorewall/compiler and /usr/share/shorewall/firewall. It +# is also released as part of Shorewall Lite where it is used by /sbin/shorewall-lite +# and /usr/share/shorewall-lite/shorecap. +# + +SHOREWALL_LIBVERSION=30303 + +[ -n "${VARDIR:=/var/lib/shorewall}" ] +[ -n "${SHAREDIR:=/usr/share/shorewall}" ] +[ -n "${CONFDIR:=/etc/shorewall}" ] + +# +# Message to stderr +# +error_message() # $* = Error Message +{ + echo " $@" >&2 +} + +# +# Conditionally produce message +# +progress_message() # $* = Message +{ + local timestamp= + + if [ $VERBOSE -gt 1 ]; then + [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " + echo "${timestamp}$@" + fi +} + +progress_message2() # $* = Message +{ + local timestamp= + + if [ $VERBOSE -gt 0 ]; then + [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " + echo "${timestamp}$@" + fi +} + +progress_message3() # $* = Message +{ + local timestamp= + + if [ $VERBOSE -ge 0 ]; then + [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " + echo "${timestamp}$@" + fi +} + +# +# Split a colon-separated list into a space-separated list +# +split() { + local ifs=$IFS + IFS=: + echo $* + IFS=$ifs +} + +# +# Search a list looking for a match -- returns zero if a match found +# 1 otherwise +# +list_search() # $1 = element to search for , $2-$n = list +{ + local e=$1 + + while [ $# -gt 1 ]; do + shift + [ "x$e" = "x$1" ] && return 0 + done + + return 1 +} + +# +# Suppress all output for a command +# +qt() +{ + "$@" >/dev/null 2>&1 +} + +# +# Determine if Shorewall is "running" +# +shorewall_is_started() { + qt $IPTABLES -L shorewall -n +} + +# +# Echos the fully-qualified name of the calling shell program +# +my_pathname() { + cd $(dirname $0) + echo $PWD/$(basename $0) +} + +# +# Source a user exit file if it exists +# +run_user_exit() # $1 = file name +{ + local user_exit=$(find_file $1) + + if [ -f $user_exit ]; then + progress_message "Processing $user_exit ..." + . $user_exit + fi +} + +# +# Set a standard chain's policy +# +setpolicy() # $1 = name of chain, $2 = policy +{ + run_iptables -P $1 $2 +} + +# +# Set a standard chain to enable established and related connections +# +setcontinue() # $1 = name of chain +{ + run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT +} + +# +# Flush one of the NAT table chains +# +flushnat() # $1 = name of chain +{ + run_iptables -t nat -F $1 +} + +# +# Flush one of the Mangle table chains +# +flushmangle() # $1 = name of chain +{ + run_iptables -t mangle -F $1 +} + +# +# Flush and delete all user-defined chains in the filter table +# +deleteallchains() { + run_iptables -F + run_iptables -X +} + +# +# Load a Kernel Module -- assumes that the variable 'moduledirectories' contains +# a space-separated list of directories to search for +# the module and that 'moduleloader' contains the +# module loader command. +# +loadmodule() # $1 = module name, $2 - * arguments +{ + local modulename=$1 + local modulefile + local suffix + + if ! list_search $modulename $MODULES ; then + shift + + for suffix in $MODULE_SUFFIX ; do + for directory in $moduledirectories; do + modulefile=$directory/${modulename}.${suffix} + + if [ -f $modulefile ]; then + case $moduleloader in + insmod) + insmod $modulefile $* + ;; + *) + modprobe $modulename $* + ;; + esac + break 2 + fi + done + done + fi +} + +# +# Reload the Modules +# +reload_kernel_modules() { + + local save_modules_dir=$MODULESDIR + local directory + local moduledirectories= + local moduleloader=modprobe + + if ! qt mywhich modprobe; then + moduleloader=insmod + fi + + [ -z "$MODULESDIR" ] && MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv4/netfilter:/lib/modules/$(uname -r)/kernel/net/netfilter + MODULES=$(lsmod | cut -d ' ' -f1) + + for directory in $(split $MODULESDIR); do + [ -d $directory ] && moduledirectories="$moduledirectories $directory" + done + + [ -n "$moduledirectories" ] && while read command; do + eval $command + done + + MODULESDIR=$save_modules_dir +} + +# +# Load kernel modules required for Shorewall +# +load_kernel_modules() # $1 = Yes, if we are to save moduleinfo in $VARDIR +{ + local save_modules_dir=$MODULESDIR + local directory + local moduledirectories= + local moduleloader=modprobe + local savemoduleinfo=${1:-Yes} # So old compiled scripts still work + + if ! qt mywhich modprobe; then + moduleloader=insmod + fi + + [ -z "$MODULESDIR" ] && \ + MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv4/netfilter:/lib/modules/$(uname -r)/kernel/net/netfilter + + for directory in $(split $MODULESDIR); do + [ -d $directory ] && moduledirectories="$moduledirectories $directory" + done + + modules=$(find_file modules) + + if [ -f $modules -a -n "$moduledirectories" ]; then + MODULES=$(lsmod | cut -d ' ' -f1) + progress_message "Loading Modules..." + . $modules + if [ $savemoduleinfo = Yes ]; then + [ -d ${VARDIR} ] || mkdir -p ${VARDIR} + echo MODULESDIR="$MODULESDIR" > ${VARDIR}/.modulesdir + cp -f $modules ${VARDIR}/.modules + fi + elif [ $savemoduleinfo = Yes ]; then + [ -d ${VARDIR} ] || mkdir -p ${VARDIR} + > ${VARDIR}/.modulesdir + > ${VARDIR}/.modules + fi + + MODULESDIR=$save_modules_dir +} + +# +# Call this function to assert mutual exclusion with Shorewall. If you invoke the +# /sbin/shorewall program while holding mutual exclusion, you should pass "nolock" as +# the first argument. Example "shorewall nolock refresh" +# +# This function uses the lockfile utility from procmail if it exists. +# Otherwise, it uses a somewhat race-prone algorithm to attempt to simulate the +# behavior of lockfile. +# +mutex_on() +{ + local try=0 + local lockf=${VARDIR}/lock + + MUTEX_TIMEOUT=${MUTEX_TIMEOUT:-60} + + if [ $MUTEX_TIMEOUT -gt 0 ]; then + + [ -d ${VARDIR} ] || mkdir -p ${VARDIR} + + if qt mywhich lockfile; then + lockfile -${MUTEX_TIMEOUT} -r1 ${lockf} + else + while [ -f ${lockf} -a ${try} -lt ${MUTEX_TIMEOUT} ] ; do + sleep 1 + try=$((${try} + 1)) + done + + if [ ${try} -lt ${MUTEX_TIMEOUT} ] ; then + # Create the lockfile + echo $$ > ${lockf} + else + echo "Giving up on lock file ${lockf}" >&2 + fi + fi + fi +} + +# +# Call this function to release mutual exclusion +# +mutex_off() +{ + rm -f ${VARDIR}/lock +} + +# +# Load an optional library +# +lib_load() # $1 = Name of the Library, $2 = Error Message heading if the library cannot be found +{ + local lib=${SHAREDIR}/lib.$1 + local loaded + + eval loaded=\$LIB_${1}_LOADED + + if [ -z "$loaded" ]; then + if [ -f $lib ]; then + progress_message "Loading library $lib..." + . $lib + eval LIB_${1}_LOADED=Yes + else + startup_error "$2 requires the Shorewall library $1 ($lib) which is not installed" + fi + fi +} + +# +# Determine if an optional library is available +# +lib_avail() # $1 = Name of the Library +{ + [ -f ${SHAREDIR}/lib.$1 ] +} + +# +# Note: The following set of IP address manipulation functions have anomalous +# behavior when the shell only supports 32-bit signed arithmatic and +# the IP address is 128.0.0.0 or 128.0.0.1. +# + +LEFTSHIFT='<<' + +# +# Validate an IP address +# +valid_address() { + local x y + local ifs=$IFS + + IFS=. + + for x in $1; do + case $x in + [0-9]|[0-9][0-9]|[1-2][0-9][0-9]) + [ $x -lt 256 ] || { IFS=$ifs; return 2; } + ;; + *) + IFS=$ifs + return 2 + ;; + esac + done + + IFS=$ifs + + return 0 +} + +# +# Convert an IP address in dot quad format to an integer +# +decodeaddr() { + local x + local temp=0 + local ifs=$IFS + + IFS=. + + for x in $1; do + temp=$(( $(( $temp $LEFTSHIFT 8 )) | $x )) + done + + echo $temp + + IFS=$ifs +} + +# +# convert an integer to dot quad format +# +encodeaddr() { + addr=$1 + local x + local y=$(($addr & 255)) + + for x in 1 2 3 ; do + addr=$(($addr >> 8)) + y=$(($addr & 255)).$y + done + + echo $y +} + +# +# Enumerate the members of an IP range -- When using a shell supporting only +# 32-bit signed arithmetic, the range cannot span 128.0.0.0. +# +# Comes in two flavors: +# +# ip_range() - produces a mimimal list of network/host addresses that spans +# the range. +# +# ip_range_explicit() - explicitly enumerates the range. +# +ip_range() { + local first last l x y z vlsm + + case $1 in + !*) + # + # Let iptables complain if it's a range + # + echo $1 + return + ;; + [0-9]*.*.*.*-*.*.*.*) + ;; + *) + echo $1 + return + ;; + esac + + first=$(decodeaddr ${1%-*}) + last=$(decodeaddr ${1#*-}) + + if [ $first -gt $last ]; then + fatal_error "Invalid IP address range: $1" + fi + + l=$(( $last + 1 )) + + while [ $first -le $last ]; do + vlsm= + x=31 + y=2 + z=1 + + while [ $(( $first % $y )) -eq 0 -a $(( $first + $y )) -le $l ]; do + vlsm=/$x + x=$(( $x - 1 )) + z=$y + y=$(( $y * 2 )) + done + + echo $(encodeaddr $first)$vlsm + first=$(($first + $z)) + done +} + +ip_range_explicit() { + local first last + + case $1 in + [0-9]*.*.*.*-*.*.*.*) + ;; + *) + echo $1 + return + ;; + esac + + first=$(decodeaddr ${1%-*}) + last=$(decodeaddr ${1#*-}) + + if [ $first -gt $last ]; then + fatal_error "Invalid IP address range: $1" + fi + + while [ $first -le $last ]; do + echo $(encodeaddr $first) + first=$(($first + 1)) + done +} + +# +# Netmask from CIDR +# +ip_netmask() { + local vlsm=${1#*/} + + [ $vlsm -eq 0 ] && echo 0 || echo $(( -1 $LEFTSHIFT $(( 32 - $vlsm )) )) +} + +# +# Network address from CIDR +# +ip_network() { + local decodedaddr=$(decodeaddr ${1%/*}) + local netmask=$(ip_netmask $1) + + echo $(encodeaddr $(($decodedaddr & $netmask))) +} + +# +# The following hack is supplied to compensate for the fact that many of +# the popular light-weight Bourne shell derivatives don't support XOR ("^"). +# +ip_broadcast() { + local x=$(( 32 - ${1#*/} )) + + [ $x -eq 32 ] && echo -1 || echo $(( $(( 1 $LEFTSHIFT $x )) - 1 )) +} + +# +# Calculate broadcast address from CIDR +# +broadcastaddress() { + local decodedaddr=$(decodeaddr ${1%/*}) + local netmask=$(ip_netmask $1) + local broadcast=$(ip_broadcast $1) + + echo $(encodeaddr $(( $(($decodedaddr & $netmask)) | $broadcast ))) +} + +# +# Test for network membership +# +in_network() # $1 = IP address, $2 = CIDR network +{ + local netmask=$(ip_netmask $2) + + test $(( $(decodeaddr $1) & $netmask)) -eq $(( $(decodeaddr ${2%/*}) & $netmask )) +} + +# +# Netmask to VLSM +# +ip_vlsm() { + local mask=$(decodeaddr $1) + local vlsm=0 + local x=$(( 128 << 24 )) # 0x80000000 + + while [ $(( $x & $mask )) -ne 0 ]; do + [ $mask -eq $x ] && mask=0 || mask=$(( $mask $LEFTSHIFT 1 )) # Not all shells shift 0x80000000 left properly. + vlsm=$(($vlsm + 1)) + done + + if [ $(( $mask & 2147483647 )) -ne 0 ]; then # 2147483647 = 0x7fffffff + echo "Invalid net mask: $1" >&2 + else + echo $vlsm + fi +} + + +# +# Chain name base for an interface -- replace all periods with underscores in the passed name. +# The result is echoed (less trailing "+"). +# +chain_base() #$1 = interface +{ + local c=${1%%+} + + while true; do + case $c in + @*) + c=at_${c#@} + ;; + *.*) + c="${c%.*}_${c##*.}" + ;; + *-*) + c="${c%-*}_${c##*-}" + ;; + *%*) + c="${c%\%*}_${c##*%}" + ;; + *@*) + c="${c%@*}_${c##*@}" + ;; + *) + echo ${c:=common} + return + ;; + esac + done +} + +# +# Query NetFilter about the existence of a filter chain +# +chain_exists() # $1 = chain name +{ + qt $IPTABLES -L $1 -n +} + +# +# Find the value 'dev' in the passed arguments then echo the next value +# + +find_device() { + while [ $# -gt 1 ]; do + [ "x$1" = xdev ] && echo $2 && return + shift + done +} + +# +# Find the value 'via' in the passed arguments then echo the next value +# + +find_gateway() { + while [ $# -gt 1 ]; do + [ "x$1" = xvia ] && echo $2 && return + shift + done +} + +# +# Find the value 'mtu' in the passed arguments then echo the next value +# + +find_mtu() { + while [ $# -gt 1 ]; do + [ "x$1" = xmtu ] && echo $2 && return + shift + done +} + +# +# Find the value 'peer' in the passed arguments then echo the next value up to +# "/" +# + +find_peer() { + while [ $# -gt 1 ]; do + [ "x$1" = xpeer ] && echo ${2%/*} && return + shift + done +} + +# +# Find the interfaces that have a route to the passed address - the default +# route is not used. +# + +find_rt_interface() { + ip route ls | while read addr rest; do + case $addr in + */*) + in_network ${1%/*} $addr && echo $(find_device $rest) + ;; + default) + ;; + *) + if [ "$addr" = "$1" -o "$addr/32" = "$1" ]; then + echo $(find_device $rest) + fi + ;; + esac + done +} + +# +# Try to find the gateway through an interface looking for 'nexthop' + +find_nexthop() # $1 = interface +{ + echo $(find_gateway `ip route ls | grep "[[:space:]]nexthop.* $1"`) +} + +# +# Find the default route's interface +# +find_default_interface() { + ip route ls | while read first rest; do + [ "$first" = default ] && echo $(find_device $rest) && return + done +} + +# +# Echo the name of the interface(s) that will be used to send to the +# passed address +# + +find_interface_by_address() { + local dev="$(find_rt_interface $1)" + local first rest + + [ -z "$dev" ] && dev=$(find_default_interface) + + [ -n "$dev" ] && echo $dev +} + +# +# Find the interface with the passed MAC address +# + +find_interface_by_mac() { + local mac=$1 first second rest dev + + ip link ls | while read first second rest; do + case $first in + *:) + dev=$second + ;; + *) + if [ "$second" = $mac ]; then + echo ${dev%:} + return + fi + esac + done +} + +# +# Determine if Interface is up +# +interface_is_up() { + [ -n "$(ip link ls dev $1 | grep -e '[<,]UP[,>]')" ] +} + +# +# Find interface address--returns the first IP address assigned to the passed +# device +# +find_first_interface_address() # $1 = interface +{ + # + # get the line of output containing the first IP address + # + addr=$(ip -f inet addr show $1 2> /dev/null | grep 'inet .* global' | head -n1) + # + # If there wasn't one, bail out now + # + [ -n "$addr" ] || fatal_error "Can't determine the IP address of $1" + # + # Strip off the trailing VLSM mask (or the peer IP in case of a P-t-P link) + # along with everything else on the line + # + echo $addr | sed 's/\s*inet //;s/\/.*//;s/ peer.*//' +} + +find_first_interface_address_if_any() # $1 = interface +{ + # + # get the line of output containing the first IP address + # + addr=$(ip -f inet addr show $1 2> /dev/null | grep 'inet .* global' | head -n1) + # + # Strip off the trailing VLSM mask (or the peer IP in case of a P-t-P link) + # along with everything else on the line + # + [ -n "$addr" ] && echo $addr | sed 's/\s*inet //;s/\/.*//;s/ peer.*//' || echo 0.0.0.0 +} + +# +# Determine if interface is usable from a Netfilter prespective +# +interface_is_usable() # $1 = interface +{ + interface_is_up $1 && [ "$(find_first_interface_address_if_any $1)" != 0.0.0.0 ] +} + +# +# Find interface addresses--returns the set of addresses assigned to the passed +# device +# +find_interface_addresses() # $1 = interface +{ + ip -f inet addr show $1 | grep inet\ | sed 's/\s*inet //;s/\/.*//;s/ peer.*//' +} + +# +# echo the list of networks routed out of a given interface +# +get_routed_networks() # $1 = interface name, $2-n = Fatal error message +{ + local address + local rest + + ip route show dev $1 2> /dev/null | + while read address rest; do + if [ "x$address" = xdefault ]; then + if [ $# -gt 1 ]; then + shift + fatal_error "$@" + else + "WARNING: default route ignored on interface $1" + fi + else + [ "$address" = "${address%/*}" ] && address="${address}/32" + echo $address + fi + done +} + +# +# Internal version of 'which' +# +mywhich() { + local dir + + for dir in $(split $PATH); do + if [ -x $dir/$1 ]; then + echo $dir/$1 + return 0 + fi + done + + return 2 +} + +# +# Set default config path +# +ensure_config_path() { + local F=${SHAREDIR}/configpath + if [ -z "$CONFIG_PATH" ]; then + [ -f $F ] || { echo " ERROR: $F does not exist"; exit 2; } + . $F + fi + + if [ -n "$SHOREWALL_DIR" ]; then + [ "${CONFIG_PATH%%:*}" = "$SHOREWALL_DIR" ] || CONFIG_PATH=$SHOREWALL_DIR:$CONFIG_PATH + fi +} + +# +# Find a File -- For relative file name, look in each ${CONFIG_PATH} then ${CONFDIR} +# +find_file() +{ + local saveifs= directory + + case $1 in + /*) + echo $1 + ;; + *) + for directory in $(split $CONFIG_PATH); do + if [ -f $directory/$1 ]; then + echo $directory/$1 + return + fi + done + + echo ${CONFDIR}/$1 + ;; + esac +} + +# +# Get fully-qualified name of file +# +resolve_file() # $1 = file name +{ + local pwd=$PWD + + case $1 in + /*) + echo $1 + ;; + .) + echo $pwd + ;; + ./*) + echo ${pwd}${1#.} + ;; + ..) + cd .. + echo $PWD + cd $pwd + ;; + ../*) + cd .. + resolve_file ${1#../} + cd $pwd + ;; + *) + echo $pwd/$1 + ;; + esac +} + +# +# Perform variable substitution on the passed argument and echo the result +# +expand() # $@ = contents of variable which may be the name of another variable +{ + eval echo \"$@\" +} + +# +# Function for including one file into another +# +INCLUDE() { + . $(find_file $(expand $@)) +} + +# +# Set the Shorewall state +# +set_state () # $1 = state +{ + echo "$1 ($(date))" > ${VARDIR}/state +} + +# +# Determine which optional facilities are supported by iptables/netfilter +# +determine_capabilities() { + qt $IPTABLES -t nat -L -n && NAT_ENABLED=Yes || NAT_ENABLED= + qt $IPTABLES -t mangle -L -n && MANGLE_ENABLED=Yes || MANGLE_ENABLED= + + CONNTRACK_MATCH= + MULTIPORT= + XMULTIPORT= + POLICY_MATCH= + PHYSDEV_MATCH= + IPRANGE_MATCH= + RECENT_MATCH= + OWNER_MATCH= + IPSET_MATCH= + CONNMARK= + XCONNMARK= + CONNMARK_MATCH= + XCONNMARK_MATCH= + RAW_TABLE= + IPP2P_MATCH= + LENGTH_MATCH= + CLASSIFY_TARGET= + ENHANCED_REJECT= + USEPKTTYPE= + KLUDGEFREE= + MARK= + XMARK= + MANGLE_FORWARD= + ADDRTYPE= + COMMENTS= + + qt $IPTABLES -N fooX1234 + qt $IPTABLES -A fooX1234 -m conntrack --ctorigdst 192.168.1.1 -j ACCEPT && CONNTRACK_MATCH=Yes + qt $IPTABLES -A fooX1234 -p tcp -m multiport --dports 21,22 -j ACCEPT && MULTIPORT=Yes + qt $IPTABLES -A fooX1234 -p tcp -m multiport --dports 21:22 -j ACCEPT && XMULTIPORT=Yes + qt $IPTABLES -A fooX1234 -m policy --pol ipsec --mode tunnel --dir in -j ACCEPT && POLICY_MATCH=Yes + + if qt $IPTABLES -A fooX1234 -m physdev --physdev-in eth0 -j ACCEPT; then + PHYSDEV_MATCH=Yes + fi + + if qt $IPTABLES -A fooX1234 -m iprange --src-range 192.168.1.5-192.168.1.124 -j ACCEPT; then + IPRANGE_MATCH=Yes + if [ -z "${KLUDGEFREE}" ]; then + qt $IPTABLES -A fooX1234 -m iprange --src-range 192.168.1.5-192.168.1.124 -m iprange --dst-range 192.168.1.5-192.168.1.124 -j ACCEPT && KLUDGEFREE=Yes + fi + fi + + qt $IPTABLES -A fooX1234 -m recent --update -j ACCEPT && RECENT_MATCH=Yes + qt $IPTABLES -A fooX1234 -m owner --uid-owner 0 -j ACCEPT && OWNER_MATCH=Yes + + if qt $IPTABLES -A fooX1234 -m connmark --mark 2 -j ACCEPT; then + CONNMARK_MATCH=Yes + qt $IPTABLES -A fooX1234 -m connmark --mark 2/0xFF -j ACCEPT && XCONNMARK_MATCH=Yes + fi + + qt $IPTABLES -A fooX1234 -p tcp -m ipp2p --ipp2p -j ACCEPT && IPP2P_MATCH=Yes + qt $IPTABLES -A fooX1234 -m length --length 10:20 -j ACCEPT && LENGTH_MATCH=Yes + qt $IPTABLES -A fooX1234 -j REJECT --reject-with icmp-host-prohibited && ENHANCED_REJECT=Yes + + qt $IPTABLES -A fooX1234 -j ACCEPT -m comment --comment "This is a comment" && COMMENTS=Yes + + if [ -n "$MANGLE_ENABLED" ]; then + qt $IPTABLES -t mangle -N fooX1234 + + if qt $IPTABLES -t mangle -A fooX1234 -j MARK --set-mark 1; then + MARK=Yes + qt $IPTABLES -t mangle -A fooX1234 -j MARK --and-mark 0xFF && XMARK=Yes + fi + + if qt $IPTABLES -t mangle -A fooX1234 -j CONNMARK --save-mark; then + CONNMARK=Yes + qt $IPTABLES -t mangle -A fooX1234 -j CONNMARK --save-mark --mask 0xFF && XCONNMARK=Yes + fi + + qt $IPTABLES -t mangle -A fooX1234 -j CLASSIFY --set-class 1:1 && CLASSIFY_TARGET=Yes + qt $IPTABLES -t mangle -F fooX1234 + qt $IPTABLES -t mangle -X fooX1234 + qt $IPTABLES -t mangle -L FORWARD -n && MANGLE_FORWARD=Yes + fi + + qt $IPTABLES -t raw -L -n && RAW_TABLE=Yes + + if qt mywhich ipset; then + qt ipset -X fooX1234 # Just in case something went wrong the last time + + if qt ipset -N fooX1234 iphash ; then + if qt $IPTABLES -A fooX1234 -m set --set fooX1234 src -j ACCEPT; then + qt $IPTABLES -D fooX1234 -m set --set fooX1234 src -j ACCEPT + IPSET_MATCH=Yes + fi + qt ipset -X fooX1234 + fi + fi + + qt $IPTABLES -A fooX1234 -m pkttype --pkt-type broadcast -j ACCEPT && USEPKTTYPE=Yes + qt $IPTABLES -A fooX1234 -m addrtype --src-type BROADCAST -j ACCEPT && ADDRTYPE=Yes + + qt $IPTABLES -F fooX1234 + qt $IPTABLES -X fooX1234 +} + +report_capabilities() { + report_capability() # $1 = Capability Description , $2 Capability Setting (if any) + { + local setting= + + [ "x$2" = "xYes" ] && setting="Available" || setting="Not available" + + echo " " $1: $setting + } + + if [ $VERBOSE -gt 1 ]; then + echo "Shorewall has detected the following iptables/netfilter capabilities:" + report_capability "NAT" $NAT_ENABLED + report_capability "Packet Mangling" $MANGLE_ENABLED + report_capability "Multi-port Match" $MULTIPORT + [ -n "$MULTIPORT" ] && report_capability "Extended Multi-port Match" $XMULTIPORT + report_capability "Connection Tracking Match" $CONNTRACK_MATCH + report_capability "Packet Type Match" $USEPKTTYPE + report_capability "Policy Match" $POLICY_MATCH + report_capability "Physdev Match" $PHYSDEV_MATCH + report_capability "Packet length Match" $LENGTH_MATCH + report_capability "IP range Match" $IPRANGE_MATCH + report_capability "Recent Match" $RECENT_MATCH + report_capability "Owner Match" $OWNER_MATCH + report_capability "Ipset Match" $IPSET_MATCH + report_capability "CONNMARK Target" $CONNMARK + [ -n "$CONNMARK" ] && report_capability "Extended CONNMARK Target" $XCONNMARK + report_capability "Connmark Match" $CONNMARK_MATCH + [ -n "$CONNMARK_MATCH" ] && report_capability "Extended Connmark Match" $XCONNMARK_MATCH + report_capability "Raw Table" $RAW_TABLE + report_capability "IPP2P Match" $IPP2P_MATCH + report_capability "CLASSIFY Target" $CLASSIFY_TARGET + report_capability "Extended REJECT" $ENHANCED_REJECT + report_capability "Repeat match" $KLUDGEFREE + report_capability "MARK Target" $MARK + [ -n "$MARK" ] && report_capability "Extended MARK Target" $XMARK + report_capability "Mangle FORWARD Chain" $MANGLE_FORWARD + report_capability "Comments" $COMMENTS + report_capability "Address Type Match" $ADDRTYPE + fi + + [ -n "$PKTTYPE" ] || USEPKTTYPE= + +} + +report_capabilities1() { + report_capability1() # $1 = Capability + { + eval echo $1=\$$1 + } + + echo "#" + echo "# Shorewall $VERSION detected the following iptables/netfilter capabilities - $(date)" + echo "#" + report_capability1 NAT_ENABLED + report_capability1 MANGLE_ENABLED + report_capability1 MULTIPORT + report_capability1 XMULTIPORT + report_capability1 CONNTRACK_MATCH + report_capability1 USEPKTTYPE + report_capability1 POLICY_MATCH + report_capability1 PHYSDEV_MATCH + report_capability1 LENGTH_MATCH + report_capability1 IPRANGE_MATCH + report_capability1 RECENT_MATCH + report_capability1 OWNER_MATCH + report_capability1 IPSET_MATCH + report_capability1 CONNMARK + report_capability1 XCONNMARK + report_capability1 CONNMARK_MATCH + report_capability1 XCONNMARK_MATCH + report_capability1 RAW_TABLE + report_capability1 IPP2P_MATCH + report_capability1 CLASSIFY_TARGET + report_capability1 ENHANCED_REJECT + report_capability1 KLUDGEFREE + report_capability1 MARK + report_capability1 XMARK + report_capability1 MANGLE_FORWARD + report_capability1 COMMENTS + report_capability1 ADDRTYPE +} + +# +# Delete IP address +# +del_ip_addr() # $1 = address, $2 = interface +{ + [ $(find_first_interface_address_if_any $2) = $1 ] || qt ip addr del $1 dev $2 +} + +# Add IP Aliases +# +add_ip_aliases() # $* = List of addresses +{ + local addresses external interface inet cidr rest val arping=$(mywhich arping) + + address_details() + { + # + # Folks feel uneasy if they don't see all of the same + # decoration on these IP addresses that they see when their + # distro's net config tool adds them. In an attempt to reduce + # the anxiety level, we have the following code which sets + # the VLSM and BRD from an existing address in the same networks + # + # Get all of the lines that contain inet addresses with broadcast + # + ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | while read inet cidr rest ; do + case $cidr in + */*) + if in_network $external $cidr; then + echo "/${cidr#*/} brd $(broadcastaddress $cidr)" + break + fi + ;; + esac + done + } + + do_one() + { + val=$(address_details) + + ip addr add ${external}${val} dev $interface $label + [ -n "$arping" ] && qt $arping -U -c 2 -I $interface $external + echo "$external $interface" >> $STATEDIR/nat + [ -n "$label" ] && label="with $label" + progress_message " IP Address $external added to interface $interface $label" + } + + progress_message "Adding IP Addresses..." + + while [ $# -gt 0 ]; do + external=$1 + interface=$2 + label= + + if [ "$interface" != "${interface%:*}" ]; then + label="${interface#*:}" + interface="${interface%:*}" + label="label $interface:$label" + fi + + shift 2 + + list_search $external $(find_interface_addresses $interface) || do_one + done +} + +detect_gateway() # $1 = interface +{ + local interface=$1 + # + # First assume that this is some sort of point-to-point interface + # + gateway=$( find_peer $(ip addr ls $interface ) ) + # + # Maybe there's a default route through this gateway already + # + [ -n "$gateway" ] || gateway=$(find_gateway $(ip route ls dev $interface)) + # + # Last hope -- is there a load-balancing route through the interface? + # + [ -n "$gateway" ] || gateway=$(find_nexthop $interface) + # + # Be sure we found one + # + [ -n "$gateway" ] && echo $gateway +} + +# +# Disable IPV6 +# +disable_ipv6() { + local foo="$(ip -f inet6 addr ls 2> /dev/null)" + + if [ -n "$foo" ]; then + if qt mywhich ip6tables; then + ip6tables -P FORWARD DROP + ip6tables -P INPUT DROP + ip6tables -P OUTPUT DROP + ip6tables -F + ip6tables -X + ip6tables -A OUTPUT -o lo -j ACCEPT + ip6tables -A INPUT -i lo -j ACCEPT + else + error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables" + fi + fi +} + +# +# Add a logging rule. +# +do_log_rule_limit() # $1 = log level, $2 = chain, $3 = display Chain $4 = disposition , $5 = rate limit $6=log tag $7=command $... = predicates for the rule +{ + local level=$1 + local chain=$2 + local displayChain=$3 + local disposition=$4 + local rulenum= + local limit= + local tag= + local command= + local prefix + local base=$(chain_base $displayChain) + local pf + + limit="${5:-$LOGLIMIT}" # Do this here rather than in the declaration above to appease /bin/ash. + tag=${6:+$6 } + command=${7:--A} + + shift 7 + + if [ -n "$tag" -a -n "$LOGTAGONLY" ]; then + displayChain=$tag + tag= + fi + + if [ -n "$LOGRULENUMBERS" ]; then + # + # Hack for broken printf on some lightweight shells + # + [ $(printf "%d" 1) = "1" ] && pf=printf || pf=$(mywhich printf) + + eval rulenum=\$${base}_logrules + + rulenum=${rulenum:-1} + + prefix="$($pf "$LOGFORMAT" $displayChain $rulenum $disposition)${tag}" + + rulenum=$(($rulenum + 1)) + eval ${base}_logrules=$rulenum + else + prefix="$(printf "$LOGFORMAT" $displayChain $disposition)${tag}" + fi + + if [ ${#prefix} -gt 29 ]; then + prefix="$(echo $prefix | truncate 29)" + error_message "WARNING: Log Prefix shortened to \"$prefix\"" + fi + + case $level in + ULOG) + $IPTABLES $command $chain $@ $limit -j ULOG $LOGPARMS --ulog-prefix "$prefix" + ;; + *) + $IPTABLES $command $chain $@ $limit -j LOG $LOGPARMS --log-level $level --log-prefix "$prefix" + ;; + esac + + if [ $? -ne 0 ] ; then + [ -z "$STOPPING" ] && { stop_firewall; exit 2; } + fi +} + +do_log_rule() # $1 = log level, $2 = chain, $3 = disposition , $... = predicates for the rule +{ + local level=$1 + local chain=$2 + local disposition=$3 + + shift 3 + + do_log_rule_limit $level $chain $chain $disposition "$LOGLIMIT" "" -A $@ +} + +delete_tc1() +{ + clear_one_tc() { + tc qdisc del dev $1 root 2> /dev/null + tc qdisc del dev $1 ingress 2> /dev/null + + } + + run_user_exit tcclear + + run_ip link list | \ + while read inx interface details; do + case $inx in + [0-9]*) + clear_one_tc ${interface%:} + ;; + *) + ;; + esac + done +} + +# +# Detect a device's MTU +# +get_device_mtu() # $1 = device +{ + local output="$(ip link ls dev $1 2> /dev/null)" # quotes required for /bin/ash + + if [ -n "$output" ]; then + echo $(find_mtu $output) + else + echo 1500 + fi +} + +# +# Undo changes to routing +# +undo_routing() { + + if [ -z "$NOROUTES" ]; then + # + # Restore rt_tables database + # + if [ -f ${VARDIR}/rt_tables ]; then + cp -f ${VARDIR}/rt_tables /etc/iproute2/ && progress_message "/etc/iproute2/rt_tables database restored" + rm -f ${VARDIR}/rt_tables + fi + # + # Restore the rest of the routing table + # + if [ -f ${VARDIR}/undo_routing ]; then + . ${VARDIR}/undo_routing + progress_message "Shorewall-generated routing tables and routing rules removed" + rm -f ${VARDIR}/undo_routing + fi + fi + +} + +restore_default_route() { + if [ -z "$NOROUTES" -a -f ${VARDIR}/default_route ]; then + local default_route= route + + while read route ; do + case $route in + default*) + if [ -n "$default_route" ]; then + case "$default_route" in + *metric*) + # + # Don't restore a route with a metric -- we only replace the one with metric == 0 + # + qt ip route delete default metric 0 && \ + progress_message "Default Route with metric 0 deleted" + ;; + *) + qt ip route replace $default_route && \ + progress_message "Default Route (${default_route# }) restored" + ;; + esac + + break + fi + + default_route="$default_route $route" + ;; + *) + default_route="$default_route $route" + ;; + esac + done < ${VARDIR}/default_route + + rm -f ${VARDIR}/default_route + fi +} + +# +# Determine how to do "echo -e" +# + +find_echo() { + local result + + result=$(echo "a\tb") + [ ${#result} -eq 3 ] && { echo echo; return; } + + result=$(echo -e "a\tb") + [ ${#result} -eq 3 ] && { echo "echo -e"; return; } + + result=$(which echo) + [ -n "$result" ] && { echo "$result -e"; return; } + + echo echo +} diff --git a/New/lib.config b/New/lib.config new file mode 100644 index 000000000..dd5475f9e --- /dev/null +++ b/New/lib.config @@ -0,0 +1,2184 @@ +#!/bin/sh +# +# Shorewall 3.4 -- /usr/share/shorewall/lib.config +# +# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] +# +# (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) +# +# Complete documentation is available at http://shorewall.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of Version 2 of the GNU General Public License +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA +# +# This library contains the configuration file parsing code common to +# /usr/share/shorewall/compiler and /usr/share/shorewall/firewall +# + +# +# Replace commas with spaces and echo the result +# +separate_list() { + local list="$@" + local part + local newlist + local firstpart + local lastpart + local enclosure + + case "$list" in + *,|,*|*,,*|*[[:space:]]*) + # + # There's been whining about us not catching embedded white space in + # comma-separated lists. This is an attempt to snag some of the cases. + # + # The 'TERMINATOR' function will be set by the 'firewall' script to + # either 'startup_error' or 'fatal_error' depending on the command and + # command phase + # + [ -n "$TERMINATOR" ] && \ + $TERMINATOR "Invalid comma-separated list \"$@\"" + echo "WARNING -- invalid comma-separated list \"$@\"" >&2 + ;; + *\[*\]*) + # + # Where we need to embed comma-separated lists within lists, we enclose them + # within square brackets. + # + firstpart=${list%%\[*} + lastpart=${list#*\[} + enclosure=${lastpart%%\]*} + lastpart=${lastpart#*\]} + case $lastpart in + \,*) + case $firstpart in + *\,) + echo "$(separate_list ${firstpart%,}) [$enclosure] $(separate_list ${lastpart#,})" + ;; + *) + echo "$(separate_list $firstpart)[$enclosure] $(separate_list ${lastpart#,})" + ;; + esac + ;; + *) + case $firstpart in + *\,) + echo "$(separate_list ${firstpart%,}) [$enclosure]$(separate_list $lastpart)" + ;; + *) + echo "$(separate_list $firstpart)[$enclosure]$(separate_list $lastpart)" + ;; + esac + ;; + esac + return + ;; + esac + + list="$@" + part="${list%%,*}" + newlist="$part" + + while [ "x$part" != "x$list" ]; do + list="${list#*,}"; + part="${list%%,*}"; + newlist="$newlist $part"; + done + + echo "$newlist" +} + +# +# Undo the effect of 'separate_list()' +# +combine_list() +{ + local f o= + + for f in $* ; do + o="${o:+$o,}$f" + done + + echo $o +} + +# +# Display elements of a list with leading white space +# +display_list() # $1 = List Title, rest of $* = list to display +{ + [ $# -gt 1 ] && echo " $*" +} + +# +# Determine if a chain is a policy chain +# +is_policy_chain() # $1 = name of chain +{ + eval test \"\$${1}_is_policy\" = Yes +} + +# Function to truncate a string -- It uses 'cut -b -' +# rather than ${v:first:last} because light-weight shells like ash and +# dash do not support that form of expansion. +# + +truncate() # $1 = length +{ + cut -b -${1} +} + +# +# Return a space separated list of values matching +# +list_walk() # $1 = element to search for, $2-$n = list +{ + local e=$1 result= + + while [ $# -gt 1 ]; do + shift + case $1 in + $e*) + result="$result ${1##$e}" + ;; + esac + done + echo $result +} + +# +# Functions to count list elements +# - - - - - - - - - - - - - - - - +# Whitespace-separated list +# +list_count1() { + echo $# +} +# +# Comma-separated list +# +list_count() { + list_count1 $(separate_list $1) +} + +# +# Filter that expands variables +# +expand_line() { + local line + + while read line; do + echo $(expand $line) + done +} + +# +# Add whitespace after leading "!" +# +fix_bang() +{ + local result= + + while [ $# -gt 0 ]; do + case $1 in + !*) + result="$result ! ${1#!}" + ;; + *) + result="$result $1" + ;; + esac + shift + done + + echo $result +} + +# +# Read the zones file and find the firewall zone +# +get_firewall_zone() { + local zone type rest comment='#*' f=$(find_file zones) + + [ -f $f ] || startup_error "Unable to find zones file" + + while read zone type rest; do + case $zone in + $comment) + ;; + *) + if [ "x$type" = xfirewall ]; then + FW=$zone + return + fi + ;; + esac + done < $f + + startup_error "No firewall zone defined in $f" +} + +# +# This function assumes that the TMP_DIR variable is set and that +# its value names an existing directory. +# +determine_zones() +{ + local zone parent parents rest new_zone_file= r + + merge_zone() + { + local z zones="$ZONES" merged= + + if [ -n "$parents" ]; then + ZONES= + for z in $zones; do + if [ -z "$merged" ] && list_search $z $parents; then + ZONES="$ZONES $zone" + merged=Yes + fi + ZONES="$ZONES $z" + done + else + ZONES="$ZONES $zone" + fi + } + + ZONES= + IPV4_ZONES= + IPSEC_ZONES= + + [ "$IPSECFILE" = zones ] && new_zone_file=Yes || test -n "${FW:=fw}" + + while read zone type rest; do + case $zone in + *:*) + parents=${zone#*:} + zone=${zone%:*} + [ -n "$zone" ] || startup_error "Invalid nested zone syntax: :$parents" + parents=$(separate_list $parents) + eval ${zone}_parents=\"$parents\" + ;; + *) + parents= + eval ${zone}_parents= + ;; + esac + + for parent in $parents; do + [ "$parent" = "$FW" ] && startup_error "Sub-zones of the firewall zone are not allowed" + list_search $parent $ZONES || startup_error "Parent zone not defined: $parent" + done + + [ ${#zone} -gt $MAXZONENAMELENGTH ] && startup_error "Zone name longer than $MAXZONENAMELENGTH characters: $zone" + + case "$zone" in + [0-9*]) + startup_error "Illegal zone name \"$zone\" in zones file" + ;; + all|none) + startup_error "Reserved zone name \"$zone\" in zones file" + ;; + esac + + if [ -n "$new_zone_file" ]; then + case ${type:=ipv4} in + ipv4|IPv4|IPV4|plain|-) + list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" + merge_zone + IPV4_ZONES="$IPV4_ZONES $zone" + ;; + ipsec|IPSEC|ipsec4|IPSEC4) + list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" + [ -n "$POLICY_MATCH" ] || startup_error "Your kernel and/or iptables does not support policy match" + eval ${zone}_is_ipsec=Yes + eval ${zone}_is_complex=Yes + merge_zone + IPSEC_ZONES="$IPSEC_ZONES $zone" + ;; + firewall) + [ -n "$FW" ] && startup_error "Only one firewall zone may be defined" + list_search $zone $ZONES && startup_error "Zone $zone is defined more than once" + [ -n "$parents" ] && startup_error "The firewall zone may not be nested" + for r in $rest; do + [ "x$r" = x- ] || startup_error "OPTIONS not allowed on the firewall zone" + done + FW=$zone + ;; + *) + startup_error "Invalid Zone Type: $type" + ;; + esac + + eval ${zone}_type=$type + else + list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" + ZONES="$ZONES $zone" + IPV4_ZONES="$IPV4_ZONES $zone" + eval ${zone}_type=ipv4 + fi + done < $TMP_DIR/zones + + [ -z "$ZONES" ] && startup_error "No ipv4 or ipsec Zones Defined" + + [ -z "$FW" ] && startup_error "No Firewall Zone Defined" +} + +# +# Validate the zone names and options in the interfaces file +# +validate_interfaces_file() { + local wildcard + local found_obsolete_option= + local z interface networks options r iface option + + while read z interface networks options; do + r="$z $interface $networks $options" + + [ "x$z" = "x-" ] && z= + + if [ -n "$z" ]; then + validate_zone $z || startup_error "Invalid zone ($z) in record \"$r\"" + fi + + list_search $interface $ALL_INTERFACES && \ + startup_error "Duplicate Interface $interface" + + wildcard= + + case $interface in + *:*|+) + startup_error "Invalid Interface Name: $interface" + ;; + *+) + wildcard=Yes + ;; + esac + + ALL_INTERFACES="$ALL_INTERFACES $interface" + options=$(separate_list $options) + iface=$(chain_base $interface) + + eval ${iface}_broadcast="$networks" + eval ${iface}_zone="$z" + eval ${iface}_options=\"$options\" + + for option in $options; do + case $option in + -) + ;; + dhcp|tcpflags|arp_filter|routefilter|logmartians|sourceroute|blacklist|nosmurfs|upnp|-) + ;; + proxyarp) + lib_load proxyarp "The 'proxyarp' option on interface $interface" + ;; + maclist) + lib_load maclist "The 'maclist' option" + ;; + norfc1918) + if [ "$PROGRAM" != compiler ]; then + addr=$(ip -f inet addr show $interface 2> /dev/null | grep inet | head -n1) + if [ -n "$addr" ]; then + addr=$(echo $addr | sed 's/inet //;s/\/.*//;s/ peer.*//') + for network in 10.0.0.0/8 176.16.0.0/12 192.168.0.0/16; do + if in_network $addr $network; then + startup_error "The 'norfc1918' option may not be specified on an interface with an RFC 1918 address. Interface:$interface" + fi + done + fi + fi + ;; + arp_ignore=*) + eval ${iface}_arp_ignore=${option#*=} + ;; + arp_ignore) + eval ${iface}_arp_ignore=1 + ;; + detectnets) + [ -n "$wildcard" ] && \ + startup_error "The \"detectnets\" option may not be used with a wild-card interface" + [ -n "$EXPORT" ] && \ + startup_error "'detectnets' not permitted with the -e run-line option" + ;; + routeback) + [ -n "$z" ] || startup_error "The routeback option may not be specified on a multi-zone interface" + ;; + *) + error_message "WARNING: Invalid option ($option) in record \"$r\"" + ;; + esac + done + done < $TMP_DIR/interfaces + + [ -z "$ALL_INTERFACES" ] && startup_error "No Interfaces Defined" +} + +# +# Process the ipsec information in the zones file +# +setup_ipsec() { + local zone using_ipsec= + # + # Add a --set-mss rule to the passed chain + # + set_mss1() # $1 = chain, $2 = MSS + { + eval local policy=\$${1}_policy + + if [ "$policy" != NONE ]; then + ensurechain $1 + run_iptables -I $1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss $2 + fi + } + # + # Set up rules to set MSS to and/or from zone "$zone" + # + set_mss() # $1 = MSS value, $2 = _in, _out or "" + { + for z in $ZONES $FW; do + case $2 in + _in) + set_mss1 ${zone}2${z} $1 + ;; + _out) + set_mss1 ${z}2${zone} $1 + ;; + *) + set_mss1 ${z}2${zone} $1 + set_mss1 ${zone}2${z} $1 + ;; + esac + done + } + + do_options() # $1 = _in, _out or "" - $2 = option list + { + local option newoptions= val + + [ x${2} = x- ] && return + + for option in $(separate_list $2); do + val=${option#*=} + + case $option in + mss=[0-9]*) [ "$PROGRAM" = compiler ] && set_mss $val $1 ;; + strict) newoptions="$newoptions --strict" ;; + next) newoptions="$newoptions --next" ;; + reqid=*) newoptions="$newoptions --reqid $val" ;; + spi=*) newoptions="$newoptions --spi $val" ;; + proto=*) newoptions="$newoptions --proto $val" ;; + mode=*) newoptions="$newoptions --mode $val" ;; + tunnel-src=*) newoptions="$newoptions --tunnel-src $val" ;; + tunnel-dst=*) newoptions="$newoptions --tunnel-dst $val" ;; + reqid!=*) newoptions="$newoptions ! --reqid $val" ;; + spi!=*) newoptions="$newoptions ! --spi $val" ;; + proto!=*) newoptions="$newoptions ! --proto $val" ;; + mode!=*) newoptions="$newoptions ! --mode $val" ;; + tunnel-src!=*) newoptions="$newoptions ! --tunnel-src $val" ;; + tunnel-dst!=*) newoptions="$newoptions ! --tunnel-dst $val" ;; + *) fatal_error "Invalid option \"$option\" for zone $zone" ;; + esac + done + + if [ -n "$newoptions" ]; then + [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" + eval ${zone}_is_complex=Yes + eval ${zone}_ipsec${1}_options=\"${newoptions# }\" + fi + } + + case $IPSECFILE in + zones) + f=zones + progress_message "$DOING IPSEC..." + [ $PROGRAM = compiler -a -n "$IPSEC_ZONES" ] && save_progress_message "Setting up IPSEC management..." + ;; + ipsec) + using_ipsec=Yes + if [ -s ${TMP_DIR}/ipsec ]; then + progress_message "$DOING ipsec..." + [ $PROGRAM = compiler ] && save_progress_message "Setting up IPSEC management..." + fi + ;; + esac + + while read zone type options in_options out_options mss; do + if [ -n "$using_ipsec" ]; then + validate_zone1 $zone || fatal_error "Unknown zone: $zone" + fi + + if [ -n "$type" ]; then + if [ -n "$using_ipsec" ]; then + case $type in + No|no) + ;; + Yes|yes) + [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" + eval ${zone}_is_ipsec=Yes + eval ${zone}_is_complex=Yes + eval ${zone}_type=ipsec4 + ;; + *) + fatal_error "Invalid IPSEC column contents" + ;; + esac + fi + + do_options "" $options + do_options "_in" $in_options + do_options "_out" $out_options + fi + + done < $TMP_DIR/$f +} + +# +# Validate the zone names and options in the hosts file +# +validate_hosts_file() { + local z hosts options r interface host option zports ipsec= + + check_bridge_port() + { + list_search ${interface}:${1} $zports || zports="$zports ${interface}:${1}" + list_search $1 $ALL_PORTS || ALL_PORTS="$ALL_PORTS $1" + } + + while read z hosts options; do + r="$z $hosts $options" + validate_zone1 $z || startup_error "Invalid zone ($z) in record \"$r\"" + + case $hosts in + *:*) + + interface=${hosts%%:*} + iface=$(chain_base $interface) + + list_search $interface $ALL_INTERFACES || \ + startup_error "Unknown interface ($interface) in record \"$r\"" + + hosts=${hosts#*:} + ;; + *) + startup_error "Invalid HOST(S) column contents: $hosts" + ;; + esac + + eval zports=\$${z}_ports + + if [ -z "$BRIDGING" ]; then + case $hosts in + *!*!*) + startup_error "Invalid hosts file entry: \"$r\"" + ;; + !*) + hosts=0.0.0.0/0 + eval ${z}_is_complex=Yes + ;; + *!*) + hosts=${hosts%%!*} + eval ${z}_is_complex=Yes + ;; + esac + fi + + for host in $(separate_list $hosts); do + if [ -n "$BRIDGING" ]; then + case $host in + *:*) + known_interface ${host%:*} && \ + startup_error "Bridged interfaces may not be defined in ${CONFDIR}/interfaces: $host" + check_bridge_port ${host%%:*} + ;; + *.*.*) + ;; + *+|+*|*!*) + eval ${z}_is_complex=Yes + ;; + *) + known_interface $host && \ + startup_error "Bridged interfaces may not be defined in ${CONFDIR}/interfaces: $host" + check_bridge_port $host + ;; + esac + else + case $host in + *.*.*) + ;; + +*) + eval ${z}_is_complex=Yes + ;; + *) + startup_error "BRIDGING=Yes is needed for this zone definition: $r" + ;; + esac + fi + + for option in $(separate_list $options) ; do + case $option in + norfc1918|blacklist|tcpflags|nosmurfs|-) + ;; + maclist) + lib_load maclist "The 'maclist' option" + ;; + ipsec) + [ -n "$POLICY_MATCH" ] || \ + startup_error "Your kernel and/or iptables does not support policy match: ipsec" + eval ${z}_ipsec_hosts=\"\$${z}_ipsec_hosts $interface:$host\" + eval ${z}_is_complex=Yes + ipsec=Yes + ;; + routeback) + eval ${z}_routeback=\"$interface:$host \$${z}_routeback\" + ;; + *) + error_message "WARNING: Invalid option ($option) in record \"$r\"" + ;; + esac + done + done + + [ -n "$zports" ] && eval ${z}_ports=\"$zports\" + + done < $TMP_DIR/hosts + + [ -n "$ALL_PORTS" ] && progress_message2 " Bridge ports are: $ALL_PORTS" + + [ -n "${IPSEC_ZONES}${ipsec}" ] || POLICY_MATCH= +} + +# +# Find interfaces to a given zone +# +# Search the variables representing the contents of the interfaces file and +# for each record matching the passed ZONE, echo the expanded contents of +# the "INTERFACE" column +# +find_interfaces() # $1 = interface zone +{ + local zne=$1 + local z + local interface + + for interface in $ALL_INTERFACES; do + eval z=\$$(chain_base $interface)_zone + [ "x${z}" = x${zne} ] && echo $interface + done +} + +# +# Forward Chain for an interface +# +forward_chain() # $1 = interface +{ + echo $(chain_base $1)_fwd +} + +# +# Input Chain for an interface +# +input_chain() # $1 = interface +{ + echo $(chain_base $1)_in +} + +# +# Output Chain for an interface +# +output_chain() # $1 = interface +{ + echo $(chain_base $1)_out +} + +# +# Masquerade Chain for an interface +# +masq_chain() # $1 = interface +{ + echo $(chain_base $1)_masq +} + +# +# MAC Verification Chain for an interface +# +mac_chain() # $1 = interface +{ + echo $(chain_base $1)_mac +} + +macrecent_target() # $1 - interface +{ + [ -n "$MACLIST_TTL" ] && echo $(chain_base $1)_rec || echo RETURN +} + +# +# Functions for creating dynamic zone rules +# +dynamic_fwd() # $1 = interface +{ + echo $(chain_base $1)_dynf +} + +dynamic_in() # $1 = interface +{ + echo $(chain_base $1)_dyni +} + +dynamic_out() # $1 = interface +{ + echo $(chain_base $1)_dyno +} + +dynamic_chains() #$1 = interface +{ + local c=$(chain_base $1) + + echo ${c}_dyni ${c}_dynf ${c}_dyno +} + +# +# DNAT Chain from a zone +# +dnat_chain() # $1 = zone +{ + echo ${1}_dnat +} + +# +# SNAT Chain to an interface +# +snat_chain() # $1 = interface +{ + echo $(chain_base $1)_snat +} + +# +# ECN Chain to an interface +# +ecn_chain() # $1 = interface +{ + echo $(chain_base $1)_ecn +} + +# +# First chains for an interface +# +first_chains() #$1 = interface +{ + local c=$(chain_base $1) + + echo ${c}_fwd ${c}_in +} + +# +# Out Chain to an interface +# +out_chain() # $1 = interface +{ + echo $(chain_base $1)_out +} + +# +# Horrible hack to work around an iptables limitation +# +iprange_echo() +{ + if [ -n "$KLUDGEFREE" ]; then + echo "-m iprange $@" + elif [ -f $TMP_DIR/iprange ]; then + echo $@ + else + echo "-m iprange $@" + > $TMP_DIR/iprange + fi +} + +# +# Get set flags (ipsets). +# +get_set_flags() # $1 = set name and optional [levels], $2 = src or dst +{ + local temp setname=$1 options=$2 + + [ -n "$IPSET_MATCH" ] || fatal_error "Your kernel and/or iptables does not include ipset match: $1" + + case $1 in + *\[[1-6]\]) + temp=${1#*\[} + temp=${temp%\]} + setname=${1%\[*} + while [ $temp -gt 1 ]; do + options="$options,$2" + temp=$(($temp - 1)) + done + ;; + *\[*\]) + options=${1#*\[} + options=${options%\]} + setname=${1%\[*} + ;; + *) + ;; + esac + + echo "--set ${setname#+} $options" +} + +# +# Horrible hack to work around an iptables limitation +# +physdev_echo() +{ + if [ -n "$KLUDGEFREE" ]; then + echo -m physdev $@ + elif [ -f $TMP_DIR/physdev ]; then + echo $@ + else + echo -m physdev $@ + > $TMP_DIR/physdev + fi +} + +# +# Source IP range +# +source_ip_range() # $1 = Address or Address Range +{ + [ $# -gt 0 ] && case $1 in + *.*.*.*-*.*.*.*) + case $1 in + !*) + iprange_echo "! --src-range ${1#!}" + ;; + *) + iprange_echo "--src-range $1" + ;; + esac + ;; + !+*) + echo "-m set ! $(get_set_flags ${1#!} src)" + ;; + +*) + echo "-m set $(get_set_flags $1 src)" + ;; + *) + echo "-s $1" + ;; + esac +} + +# +# Destination IP range +# +dest_ip_range() # $1 = Address or Address Range +{ + [ $# -gt 0 ] && case $1 in + *.*.*.*-*.*.*.*) + case $1 in + !*) + iprange_echo "! --dst-range ${1#!}" + ;; + *) + iprange_echo "--dst-range $1" + ;; + esac + ;; + !+*) + echo "-m set ! $(get_set_flags ${1#!} dst)" + ;; + +*) + echo "-m set $(get_set_flags $1 dst)" + ;; + *) + echo "-d $1" + ;; + esac +} + +both_ip_ranges() # $1 = Source address or range, $2 = dest address or range +{ + local rangeprefix= setprefix= rangematch= setmatch= + + case $1 in + *.*.*.*-*.*.*.*) + rangeprefix="-m iprange" + rangematch="--src-range $1" + ;; + !+*) + setprefix="-m set" + setmatch="! $(get_set_flags ${1#!} src)" + ;; + +*) + setprefix="-m set" + setmatch="$(get_set_flags $1 src)" + ;; + *) + rangematch="-s $1" + ;; + esac + + case $2 in + *.*.*.*-*.*.*.*) + rangeprefix="-m iprange" + rangematch="$rangematch --dst-range $2" + ;; + !+*) + setprefix="-m set" + match="$setmatch ! $(get_set_flags ${2#!} dst)" + ;; + +*) + setprefix="-m set" + setmatch="$setmatch $(get_set_flags $2 dst)" + ;; + *) + rangematch="$rangematch -d $2" + ;; + esac + + echo "$rangeprefix $rangematch $setprefix $setmatch" +} + +# +# Loosly Match the name of an interface +# + +if_match() # $1 = Name in interfaces file - may end in "+" + # $2 = Full interface name - may also end in "+" +{ + local pattern=${1%+} + + case $1 in + *+) + test "x$(echo $2 | truncate ${#pattern} )" = "x${pattern}" + ;; + *) + test "x$1" = "x$2" + ;; + esac +} + +# +# We allow hosts to be specified by IP address or by physdev. These two functions +# are used to produce the proper match in a netfilter rule. +# +match_source_hosts() +{ + if [ -n "$BRIDGING" ]; then + case $1 in + *:*) + physdev_echo "--physdev-in ${1%:*} $(source_ip_range ${1#*:})" + ;; + *.*.*.*|+*|!+*) + echo $(source_ip_range $1) + ;; + *) + physdev_echo "--physdev-in $1" + ;; + esac + else + echo $(source_ip_range $1) + fi +} + +match_dest_hosts() +{ + if [ -n "$BRIDGING" ]; then + case $1 in + *:*) + physdev_echo "--physdev-out ${1%:*} $(dest_ip_range ${1#*:})" + ;; + *.*.*.*|+*|!+*) + echo $(dest_ip_range $1) + ;; + *) + physdev_echo "--physdev-out $1" + ;; + esac + else + echo $(dest_ip_range $1) + fi +} +# +# Matches for either or :
+# +match_source() +{ + case "$1" in + *:*) + echo "-i ${1%%:*} $(match_source_hosts ${1#*:})" + ;; + *) + echo $(dest_ip_range $1) + ;; + esac +} + +match_dest() +{ + case "$1" in + *:*) + echo "-o ${1%%:*} $(match_dest_hosts ${1#*:})" + ;; + *) + echo $(dest_ip_range $1) + ;; + esac +} + +# +# Similarly, the source or destination in a rule can be qualified by a device name. If +# the device is defined in ${CONFDIR}/interfaces then a normal interface match is +# generated (-i or -o); otherwise, a physdev match is generated. +#------------------------------------------------------------------------------------- +# +# loosely match the passed interface with those in ${CONFDIR}/interfaces. +# +known_interface() # $1 = interface name +{ + local iface + + for iface in $ALL_INTERFACES ; do + if if_match $iface $1 ; then + return 0 + fi + done + + return 1 +} + +known_port() # $1 = port name +{ + local port + + for port in $ALL_PORTS ; do + if if_match $port $1 ; then + return 0 + fi + done + + return 1 +} + +match_source_dev() +{ + if [ -n "$BRIDGING" ]; then + known_port $1 && physdev_echo "--physdev-in $1" || echo -i $1 + elif known_interface $1; then + echo -i $1 + elif [ -n "$PHYSDEV_MATCH" ]; then + physdev_echo "--physdev-in $1" + else + echo -i $1 + fi +} + +match_dest_dev() +{ + if [ -n "$BRIDGING" ]; then + known_port $1 && physdev_echo "--physdev-out $1" || echo -o $1 + elif known_interface $1; then + echo -o $1 + elif [ -n "$PHYSDEV_MATCH" ]; then + physdev_echo "--physdev-out $1" + else + echo -o $1 + fi +} + +verify_interface() +{ + known_interface $1 || { [ -n "$BRIDGING" ] && known_port $1 ; } +} + +# +# Determine if communication to/from a host is encrypted using IPSEC +# +is_ipsec_host() # $1 = zone, $2 = host +{ + eval local is_ipsec=\$${1}_is_ipsec + eval local hosts=\"\$${1}_ipsec_hosts\" + + test -n "$is_ipsec" || list_search $2 $hosts +} + +# +# Generate a match for decrypted packets +# +match_ipsec_in() # $1 = zone, $2 = host +{ + if is_ipsec_host $1 $2 ; then + eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_in_options\" + echo "-m policy --pol ipsec --dir in $options" + elif [ -n "$POLICY_MATCH" ]; then + echo "-m policy --pol none --dir in" + fi +} + +# +# Generate a match for packets that will be encrypted +# +match_ipsec_out() # $1 = zone, $2 = host +{ + if is_ipsec_host $1 $2 ; then + eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_out_options\" + echo "-m policy --pol ipsec --dir out $options" + elif [ -n "$POLICY_MATCH" ]; then + echo "-m policy --pol none --dir out" + fi +} + +# +# Jacket for ip_range() that takes care of iprange match +# + +firewall_ip_range() # $1 = IP address or range +{ + [ -n "$IPRANGE_MATCH" ] && echo $1 || ip_range $1 +} + +# +# +# Find hosts in a given zone +# +# Read hosts file and for each record matching the passed ZONE, +# echo the expanded contents of the "HOST(S)" column +# +find_hosts() # $1 = host zone +{ + local hosts interface address addresses + + while read z hosts options; do + if [ "x$(expand $z)" = "x$1" ]; then + interface=${hosts%%:*} + addresses=${hosts#*:} + case $addresses in + !*) + echo $interface:0.0.0.0/0 + ;; + *) + for address in $(separate_list ${addresses%%!*}); do + echo $interface:$address + done + ;; + esac + fi + done < $TMP_DIR/hosts +} + +# +# +# Find exclusions in a given zone +# +# Read hosts file and for each record matching the passed ZONE, +# echo any exclusions +# +find_exclusions() # $1 = host zone +{ + local hosts interface address addresses + + while read z hosts options; do + if [ "x$z" = "x$1" ]; then + interface=${hosts%%:*} + addresses=${hosts#*:} + case $addresses in + *!*) + for address in $(separate_list ${addresses#*!}); do + echo $interface:$address + done + ;; + esac + fi + done < $TMP_DIR/hosts +} + +# +# Determine the interfaces on the firewall +# +# For each zone, create a variable called ${zone}_interfaces. This +# variable contains a space-separated list of interfaces to the zone +# +determine_interfaces() { + for zone in $ZONES; do + interfaces=$(find_interfaces $zone) + interfaces=$(echo $interfaces) # Remove extra trash + eval ${zone}_interfaces=\"\$interfaces\" + done +} + +# +# Determine if an interface has a given option +# +interface_has_option() # $1 = interface, #2 = option +{ + local options + + eval options=\$$(chain_base $1)_options + + list_search $2 $options +} + +# +# Determine the defined hosts in each zone +# +determine_hosts() { + for zone in $ZONES; do + hosts=$(find_hosts $zone) + hosts=$(echo $hosts) # Remove extra trash + exclusions=$(find_exclusions $zone) + exclusions=$(echo $exclusions) # Remove extra trash + + eval interfaces=\$${zone}_interfaces + + for interface in $interfaces; do + if interface_has_option $interface detectnets; then + networks=$(get_routed_networks $interface "detectnets not allowed on interface with default route - $interface" ) + else + networks=0.0.0.0/0 + fi + + for network in $networks; do + if [ -z "$hosts" ]; then + hosts=$interface:$network + else + hosts="$hosts $interface:$network" + fi + + if interface_has_option $interface routeback; then + eval ${zone}_routeback=\"$interface:$network \$${zone}_routeback\" + fi + done + done + + interfaces= + + for host in $hosts; do + interface=${host%:*} + if list_search $interface $interfaces; then + list_search $interface:0.0.0.0/0 $hosts && \ + startup_error "Invalid zone definition for zone $zone" + list_search $interface:0/0 $hosts && \ + startup_error "Invalid zone definition for zone $zone" + eval ${zone}_is_complex=Yes + else + if [ -z "$interfaces" ]; then + interfaces=$interface + else + interfaces="$interfaces $interface" + fi + fi + done + + eval ${zone}_exclusions="\$exclusions" + eval ${zone}_interfaces="\$interfaces" + eval ${zone}_hosts="\$hosts" + + if [ -n "$hosts" ]; then + if [ $VERBOSE -ge 1 ]; then + [ -n "$exclusions" ] && display_list "$zone Zone:" $hosts minus "($exclusions)" || display_list "$zone Zone:" $hosts + fi + else + error_message "WARNING: Zone $zone is empty" + fi + done +} + +# +# Ensure that the passed zone is defined in the zones file or is the firewall +# +validate_zone() # $1 = zone +{ + list_search $1 $ZONES $FW +} +# +# Ensure that the passed zone is defined in the zones file. +# +validate_zone1() # $1 = zone +{ + list_search $1 $ZONES +} + +# +# Format a match by the passed MAC address +# The passed address begins with "~" and uses "-" as a separator between bytes +# Example: ~01-02-03-04-05-06 +# +mac_match() # $1 = MAC address formated as described above +{ + echo "--match mac --mac-source $(echo $1 | sed 's/~//;s/-/:/g')" +} + +# +# Find interfaces that have the passed option specified +# +find_interfaces_by_option() # $1 = option +{ + for interface in $ALL_INTERFACES; do + eval options=\$$(chain_base $interface)_options + list_search $1 $options && echo $interface + done +} + +# +# This slightly slower version is used to find both the option and option followed +# by equal sign ("=") and a value +# +find_interfaces_by_option1() # $1 = option +{ + local options option + + for interface in $ALL_INTERFACES; do + eval options=\$$(chain_base $interface)_options + for option in $options; do + if [ "${option%=*}" = "$1" ]; then + echo $interface + break + fi + done + done +} + +# +# Find hosts with the passed option +# +find_hosts_by_option() # $1 = option +{ + local ignore hosts interface address addresses options ipsec= list + + while read ignore hosts options; do + list=$(separate_list $options) + if list_search $1 $list; then + list_search ipsec $list && ipsec=ipsec || ipsec=none + interface=${hosts%%:*} + addresses=${hosts#*:} + for address in $(separate_list $addresses); do + echo ${ipsec}^$interface:$address + done + fi + done < $TMP_DIR/hosts + + for interface in $ALL_INTERFACES; do + interface_has_option $interface $1 && \ + echo none^${interface}:0.0.0.0/0 + done +} + +# +# Process the routestopped file either adding or deleting rules +# +process_routestopped() # $1 = command +{ + local hosts= interface host host1 options networks source= dest= matched + + while read interface host options; do + [ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0 + for h in $(separate_list $host); do + hosts="$hosts $interface:$h" + done + + routeback= + + if [ -n "$options" ]; then + for option in $(separate_list $options); do + case $option in + routeback) + if [ -n "$routeback" ]; then + error_message "WARNING: Duplicate routestopped option ignored: routeback" + else + routeback=Yes + for h in $(separate_list $host); do + run_iptables $1 FORWARD -i $interface -o $interface $(both_ip_ranges $h $h) -j ACCEPT + done + fi + ;; + source) + for h in $(separate_list $host); do + source="$source $interface:$h" + done + ;; + dest) + for h in $(separate_list $host); do + dest="$dest $interface:$h" + done + ;; + critical) + ;; + *) + error_message "WARNING: Unknown routestopped option ignored: $option" + ;; + esac + done + fi + + done < $TMP_DIR/routestopped + + + for host in $hosts; do + interface=${host%:*} + networks=${host#*:} + source_range=$(source_ip_range $networks) + dest_range=$(dest_ip_range $networks) + run_iptables $1 INPUT -i $interface $source_range -j ACCEPT + [ -z "$ADMINISABSENTMINDED" ] && \ + run_iptables $1 OUTPUT -o $interface $dest_range -j ACCEPT + + matched= + + if list_search $host $source ; then + run_iptables $1 FORWARD -i $interface $source_range -j ACCEPT + matched=Yes + fi + + if list_search $host $dest ; then + run_iptables $1 FORWARD -o $interface $dest_range -j ACCEPT + matched=Yes + fi + + if [ -z "$matched" ]; then + for host1 in $hosts; do + [ "$host" != "$host1" ] && run_iptables $1 FORWARD -i $interface -o ${host1%:*} $(both_ip_ranges $networks ${host1#*:}) -j ACCEPT + done + fi + done +} + +process_criticalhosts() +{ + local hosts= interface host h options networks criticalhosts= + + while read interface host options; do + [ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0 || host=$(separate_list $host) + + if [ -n "$options" ]; then + for option in $(separate_list $options); do + case $option in + routeback|source|dest) + ;; + critical) + for h in $host; do + criticalhosts="$criticalhosts $interface:$h" + done + ;; + *) + error_message "WARNING: Unknown routestopped option ignored: $option" + ;; + esac + done + fi + done < $TMP_DIR/routestopped + + if [ -n "$criticalhosts" ]; then + CRITICALHOSTS=$criticalhosts + progress_message "Critical Hosts are:$CRITICALHOSTS" + fi + +} + +# +# Determine which version of mktemp is present (if any) and set MKTEMP accortingly: +# +# None - No mktemp +# BSD - BSD mktemp (Mandrake) +# STD - mktemp.org mktemp +# +find_mktemp() { + local mktemp=`mywhich mktemp 2> /dev/null` + + if [ -n "$mktemp" ]; then + if qt mktemp -V ; then + MKTEMP=STD + else + MKTEMP=BSD + fi + else + MKTEMP=None + fi +} + +# +# create a temporary file. If a directory name is passed, the file will be created in +# that directory. Otherwise, it will be created in a temporary directory. +# +mktempfile() { + + [ -z "$MKTEMP" ] && find_mktemp + + if [ $# -gt 0 ]; then + case "$MKTEMP" in + BSD) + mktemp $1/shorewall.XXXXXX + ;; + STD) + mktemp -p $1 shorewall.XXXXXX + ;; + None) + > $1/shorewall-$$ && echo $1/shorewall-$$ + ;; + *) + error_message "ERROR:Internal error in mktempfile" + ;; + esac + else + case "$MKTEMP" in + BSD) + mktemp /tmp/shorewall.XXXXXX + ;; + STD) + mktemp -t shorewall.XXXXXX + ;; + None) + rm -f /tmp/shorewall-$$ + > /tmp/shorewall-$$ && echo /tmp/shorewall-$$ + ;; + *) + error_message "ERROR:Internal error in mktempfile" + ;; + esac + fi +} + +# +# create a temporary directory +# +mktempdir() { + + [ -z "$MKTEMP" ] && find_mktemp + + case "$MKTEMP" in + STD) + mktemp -td shorewall.XXXXXX + ;; + None|BSD) + # + # Not all versions of the BSD mktemp support the -d option under Linux + # + qt rm -rf /tmp/shorewall-$$ + mkdir -p /tmp/shorewall-$$ && chmod 700 /tmp/shorewall-$$ && echo /tmp/shorewall-$$ + ;; + *) + error_message "ERROR:Internal error in mktempdir" + ;; + esac +} + +# +# Read a file and handle "INCLUDE" directives +# + +read_file() # $1 = file name, $2 = nest count +{ + local first rest + + if [ -f $1 ]; then + while read first rest; do + if [ "x$first" = "xINCLUDE" ]; then + if [ $2 -lt 4 ]; then + read_file $(find_file $(expand ${rest%#*})) $(($2 + 1)) + else + error_message "WARNING: INCLUDE in $1 ignored (nested too deeply)" + fi + else + echo "$first $rest" + fi + done < $1 + else + [ -n "$TERMINATOR" ] && $TERMINATOR "No such file: $1" + echo "WARNING -- No such file: $1" + fi +} + +# +# Strip comments and blank lines from a file and place the result in the +# temporary directory +# +strip_file() # $1 = Base Name of the file, $2 = Full Name of File (optional) +{ + local fname + + if [ ! -f $TMP_DIR/$1 ]; then + [ $# = 1 ] && fname=$(find_file $1) || fname=$2 + + if [ -f $fname ]; then + read_file $fname 0 | cut -d'#' -f1 | grep -v '^[[:space:]]*$' | expand_line > $TMP_DIR/$1 + else + > $TMP_DIR/$1 + fi + fi +} + +# +# Strip the passed file. +# +# Return success if +# a) the stripped file is non-empty and the library was successfully loaded; or +# b) the stripped file is empty but the library had been loaded previously +# +strip_file_and_lib_load() # $1 = logical file name, $2 = library to load if the stripped file is non-empty +{ + local f=$(find_file $1) + + strip_file $1 $f + + if [ -s $TMP_DIR/$1 ]; then + lib_load $2 "A non-empty $1 file ($f)" + return 0 + fi + + eval test -n \"\$LIB_${2}_LOADED\" +} + +# +# Check that a mark value or mask is less that 256 or that it is less than 65536 and +# that it's lower 8 bits are zero. +# +verify_mark() # $1 = value to test +{ + verify_mark2() + { + case $1 in + 0*) + [ $(($1)) -lt 256 ] && return 0 + [ -n "$HIGH_ROUTE_MARKS" ] || return 1 + [ $(($1)) -gt 65535 ] && return 1 + return $(($1 & 0xFF)) + ;; + [1-9]*) + [ $1 -lt 256 ] && return 0 + [ -n "$HIGH_ROUTE_MARKS" ] || return 1 + [ $1 -gt 65535 ] && return 1 + return $(($1 & 0xFF)) + ;; + *) + return 2 + ;; + esac + } + + verify_mark2 $1 || fatal_error "Invalid Mark or Mask value: $1" +} + +# +# Determine the value for a parameter that defaults to Yes +# +added_param_value_yes() # $1 = Parameter Name, $2 = Parameter value +{ + local val="$2" + + if [ -z "$val" ]; then + echo "Yes" + else case $val in + [Yy][Ee][Ss]) + echo "Yes" + ;; + [Nn][Oo]) + echo "" + ;; + *) + startup_error "Invalid value ($val) for $1" + ;; + esac + fi +} + +# +# Determine the value for a parameter that defaults to No +# +added_param_value_no() # $1 = Parameter Name, $2 = Parameter value +{ + local val="$2" + + if [ -z "$val" ]; then + echo "" + else case $val in + [Yy][Ee][Ss]) + echo "Yes" + ;; + [Nn][Oo]) + echo "" + ;; + *) + startup_error "Invalid value ($val) for $1" + ;; + esac + fi +} + +# +# Initialize this program +# +do_initialize() { + + # Run all utility programs using the C locale + # + # Thanks to Vincent Planchenault for this tip # + + export LC_ALL=C + + # Make sure umask is sane + umask 077 + + PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin + # + # Establish termination function + # + TERMINATOR=fatal_error + # + # Clear all configuration variables (shorewall.conf) + # + STARTUP_ENABLED= + # + #VERBOSE is inherited -- VERBOSITY is only used in the CIs + # + # + # Logging + # + LOGFILE= + LOGFORMAT= + LOGTAGONLY= + LOGRATE= + LOGBURST= + LOGALLNEW= + BLACKLIST_LOGLEVEL= + MACLIST_LOG_LEVEL= + TCP_FLAGS_LOG_LEVEL= + RFC1918_LOG_LEVEL= + SMURF_LOG_LEVEL= + LOG_MARTIANS= + # + # Location of files + # + IPTABLES= + #PATH is inherited + SHOREWALL_SHELL= + SUBSYSLOCK= + MODULESDIR= + #CONFIG_PATH is inherited + RESTOREFILE= + IPSECFILE= + # + # Default Actions/Macros + # + DROP_DEFAULT= + REJECT_DEFAULT= + ACCEPT_DEFAULT= + QUEUE_DEFAULT= + # + # Firewall Options + # + IP_FORWARDING= + ADD_IP_ALIASES= + ADD_SNAT_ALIASES= + RETAIN_ALIASES= + TC_ENABLED= + TC_EXPERT= + CLEAR_TC= + MARK_IN_FORWARD_CHAIN= + CLAMPMSS= + ROUTE_FILTER= + DETECT_DNAT_IPADDRS= + MUTEX_TIMEOUT= + ADMINISABSENTMINDED= + BLACKLISTNEWONLY= + DELAYBLACKLISTLOAD= + MODULE_SUFFIX= + DISABLE_IPV6= + BRIDGING= + DYNAMIC_ZONES= + PKTTYPE= + RFC1918_STRICT= + MACLIST_TABLE= + MACLIST_TTL= + SAVE_IPSETS= + MAPOLDACTIONS= + FASTACCEPT= + IMPLICIT_CONTINUE= + HIGH_ROUTE_MARKS= + USE_ACTIONS= + OPTIMIZE= + EXPORTPARAMS= + # + # Packet Disposition + # + MACLIST_DISPOSITION= + TCP_FLAGS_DISPOSITION= + BLACKLIST_DISPOSITION= + # + # Other Globals + # + VERSION= + FW= + USEPKTYPE= + LOGLIMIT= + LOGPARMS= + OUTPUT= + ALL_INTERFACES= + ROUTEMARK_INTERFACES= + PROVIDERS= + CRITICALHOSTS= + EXCLUSION_SEQ=1 + STOPPING= + HAVE_MUTEX= + ALIASES_TO_ADD= + SECTION=ESTABLISHED + SECTIONS= + ALL_PORTS= + ACTIONS= + USEDACTIONS= + DEFAULT_MACROS= + COMMENT= + VERSION_FILE= + LOGRULENUMBERS= + ORIGINAL_POLICY_MATCH= + TMP_DIR=$(mktempdir) + + [ -n "$TMP_DIR" ] && chmod 700 $TMP_DIR || \ + startup_error "Can't create a temporary directory" + + case $PROGRAM in + compiler) + trap "[ -n "$OUTPUT" ] && rm -f $OUTPUT;rm -rf $TMP_DIR; exit 2" 1 2 3 4 5 6 9 + ;; + firewall) + trap "[ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE;rm -rf $TMP_DIR; my_mutex_off; exit 2" 1 2 3 4 5 6 9 + ;; + esac + + ensure_config_path + + VERSION_FILE=$SHAREDIR/version + + [ -f $VERSION_FILE ] && VERSION=$(cat $VERSION_FILE) + + run_user_exit params + + config=$(find_file shorewall.conf) + + if [ -f $config ]; then + if [ -r $config ]; then + progress_message "Processing $config..." + . $config + else + startup_error "Cannot read $config (Hint: Are you root?)" + fi + else + startup_error "$config does not exist!" + fi + + # + # Restore CONFIG_PATH if the shorewall.conf file cleared it + # + ensure_config_path + # + # Determine the capabilities of the installed iptables/netfilter + # We load the kernel modules here to accurately determine + # capabilities when module autoloading isn't enabled. + # + PKTTYPE=$(added_param_value_no PKTTYPE $PKTTYPE) + + [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ] + + if [ -z "$EXPORT" -a $(id -u) -eq 0 ]; then + + load_kernel_modules Yes + + if [ -z "$IPTABLES" ]; then + IPTABLES=$(mywhich iptables 2> /dev/null) + [ -z "$IPTABLES" ] && startup_error "Can't find iptables executable" + else + [ -e "$IPTABLES" ] || startup_error "\$IPTABLES=$IPTABLES does not exist or is not executable" + fi + + f=$(find_file capabilities) + + [ -f $f ] && . $f || determine_capabilities + + else + f=$(find_file capabilities) + [ -f $f ] && . $f || startup_error "The -e flag requires a capabilities file" + fi + + ORIGINAL_POLICY_MATCH=$POLICY_MATCH + + ADD_IP_ALIASES="$(added_param_value_yes ADD_IP_ALIASES $ADD_IP_ALIASES)" + + if [ -n "${LOGRATE}${LOGBURST}" ]; then + LOGLIMIT="--match limit" + [ -n "$LOGRATE" ] && LOGLIMIT="$LOGLIMIT --limit $LOGRATE" + [ -n "$LOGBURST" ] && LOGLIMIT="$LOGLIMIT --limit-burst $LOGBURST" + fi + + if [ -n "$IP_FORWARDING" ]; then + case "$IP_FORWARDING" in + On|Off|Keep|on|off|keep|ON|OFF|KEEP) + ;; + *) + startup_error "Invalid value ($IP_FORWARDING) for IP_FORWARDING" + ;; + esac + else + IP_FORWARDING=On + fi + + [ -n "${BLACKLIST_DISPOSITION:=DROP}" ] + + case "$CLAMPMSS" in + [0-9]*) + ;; + *) + CLAMPMSS=$(added_param_value_no CLAMPMSS $CLAMPMSS) + ;; + esac + + ADD_SNAT_ALIASES=$(added_param_value_no ADD_SNAT_ALIASES $ADD_SNAT_ALIASES) + ROUTE_FILTER=$(added_param_value_no ROUTE_FILTER $ROUTE_FILTER) + LOG_MARTIANS=$(added_param_value_no LOG_MARTIANS $LOG_MARTIANS) + DETECT_DNAT_IPADDRS=$(added_param_value_no DETECT_DNAT_IPADDRS $DETECT_DNAT_IPADDRS) + + MACLIST_TARGET=reject + + if [ -n "$MACLIST_DISPOSITION" ] ; then + case $MACLIST_DISPOSITION in + REJECT) + ;; + DROP) + MACLIST_TARGET=DROP + ;; + ACCEPT) + MACLIST_TARGET=RETURN + ;; + *) + startup_error "Invalid value ($MACLIST_DISPOSITION) for MACLIST_DISPOSITION" + ;; + esac + else + MACLIST_DISPOSITION=REJECT + fi + + if [ -n "$TCP_FLAGS_DISPOSITION" ] ; then + case $TCP_FLAGS_DISPOSITION in + REJECT|ACCEPT|DROP) + ;; + *) + startup_error "Invalid value ($TCP_FLAGS_DISPOSITION) for TCP_FLAGS_DISPOSITION" + ;; + esac + else + TCP_FLAGS_DISPOSITION=DROP + fi + + [ -n "${RFC1918_LOG_LEVEL:=info}" ] + + MARK_IN_FORWARD_CHAIN=$(added_param_value_no MARK_IN_FORWARD_CHAIN $MARK_IN_FORWARD_CHAIN) + [ -n "$MARK_IN_FORWARD_CHAIN" ] && MARKING_CHAIN=tcfor || MARKING_CHAIN=tcpre + CLEAR_TC=$(added_param_value_yes CLEAR_TC $CLEAR_TC) + + if [ -n "$LOGFORMAT" ]; then + if [ -n "$(echo $LOGFORMAT | grep '%d')" ]; then + LOGRULENUMBERS=Yes + temp=$(printf "$LOGFORMAT" fooxx2barxx 1 ACCEPT 2> /dev/null) + if [ $? -ne 0 ]; then + startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\"" + fi + else + temp=$(printf "$LOGFORMAT" fooxx2barxx ACCEPT 2> /dev/null) + if [ $? -ne 0 ]; then + startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\"" + fi + fi + + [ ${#temp} -le 29 ] || startup_error "LOGFORMAT string is longer than 29 characters: \"$LOGFORMAT\"" + + MAXZONENAMELENGTH=$(( 5 + ( ( 29 - ${#temp}) / 2) )) + MAXZONENAMELENGTH=${MAXZONENAMELENGTH%.*} + else + LOGFORMAT="Shorewall:%s:%s:" + MAXZONENAMELENGTH=5 + fi + + ADMINISABSENTMINDED=$(added_param_value_no ADMINISABSENTMINDED $ADMINISABSENTMINDED) + BLACKLISTNEWONLY=$(added_param_value_no BLACKLISTNEWONLY $BLACKLISTNEWONLY) + DISABLE_IPV6=$(added_param_value_no DISABLE_IPV6 $DISABLE_IPV6) + BRIDGING=$(added_param_value_no BRIDGING $BRIDGING) + + DYNAMIC_ZONES=$(added_param_value_no DYNAMIC_ZONES $DYNAMIC_ZONES) + if [ -n "$DYNAMIC_ZONES" ]; then + [ -n "$EXPORT" ] && startup_error "DYNAMIC_ZONES=Yes is incompatible with the -e option" + lib_avail dynamiczones || error_message "WARNING: DYNAMIC_ZONES=Yes requires the Shorewall dynamiczones library (${SHAREDIR}/lib.dynamiczones) which is not installed" + fi + + STARTUP_ENABLED=$(added_param_value_yes STARTUP_ENABLED $STARTUP_ENABLED) + RETAIN_ALIASES=$(added_param_value_no RETAIN_ALIASES $RETAIN_ALIASES) + [ -n "${ADD_IP_ALIASES}${ADD_SNAT_ALIASES}" ] || RETAIN_ALIASES= + DELAYBLACKLISTLOAD=$(added_param_value_no DELAYBLACKLISTLOAD $DELAYBLACKLISTLOAD) + LOGTAGONLY=$(added_param_value_no LOGTAGONLY $LOGTAGONLY) + RFC1918_STRICT=$(added_param_value_no RFC1918_STRICT $RFC1918_STRICT) + SAVE_IPSETS=$(added_param_value_no SAVE_IPSETS $SAVE_IPSETS) + MAPOLDACTIONS=$(added_param_value_yes MAPOLDACTIONS $MAPOLDACTIONS) + FASTACCEPT=$(added_param_value_no FASTACCEPT $FASTACCEPT) + IMPLICIT_CONTINUE=$(added_param_value_no IMPLICIT_CONTINUE $IMPLICIT_CONTINUE) + HIGH_ROUTE_MARKS=$(added_param_value_no HIGH_ROUTE_MARKS $HIGH_ROUTE_MARKS) + TC_EXPERT=$(added_param_value_no TC_EXPERT $TC_EXPERT) + USE_ACTIONS=$(added_param_value_yes USE_ACTIONS $USE_ACTIONS) + EXPORTPARAMS=$(added_param_value_yes EXPORTPARAMS $EXPORTPARAMS) + [ -n "$USE_ACTIONS" ] && lib_load actions "USE_ACTIONS=Yes" + + [ -n "$XCONNMARK_MATCH" ] || XCONNMARK= + [ -n "$XMARK" ] || XCONNMARK= + + [ -n "$HIGH_ROUTE_MARKS" -a -z "$XCONNMARK" ] && startup_error "HIGH_ROUTE_MARKS=Yes requires extended CONNMARK target, extended CONNMARK match support and extended MARK support" + + case ${MACLIST_TABLE:=filter} in + filter) + ;; + mangle) + [ $MACLIST_DISPOSITION = reject ] && startup_error "MACLIST_DISPOSITION=REJECT is not allowed with MACLIST_TABLE=mangle" + ;; *) + startup_error "Invalid value ($MACLIST_TABLE) for MACLIST_TABLE option" + ;; + esac + + TC_SCRIPT= + + if [ -n "$TC_ENABLED" ] ; then + case "$TC_ENABLED" in + [Yy][Ee][Ss]) + TC_ENABLED=Yes + TC_SCRIPT=$(find_file tcstart) + [ -f $TC_SCRIPT ] || startup_error "Unable to find tcstart file" + ;; + [Ii][Nn][Tt][Ee][Rr][Nn][Aa][Ll]) + TC_ENABLED=Internal + ;; + [Nn][Oo]) + TC_ENABLED= + ;; + esac + else + TC_ENABLED=Yes + fi + + if [ -n "$TC_ENABLED" ];then + [ -n "$MANGLE_ENABLED" ] || startup_error "Traffic Shaping requires mangle support in your kernel and iptables" + fi + + [ "x${SHOREWALL_DIR}" = "x." ] && SHOREWALL_DIR="$PWD" + [ -n "${RESTOREFILE:=restore}" ] + + case "${DROP_DEFAULT:=Drop}" in + None) + DROP_DEFAULT=none + ;; + esac + + case "${REJECT_DEFAULT:=Reject}" in + None) + REJECT_DEFAULT=none + ;; + esac + + case "${QUEUE_DEFAULT:=none}" in + None) + QUEUE_DEFAULT=none + ;; + esac + + case "${ACCEPT_DEFAULT:=none}" in + None) + ACCEPT_DEFAULT=none + ;; + esac + + case "${OPTIMIZE:=0}" in + 0|1) + ;; + *) + startup_error "Invalid OPTIMIZE value ($OPTIMIZE)" + ;; + esac + # + # Check out the user's shell + # + [ -n "${SHOREWALL_SHELL:=/bin/sh}" ] + + temp=$(decodeaddr 192.168.1.1) + if [ $(encodeaddr $temp) != 192.168.1.1 ]; then + startup_error "Shell $SHOREWALL_SHELL is broken and may not be used with Shorewall" + fi + + if [ -z "$KLUDGEFREE" ]; then + rm -f $TMP_DIR/physdev + rm -f $TMP_DIR/iprange + fi + + qt mywhich awk && HAVEAWK=Yes || HAVEAWK= + # + # Pre-process all of the standard files + # + # Because 'strip_file()' does shell variable expansion, we must first determine the + # setting of $FW + # + case ${IPSECFILE:=ipsec} in + ipsec) + [ -n "${FW:=fw}" ] + strip_file ipsec + ;; + zones) + get_firewall_zone + ;; + *) + startup_error "Invalid value ($IPSECFILE) for IPSECFILE option" + ;; + esac + + strip_file zones + strip_file routestopped + strip_file interfaces + strip_file hosts + + if [ $PROGRAM = compiler ]; then + strip_file_and_lib_load accounting accounting + + if [ -n "$USE_ACTIONS" ]; then + strip_file actions + strip_file actions.std ${SHAREDIR}/actions.std + fi + + strip_file blacklist + strip_file ecn + strip_file maclist + strip_file_and_lib_load masq nat + strip_file_and_lib_load nat nat + strip_file_and_lib_load netmap nat + strip_file policy + strip_file_and_lib_load providers providers && strip_file route_rules + strip_file_and_lib_load proxyarp proxyarp + strip_file rfc1918 + strip_file routestopped + strip_file rules + + if [ "$TC_ENABLED" = Internal ]; then + strip_file_and_lib_load tcdevices tc + strip_file_and_lib_load tcclasses tc + fi + + strip_file_and_lib_load tcrules tcrules + strip_file tos + strip_file_and_lib_load tunnels tunnels + report_capabilities1 > $TMP_DIR/capabilities + export $TMP_DIR + fi + # + # Clear $FW + # + FW= + +}