# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 1999-2009 - Tom Eastep (teastep@shorewall.net) # # Options are: # # -n Don't alter Routing # -v and -q Standard Shorewall Verbosity control # # Commands are: # # start Starts the firewall # refresh Refresh the firewall # restart Restarts the firewall # reload Reload the firewall # clear Removes all firewall rules # stop Stops the firewall # status Displays firewall status # version Displays the version of Shorewall that # generated this program # ################################################################################ # Functions imported from /usr/share/shorewall/prog.header6 ################################################################################ # # Message to stderr # error_message() # $* = Error Message { echo " $@" >&2 } # # Conditionally produce message # progress_message() # $* = Message { local timestamp timestamp= if [ $VERBOSE -gt 1 ]; then [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSE -gt 1 ]; then timestamp="$(date +'%b %_d %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } progress_message2() # $* = Message { local timestamp timestamp= if [ $VERBOSE -gt 0 ]; then [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSE -gt 0 ]; then timestamp="$(date +'%b %_d %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } progress_message3() # $* = Message { local timestamp timestamp= if [ $VERBOSE -ge 0 ]; then [ -n "$TIMESTAMP" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSE -ge 0 ]; then timestamp="$(date +'%b %_d %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } # # Split a colon-separated list into a space-separated list # split() { local ifs ifs=$IFS IFS=: echo $* IFS=$ifs } # # Undo the effect of 'split()' # join() { local f local o o= for f in $* ; do o="${o:+$o:}$f" done echo $o } # # Return the number of elements in a list # list_count() # $* = list { return $# } # # 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 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 } qt1() { local status while [ 1 ]; do "$@" >/dev/null 2>&1 status=$? [ $status -ne 4 ] && return $status done } # # Determine if Shorewall is "running" # shorewall6_is_started() { qt1 $IP6TABLES -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 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 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 modulename=$1 local modulefile local suffix if ! list_search $modulename $DONT_LOAD $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 save_modules_dir=$MODULESDIR local directory local moduledirectories moduledirectories= local moduleloader moduleloader=modprobe if ! qt mywhich modprobe; then moduleloader=insmod fi [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ] [ -z "$MODULESDIR" ] && MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv6/netfilter:/lib/modules/$(uname -r)/kernel/net/netfilter:/lib/modules/$(uname -r)/kernel/net/sched/ 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 Shorewall6 # load_kernel_modules() # $1 = Yes, if we are to save moduleinfo in $VARDIR { local save_modules_dir save_modules_dir=$MODULESDIR local directory local moduledirectories moduledirectories= local moduleloader moduleloader=modprobe local savemoduleinfo savemoduleinfo=${1:-Yes} # So old compiled scripts still work if ! qt mywhich modprobe; then moduleloader=insmod fi [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ] [ -z "$MODULESDIR" ] && \ MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv6/netfilter:/lib/modules/$(uname -r)/kernel/net/netfilter:/lib/modules/$(uname -r)/kernel/net/sched/ for directory in $(split $MODULESDIR); do [ -d $directory ] && moduledirectories="$moduledirectories $directory" done [ -n "$LOAD_HELPERS_ONLY" ] && modules=$(find_file helpers) || 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 } # # Query NetFilter about the existence of a filter chain # chain_exists() # $1 = chain name { qt1 $IP6TABLES -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 } # # Try to find the gateway through an interface looking for 'nexthop' find_nexthop() # $1 = interface { echo $(find_gateway `$IP -6 route list | grep "[[:space:]]nexthop.* $1"`) } # # Find the default route's interface # find_default_interface() { $IP -6 route list | while read first rest; do [ "$first" = default ] && echo $(find_device $rest) && return done } # # Find the interface with the passed MAC address # find_interface_by_mac() { local mac mac=$1 local first local second local rest local dev $IP link list | 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 list dev $1 2> /dev/null | 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 inet6 addr show $1 2> /dev/null | grep 'inet6 .* global' | head -n1) # # If there wasn't one, bail out now # [ -n "$addr" ] || startup_error "Can't determine the IPv6 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*inet6 //;s/\/.*//;s/ peer.*//' } find_first_interface_address_if_any() # $1 = interface { # # get the line of output containing the first IP address # addr=$($IP -f inet6 addr show $1 2> /dev/null | grep 'inet6 2.* 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*inet6 //;s/\/.*//;s/ peer.*//' || echo :: } # # Determine if interface is usable from a Netfilter prespective # interface_is_usable() # $1 = interface { [ "$1" = lo ] && return 0 interface_is_up $1 && [ "$(find_first_interface_address_if_any $1)" != :: ] && run_isusable_exit $1 } # # Find interface addresses--returns the set of addresses assigned to the passed # device # find_interface_addresses() # $1 = interface { $IP -f inet6 addr show $1 2> /dev/null | grep 'inet6 2' | sed 's/\s*inet6 //;s/\/.*//;s/ peer.*//' } # # Get all interface addresses with VLSMs # find_interface_full_addresses() # $1 = interface { $IP -f inet6 addr show $1 2> /dev/null | grep 'inet6 ' | sed 's/\s*inet6 //;s/ scope.*//;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 -6 route show dev $1 2> /dev/null | while read address rest; do case "$address" in default) if [ $# -gt 1 ]; then shift fatal_error "$@" else echo "WARNING: default route ignored on interface $1" >&2 fi ;; multicast|broadcast|prohibit|nat|throw|nexthop) ;; 2*) [ "$address" = "${address%/*}" ] && address="${address}/128" echo $address ;; esac done } # # Normalize an IPv6 Address by compressing out consecutive zero elements # normalize_address() # $1 = valid IPv6 Address { local address address=$1 local j while true; do case $address in ::*) address=0$address ;; *::*) list_count $(split $address) j=$? if [ $j -eq 7 ]; then address=${address%::*}:0:${address#*::} elif [ $j -eq 8 ]; then $address=${address%::*}:${address#*::} break 2 else address=${address%::*}:0::${address#*::} fi ;; *) echo $address break 2 ;; esac done } # # Reads correctly-formed and fully-qualified host and subnet addresses from STDIN. For each # that defines a /120 or larger network, it sends to STDOUT: # # The corresponding subnet-router anycast address (all host address bits are zero) # The corresponding anycast addresses defined by RFC 2526 (the last 128 addresses in the subnet) # convert_to_anycast() { local address local badress local vlsm local host local o local m m= local z z=65535 local l while read address; do case $address in 2*|3*) vlsm=${address#*/} vlsm=${vlsm:=128} if [ $vlsm -le 120 ]; then # # Defines a viable subnet -- first get the subnet-router anycast address # host=$((128 - $vlsm)) address=$(normalize_address ${address%/*}) while [ $host -ge 16 ]; do address=${address%:*} host=$(($host - 16)) done if [ $host -gt 0 ]; then # # VLSM is not a multiple of 16 # host=$((16 - $host)) o=$((0x${address##*:})) m=0 while [ $host -gt 0 ]; do m=$((($m >> 1) | 0x8000)) z=$(($z >> 1)) host=$(($host - 1)) done o=$(($o & $m)) badress=${address%:*} address=$badress:$(printf %04x $o) z=$(($o | $z)) if [ $vlsm -gt 112 ]; then z=$(($z & 0xff80)) fi badress=$badress:$(printf %04x $z) else badress=$address fi # # Note: at this point $address and $badress are the same except possibly for # the contents of the last half-word # list_count $(split $address) l=$? # # Now generate the anycast addresses defined by RFC 2526 # if [ $l -lt 8 ]; then # # The subnet-router address # echo $address:: while [ $l -lt 8 ]; do badress=$badress:ffff l=$(($l + 1 )) done else # # The subnet-router address # echo $address fi # # And the RFC 2526 addresses # echo $badress/121 fi ;; esac done } # # Generate a list of anycast addresses for a given interface # get_interface_acasts() # $1 = interface { local addresses addresses= find_interface_full_addresses $1 | convert_to_anycast | sort -u } # # Get a list of all configured anycast addresses on the system # get_all_acasts() { find_interface_full_addresses | convert_to_anycast | sort -u } # # 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 } # # Find a File -- For relative file name, look in each ${CONFIG_PATH} then ${CONFDIR} # find_file() { local saveifs saveifs= local 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 } # # Set the Shorewall state # set_state () # $1 = state { echo "$1 ($(date))" > ${VARDIR}/state } # # 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 $@)) } # # Detect the gateway through an interface # detect_gateway() # $1 = interface { local interface interface=$1 # # First assume that this is some sort of point-to-point interface # gateway=$( find_peer $($IP -6 addr list $interface ) ) # # Maybe there's a default route through this gateway already # [ -n "$gateway" ] || gateway=$(find_gateway $($IP -6 route list dev $interface | grep '^default')) # # 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 } # 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} } # # Clear the current traffic shaping configuration # delete_tc1() { clear_one_tc() { $TC qdisc del dev $1 root 2> /dev/null $TC qdisc del dev $1 ingress 2> /dev/null } run_tcclear_exit 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 -- echos the passed device's MTU # get_device_mtu() # $1 = device { local output output="$($IP link list dev $1 2> /dev/null)" # quotes required for /bin/ash if [ -n "$output" ]; then echo $(find_mtu $output) else echo 1500 fi } # # Version of the above that doesn't generate any output for MTU 1500. # Generates 'mtu ' otherwise, where is the device's MTU + 100 # get_device_mtu1() # $1 = device { local output output="$($IP link list dev $1 2> /dev/null)" # quotes required for /bin/ash local mtu if [ -n "$output" ]; then mtu=$(find_mtu $output) if [ -n "$mtu" ]; then [ $mtu = 1500 ] || echo mtu $(($mtu + 100)) fi fi } # # Undo changes to routing # undo_routing() { if [ -z "$NOROUTES" ]; then # # Restore rt_tables database # if [ -f ${VARDIR}/rt_tables ]; then [ -w /etc/iproute2/rt_table -a -z "$KEEP_RT_TABLES" ] && 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 the default route that was in place before the initial 'shorewall start' # restore_default_route() { if [ -z "$NOROUTES" -a -f ${VARDIR}/default_route ]; then local default_route default_route= local route local result result=1 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 -6 route delete default metric 0 && \ progress_message "Default Route with metric 0 deleted" ;; *) qt $IP -6 route replace $default_route && \ result=0 && \ 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 return $result } # # 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 } # # Flush the conntrack table if $PURGE is non-empty # conditionally_flush_conntrack() { if [ -n "$PURGE" ]; then if [ -n $(which conntrack) ]; then conntrack -F else error_message "WARNING: The '-p' option requires the conntrack utility which does not appear to be installed on this system" fi fi } # # Remove all Shorewall-added rules # clear_firewall() { stop_firewall setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT run_iptables -F echo 1 > /proc/sys/net/ipv6/conf/all/forwarding run_clear_exit set_state "Cleared" logger -p kern.info "$PRODUCT Cleared" } # # Issue a message and stop/restore the firewall # fatal_error() { echo " ERROR: $@" >&2 if [ $LOG_VERBOSE -gt 1 ]; then timestamp="$(date +'%_b %d %T') " echo "${timestamp} ERROR: $@" >> $STARTUP_LOG fi stop_firewall [ -n "$TEMPFILE" ] && rm -f $TEMPFILE exit 2 } # # Issue a message and stop # startup_error() # $* = Error Message { echo " ERROR: $@: Firewall state not changed" >&2 case $COMMAND in start) logger -p kern.err "ERROR:$PRODUCT start failed:Firewall state not changed" ;; restart) logger -p kern.err "ERROR:$PRODUCT restart failed:Firewall state not changed" ;; restore) logger -p kern.err "ERROR:$PRODUCT restore failed:Firewall state not changed" ;; esac if [ $LOG_VERBOSE -gt 1 ]; then timestamp="$(date +'%_b %d %T') " case $COMMAND in start) echo "${timestamp} ERROR:$PRODUCT start failed:Firewall state not changed" >> $STARTUP_LOG ;; restart) echo "${timestamp} ERROR:$PRODUCT restart failed:Firewall state not changed" >> $STARTUP_LOG ;; restore) echo "${timestamp} ERROR:$PRODUCT restore failed:Firewall state not changed" >> $STARTUP_LOG ;; esac fi kill $$ exit 2 } # # Run iptables and if an error occurs, stop/restore the firewall # run_iptables() { local status while [ 1 ]; do $IP6TABLES $@ status=$? [ $status -ne 4 ] && break done if [ $status -ne 0 ]; then error_message "ERROR: Command \"$IP6TABLES $@\" Failed" stop_firewall exit 2 fi } # # Run iptables retrying exit status 4 # do_iptables() { local status while [ 1 ]; do $IP6TABLES $@ status=$? [ $status -ne 4 ] && return $status; done } # # Run iptables and if an error occurs, stop/restore the firewall # run_ip() { if ! $IP -6 $@; then error_message "ERROR: Command \"$IP -6 $@\" Failed" stop_firewall exit 2 fi } # # Run tc and if an error occurs, stop/restore the firewall # run_tc() { if ! $TC $@ ; then error_message "ERROR: Command \"$TC $@\" Failed" stop_firewall exit 2 fi } # # Restore the rules generated by 'drop','reject','logdrop', etc. # restore_dynamic_rules() { if [ -f ${VARDIR}/save ]; then progress_message2 "Setting up dynamic rules..." rangematch='source IP range' while read target ignore1 ignore2 address ignore3 rest; do case $target in DROP|reject|logdrop|logreject) case $rest in $rangematch*) run_iptables -A dynamic -m iprange --src-range ${rest#source IP range} -j $target ;; *) if [ -z "$rest" ]; then run_iptables -A dynamic -s $address -j $target else error_message "WARNING: Unable to restore dynamic rule \"$target $ignore1 $ignore2 $address $ignore3 $rest\"" fi ;; esac ;; esac done < ${VARDIR}/save fi } # # Run the .iptables_restore_input as a set of discrete iptables commands # debug_restore_input() { local first second rest table chain # # Clear the ruleset # qt1 $IP6TABLES -t mangle -F qt1 $IP6TABLES -t mangle -X for chain in PREROUTING INPUT FORWARD POSTROUTING; do qt1 $IP6TABLES -t mangle -P $chain ACCEPT done qt1 $IP6TABLES -t raw -F qt1 $IP6TABLES -t raw -X for chain in PREROUTING OUTPUT; do qt1 $IP6TABLES -t raw -P $chain ACCEPT done qt1 $IP6TABLES -t filter -F qt1 $IP6TABLES -t filter -X for chain in INPUT FORWARD OUTPUT; do qt1 $IP6TABLES -t filter -P $chain -P ACCEPT done while read first second rest; do case $first in -*) # # We can't call run_iptables() here because the rules may contain quoted strings # eval $IP6TABLES -t $table $first $second $rest if [ $? -ne 0 ]; then error_message "ERROR: Command \"$IP6TABLES $first $second $rest\" Failed" stop_firewall exit 2 fi ;; :*) chain=${first#:} if [ "x$second" = x- ]; then do_iptables -t $table -N $chain else do_iptables -t $table -P $chain $second fi if [ $? -ne 0 ]; then error_message "ERROR: Command \"$IP6TABLES $first $second $rest\" Failed" stop_firewall exit 2 fi ;; # # This grotesque hack with the table names works around a bug/feature with ash # '*'raw) table=raw ;; '*'mangle) table=mangle ;; '*'nat) table=nat ;; '*'filter) table=filter ;; esac done } ################################################################################ # End of functions imported from /usr/share/shorewall/prog.header6 ################################################################################