# # Shorewall 5.2 -- /usr/share/shorewall/lib.common # # (c) 2010-2018 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at https://shorewall.org # # This program is part of Shorewall. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation, either version 2 of the license or, at your # option, any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # # The purpose of this library is to hold those functions used by both the CLI and by the # generated firewall scripts. To avoid versioning issues, it is copied into generated # scripts rather than loaded at run-time. # ######################################################################################### # # Wrapper around logger that sets the tag according to $SW_LOGGERTAG # mylogger() { local level level=$1 shift if [ -n "$SW_LOGGERTAG" ]; then logger -p $level -t "$SW_LOGGERTAG" $* else logger -p $level $* fi } # # Issue a message and stop # startup_error() # $* = Error Message { echo " ERROR: $@: Firewall state not changed" >&2 if [ $LOG_VERBOSITY -ge 0 ]; then timestamp="$(date +'%b %e %T') " echo "${timestamp} ERROR: $@" >> $STARTUP_LOG fi case $COMMAND in start) mylogger kern.err "ERROR:$g_product start failed:Firewall state not changed" ;; restart) mylogger kern.err "ERROR:$g_product restart failed:Firewall state not changed" ;; restore) mylogger kern.err "ERROR:$g_product restore failed:Firewall state not changed" ;; esac if [ $LOG_VERBOSITY -ge 0 ]; then timestamp="$(date +'%b %e %T') " case $COMMAND in start) echo "${timestamp} ERROR:$g_product start failed:Firewall state not changed" >> $STARTUP_LOG ;; restart) echo "${timestamp} ERROR:$g_product restart failed:Firewall state not changed" >> $STARTUP_LOG ;; restore) echo "${timestamp} ERROR:$g_product restore failed:Firewall state not changed" >> $STARTUP_LOG ;; esac fi mutex_off kill $$ exit 2 } # # Create the required option string and run the passed script using # $SHOREWALL_SHELL # run_it() { local script local options='-' export VARDIR script=$1 shift if [ "$g_debugging" = debug ]; then options='-D' elif [ "$g_debugging" = trace ]; then options='-T' else options='-'; fi [ -n "$g_noroutes" ] && options=${options}n [ -n "$g_timestamp" ] && options=${options}t [ -n "$g_purge" ] && options=${options}p [ -n "$g_recovering" ] && options=${options}r [ -n "$g_counters" ] && options=${options}c options="${options}V $VERBOSITY" [ -n "$RESTOREFILE" ] && options="${options} -R $RESTOREFILE" $SHOREWALL_SHELL $script $options $@ } # # Message to stderr # error_message() # $* = Error Message { echo " $@" >&2 return 1 } # # 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 $# } # # Split a colon-separated list into a space-separated list # split() { local ifs ifs=$IFS IFS=: echo $* IFS=$ifs } # # Split a comma-separated list into a space-separated list # split_list() { local ifs 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 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 } # # Suppress all output and input - mainly for preventing leaked file descriptors # to avoid SELinux denials # qtnoin() { "$@" /dev/null 2>&1 } qt1() { local status while [ 1 ]; do "$@" /dev/null 2>&1 status=$? [ $status -ne 4 ] && return $status done } # # Determine if Shorewall[6] is "running" # product_is_started() { qt1 $g_tool -L shorewall -n } shorewall_is_started() { qt1 $IPTABLES -L shorewall -n } shorewall6_is_started() { qt1 $IP6TABLES -L shorewall -n } # # Echos the fully-qualified name of the calling shell program # my_pathname() { local pwd pwd=$PWD cd $(dirname $0) echo $PWD/$(basename $0) cd $pwd } # # 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 } # # 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 shift local moduleoptions moduleoptions=$* local modulefile local suffix if [ -d /sys/module/ ]; then if ! list_search $modulename $DONT_LOAD; then if [ ! -d /sys/module/$modulename ]; then case $moduleloader in insmod) for directory in $moduledirectories; do for modulefile in $directory/${modulename}.*; do if [ -f $modulefile ]; then insmod $modulefile $moduleoptions return fi done done ;; *) modprobe -q $modulename $moduleoptions ;; esac fi fi elif ! list_search $modulename $DONT_LOAD $MODULES; then case $moduleloader in insmod) for directory in $moduledirectories; do for modulefile in $directory/${modulename}.*; do if [ -f $modulefile ]; then insmod $modulefile $moduleoptions return fi done done ;; *) modprobe -q $modulename $moduleoptions ;; esac fi } # # Reload the Modules # reload_kernel_modules() { local save_modules_dir save_modules_dir=$MODULESDIR local directory local moduledirectories moduledirectories= local moduleloader moduleloader=modprobe local uname local extras if ! qt mywhich modprobe; then moduleloader=insmod fi if [ -n "$MODULESDIR" ]; then case "$MODULESDIR" in +*) extras="$MODULESDIR" extras=${extras#+} MODULESDIR= ;; esac fi if [ -z "$MODULESDIR" ]; then uname=$(uname -r) MODULESDIR=/lib/modules/$uname/kernel/net/ipv${g_family}/netfilter:/lib/modules/$uname/kernel/net/netfilter:/lib/modules/$uname/kernel/net/sched:/lib/modules/$uname/extra:/lib/modules/$uname/extra/ipset if [ -n "$extras" ]; then for directory in $(split "$extras"); do MODULESDIR="$MODULESDIR:/lib/modules/$uname/$directory" done fi fi [ -d /sys/module/ ] || 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 save_modules_dir=$MODULESDIR local directory local moduledirectories moduledirectories= local moduleloader moduleloader=modprobe local savemoduleinfo savemoduleinfo=${1:-Yes} # So old compiled scripts still work local uname local extras if ! qt mywhich modprobe; then moduleloader=insmod fi if [ -n "$MODULESDIR" ]; then case "$MODULESDIR" in +*) extras="$MODULESDIR" extras=${extras#+} MODULESDIR= ;; esac fi if [ -z "$MODULESDIR" ]; then uname=$(uname -r) MODULESDIR=/lib/modules/$uname/kernel/net/ipv${g_family}/netfilter:/lib/modules/$uname/kernel/net/netfilter:/lib/modules/$uname/kernel/net/sched:/lib/modules/$uname/extra:/lib/modules/$uname/extra/ipset if [ -n "$extras" ]; then for directory in $(split "$extras"); do MODULESDIR="$MODULESDIR:/lib/modules/$uname/$directory" done fi fi for directory in $(split $MODULESDIR); do [ -d $directory ] && moduledirectories="$moduledirectories $directory" done modules=$(find_file helpers) if [ -f $modules -a -n "$moduledirectories" ]; then [ -d /sys/module/ ] || 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 } # # Note: The following set of IP address manipulation functions have anomalous # behavior when the shell only supports 32-bit signed arithmetic and # the IP address is 128.0.0.0 or 128.0.0.1. # LEFTSHIFT='<<' # # Convert an IP address in dot quad format to an integer # decodeaddr() { local x local temp temp=0 local ifs 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 y=$(($addr & 255)) for x in 1 2 3 ; do addr=$(($addr >> 8)) y=$(($addr & 255)).$y done echo $y } # # Netmask from CIDR # ip_netmask() { local vlsm vlsm=${1#*/} [ $vlsm -eq 0 ] && echo 0 || echo $(( -1 $LEFTSHIFT $(( 32 - $vlsm )) )) } # # Network address from CIDR # ip_network() { local decodedaddr decodedaddr=$(decodeaddr ${1%/*}) local netmask 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 do not support XOR ("^"). # ip_broadcast() { local x x=$(( 32 - ${1#*/} )) [ $x -eq 32 ] && echo -1 || echo $(( $(( 1 $LEFTSHIFT $x )) - 1 )) } # # Calculate broadcast address from CIDR # broadcastaddress() { local decodedaddr decodedaddr=$(decodeaddr ${1%/*}) local netmask netmask=$(ip_netmask $1) local broadcast broadcast=$(ip_broadcast $1) echo $(encodeaddr $(( $(($decodedaddr & $netmask)) | $broadcast ))) } # # Test for network membership # in_network() # $1 = IP address, $2 = CIDR network { local netmask netmask=$(ip_netmask $2) # # Use string comparison to work around a broken BusyBox ash in OpenWRT # test $(( $(decodeaddr $1) & $netmask)) = $(( $(decodeaddr ${2%/*}) & $netmask )) } # # Query NetFilter about the existence of a filter chain # chain_exists() # $1 = chain name, $2 = table name (optional) { qt1 $g_tool -t ${2:-filter} -L $1 -n } # # 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 } # # Find interface address--returns the first IP address assigned to the passed # device # find_first_interface_address() # $1 = interface { if [ $g_family -eq 4 ]; then # # get the line of output containing the first IP address # addr=$(${IP:-ip} -f inet addr show $1 2> /dev/null | grep 'inet .* global' | head -n1) # # If there wasn't one, bail out now # [ -n "$addr" ] || startup_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.*//' else # # get the line of output containing the first IP address # addr=$(${IP:-ip} -f inet6 addr show dev $1 2> /dev/null | grep -F 'inet6 ' | grep -vF 'scope link' | 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.*//' fi } find_first_interface_address_if_any() # $1 = interface { if [ $g_family -eq 4 ]; then # # get the line of output containing the first IP address # addr=$(${IP:-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 else # # get the line of output containing the first IP address # addr=$(${IP:-ip} -f inet6 addr show dev $1 2> /dev/null | grep -F 'inet6 ' | grep -vF 'scope link' | 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 :: fi } # #Determines if the passed interface is a loopback interface # loopback_interface() { #$1 = Interface name [ "$1" = lo ] || $IP link show $1 | fgrep -q LOOPBACK } # # Find Loopback Interfaces # find_loopback_interfaces() { local interfaces [ -x "$IP" ] && interfaces=$($IP link show | fgrep LOOPBACK | sed 's/://g' | cut -d ' ' -f 2) [ -n "$interfaces" ] && echo $interfaces || echo lo } # # 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 if [ -n "$g_shorewalldir" ]; then echo ${g_shorewalldir}/$1 else echo ${g_confdir}/$1 fi ;; esac } # # Set the Shorewall state # set_state () # $1 = state { if [ $# -gt 1 ]; then echo "$1 $(date) from $2" > ${VARDIR}/state else echo "$1 $(date)" > ${VARDIR}/state fi } # # 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 $@)) } # 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} } # # Call this function to assert mutual exclusion with Shorewall. If you invoke the # /sbin/shorewall program while holding mutual exclusion, you should pass -N as # the first argument. Example "shorewall -N 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 try=0 local lockf lockf=${LOCKFILE:=${VARDIR}/lock} local lockpid local lockd local lockbin local openwrt MUTEX_TIMEOUT=${MUTEX_TIMEOUT:-60} if [ -z "$g_havemutex" -a $MUTEX_TIMEOUT -gt 0 ]; then lockd=$(dirname $LOCKFILE) [ -d "$lockd" ] || mkdir -p "$lockd" lockbin=$(mywhich lock) [ -n "$lockbin" -a -h "$lockbin" ] && openwrt=Yes if [ -f $lockf ]; then lockpid=`cat ${lockf} 2> /dev/null` if [ -z "$lockpid" ] || [ $lockpid = 0 ]; then rm -f ${lockf} error_message "WARNING: Stale lockfile ${lockf} removed" elif [ -z "$openwrt" ]; then if [ $lockpid -eq $$ ]; then fatal_error "Mutex_on confusion" elif ! qt ps --pid ${lockpid}; then rm -f ${lockf} error_message "WARNING: Stale lockfile ${lockf} from pid ${lockpid} removed" fi fi fi if [ -n "$openwrt" ]; then lock ${lockf} || fatal_error "Can't lock ${lockf}" g_havemutex="lock -u ${lockf}" elif qt mywhich lockfile; then lockfile -${MUTEX_TIMEOUT} -r1 ${lockf} || fatal_error "Can't lock ${lockf}" g_havemutex="rm -f ${lockf}" chmod u+w ${lockf} echo $$ > ${lockf} chmod u-w ${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} g_havemutex="rm -f ${lockf}" else echo "Giving up on lock file ${lockf}" >&2 fi fi if [ -n "$g_havemutex" ]; then trap mutex_off EXIT fi fi } # # Call this function to release mutual exclusion # mutex_off() { if [ -n "$g_havemutex" ]; then eval $g_havemutex g_havemutex= trap '' exit fi }