shorewall-5.0.4/0000755000000000000000000000000012650243747012230 5ustar rootrootshorewall-5.0.4/shorewallrc.cygwin0000644000000000000000000000226012650243747015777 0ustar rootroot# # Cygwin Shorewall 5.0 rc file # BUILD=cygwin HOST=cygwin PREFIX=/usr #Top-level directory for shared files, libraries, etc. SHAREDIR=${PREFIX}/share #Directory for arch-neutral files. LIBEXECDIR=${PREFIX}/share #Directory for executable scripts. PERLLIBDIR=${PREFIX}/share/shorewall #Directory to install Shorewall Perl module directory CONFDIR=/etc #Directory where subsystem configurations are installed SBINDIR=/bin #Directory where system administration programs are installed MANDIR=${SHAREDIR}/man #Directory where manpages are installed. INITDIR=/etc/init.d #Unused on Cygwin INITFILE= #Unused on Cygwin INITSOURCE= #Unused on Cygwin ANNOTATED= #Unused on Cygwin SERVICEDIR= #Unused on Cygwin SERVICEFILE= #Unused on Cygwin SYSCONFDIR= #Unused on Cygwin SPARSE=Yes #Only install $PRODUCT/$PRODUCT.conf in $CONFDIR. VARLIB=/var/lib #Unused on Cygwin shorewall-5.0.4/action.RST0000644000000000000000000000274312647470621014104 0ustar rootroot# # Shorewall 4 - RST Action # # /usr/share/shorewall/action.RST # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2012 - 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # RST[([])] # # Default action is DROP # ########################################################################################## DEFAULTS DROP,- ?begin perl; use Shorewall::Config; use Shorewall::Chains; use Shorewall::Rules; my ( $action, $audit ) = get_action_params( 2 ); if ( supplied $audit ) { fatal_error "Invalid parameter ($audit) to action RST" if $audit ne 'audit'; $action = "A_$action"; } perl_action_tcp_helper( $action, '-p 6 --tcp-flags RST RST' ); 1; ?end perl; shorewall-5.0.4/action.SetEvent0000644000000000000000000000366512647470621015175 0ustar rootroot# # Shorewall version 5 - Set an Event # # /etc/shorewall/action.SetEvent # # Parameters: # Event: Must start with a letter and be composed of letters, digits, '-', and '_'. # Action: Action to perform after setting the event. Default is ACCEPT # Src or Dest: 'src' (default) or 'dst'. Determines if the event is associated with the source # address (src) or destination address (dst) # Disposition: Disposition for any event generated. # # For additional information, see http://www.shorewall.net/Events.html # ####################################################################################################### # DO NOT REMOVE THE FOLLOWING LINE ################################################################################################################################################################################################# #ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/ MARK CONNLIMIT TIME HEADERS SWITCH HELPER # PORT PORT(S) DEST LIMIT GROUP DEFAULTS -,ACCEPT,src ?begin perl use Shorewall::Config; use Shorewall::Chains; use Shorewall::Rules; use strict; my ( $event, $action, $destination, $disposition ) = get_action_params( 4 ); require_capability 'RECENT_MATCH', 'Use of events', 's'; require_capability 'MARK_ANYWHERE', 'Use of events', 's'; fatal_error "An event name is required" unless supplied $event; fatal_error "Invalid event name ($event)" unless $event =~ /^[a-zA-z][-\w]*$/; fatal_error "Invalid Src or Dest ($destination)" unless $destination =~ /^(?:src|dst)$/; set_action_disposition( $disposition) if supplied $disposition; set_action_name_to_caller; if ( $destination eq 'dst' ) { perl_action_helper( $action, '', '', "-m recent --name $event --set --rdest" ); } else { perl_action_helper( $action, '', '', "-m recent --name $event --set --rsource" ); } 1; ?end perl shorewall-5.0.4/INSTALL0000644000000000000000000000171112647470621013260 0ustar rootrootShoreline Firewall (Shorewall) Version 5 ----- ---- ----------------------------------------------------------------------------- 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --------------------------------------------------------------------------- Please see http://www.shorewall.net/Install.htm for installation instructions. shorewall-5.0.4/Perl/0000755000000000000000000000000012647470621013131 5ustar rootrootshorewall-5.0.4/Perl/lib.core0000644000000000000000000007322712647470621014564 0ustar rootroot# (c) 1999-2015 - Tom Eastep (teastep@shorewall.net) # # 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 . # # Options are: # # -n Do not alter Routing # -v and -q Standard Shorewall Verbosity control # -t Timestamp progress messages # -p Purge conntrack table # -r Recover from failed start/reload # -V Set verbosity level explicitly # -R Overrides RESTOREFILE setting # # Commands are: # # clear Removes all firewall rules # disable Disable an optional interface # down Stop an optional interface # enable Enable an optional interface # help Show command syntax # reenable Disable then nable an optional # interface # refresh Refresh the firewall # reload Reload the firewall # restart Restarts the firewall # restore Restore a saved configuration # reset Reset byte and packet counters # run Call a function in this program # savesets Save the ipset contents # status Displays firewall status # start Starts the firewall # stop Stops the firewall # up Start an optional interface # version Displays the version of Shorewall that # generated this program # ################################################################################ # Functions imported from /usr/share/shorewall/lib.core ################################################################################ # Address family-neutral Functions ################################################################################ # # Conditionally produce message # progress_message() # $* = Message { local timestamp timestamp= if [ $VERBOSITY -gt 1 ]; then [ -n "$g_timestamp" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSITY -gt 1 ]; then timestamp="$(date +'%b %e %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } progress_message2() # $* = Message { local timestamp timestamp= if [ $VERBOSITY -gt 0 ]; then [ -n "$g_timestamp" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSITY -gt 0 ]; then timestamp="$(date +'%b %e %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } progress_message3() # $* = Message { local timestamp timestamp= if [ $VERBOSITY -ge 0 ]; then [ -n "$g_timestamp" ] && timestamp="$(date +%H:%M:%S) " echo "${timestamp}$@" fi if [ $LOG_VERBOSITY -ge 0 ]; then timestamp="$(date +'%b %e %T') " echo "${timestamp}$@" >> $STARTUP_LOG fi } # # Set a standard chain's policy # setpolicy() # $1 = name of chain, $2 = policy { run_iptables -P $1 $2 } # # Generate a list of all network interfaces on the system # find_all_interfaces() { ${IP:-ip} link list | egrep '^[[:digit:]]+:' | cut -d ' ' -f2 | sed -r 's/(@.*)?:$//' } # # Generate a list of all network interfaces on the system that have an ipvX address # find_all_interfaces1() { ${IP:-ip} -$g_family addr list | egrep '^[[:digit:]]+:' | cut -d ' ' -f2 | sed -r 's/(@.*)?:$//' } # # 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 -$g_family route list | grep "[[:space:]]nexthop.* $1"`) } # # Find the default route's interface # find_default_interface() { $IP -$g_family route list | while read first rest; do [ "$first" = default ] && echo $(find_device $rest) && return done } # # Determine if Interface is up # interface_is_up() { [ -n "$($IP -$g_family link list dev $1 2> /dev/null | grep -e '[<,]UP[,>]')" ] } # # 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 local mask [ $g_family -eq 4 ] && mask=32 || mask=128 $IP -$g_family 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-3]*) [ "$address" = "${address%/*}" ] && address="${address}/${mask}" echo $address ;; *) if [ $g_family -eq 4 ]; then [ "$address" = "${address%/*}" ] && address="${address}/${mask}" echo $address fi ;; esac done } # # 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() { local undofiles local f if [ -z "$g_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 # undofiles="$(ls ${VARDIR}/undo_*routing 2> /dev/null)" if [ -n "$undofiles" ]; then for f in $undofiles; do . $f done rm -f $undofiles progress_message "Shorewall-generated routing tables and routing rules removed" fi fi } # # Save the default route # save_default_route() { awk \ 'BEGIN {defroute=0;}; /^default / {defroute=1; print; next}; /nexthop/ {if (defroute == 1 ) {print ; next} }; { defroute=0; };' } # # Restore the default route that was in place before the initial 'shorewall start' # replace_default_route() # $1 = USE_DEFAULT_RT { # # default_route and result are inherited from the caller # if [ -n "$default_route" ]; then case "$default_route" in *metric*) # # Don't restore a default route with a metric unless USE_DEFAULT_RT=Yes. Otherwise, we only replace the one with metric 0 # [ -n "$1" ] && qt $IP -$g_family route replace $default_route && progress_message "Default Route (${default_route# }) restored" default_route= ;; *) qt $IP -$g_family route replace $default_route && progress_message "Default Route (${default_route# }) restored" result=0 default_route= ;; esac fi } # # Delete default routes with metric 0 from the passed routing table # delete_default_routes() # $1 = table number { $IP -$g_family route ls table $1 | grep -F default | grep -vF metric | while read route; do qt $IP -$g_family route del $route done } restore_default_route() # $1 = USE_DEFAULT_RT { local result result=1 if [ -z "$g_noroutes" -a -f ${VARDIR}/default_route ]; then local default_route default_route= local route while read route ; do case $route in default*) replace_default_route $1 default_route="$default_route $route" ;; *) default_route="$default_route $route" ;; esac done < ${VARDIR}/default_route replace_default_route $1 if [ $result = 1 ]; then # # We didn't restore a default route with metric 0 # if $IP -$g_family -o route list 2> /dev/null | grep -F default | grep -qvF metric; then # # But we added a default route with metric 0 # qt $IP -$g_family route del default metric 0 && progress_message "Default route with metric 0 deleted" fi fi rm -f ${VARDIR}/default_route fi return $result } # # Flush the conntrack table if $g_purge is non-empty # conditionally_flush_conntrack() { if [ -n "$g_purge" ]; then if [ -n $(mywhich 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 } # # Issue a message and stop/restore the firewall. # fatal_error() { echo " ERROR: $@" >&2 if [ $LOG_VERBOSITY -ge 0 ]; then timestamp="$(date +'%b %e %T') " echo "${timestamp} ERROR: $@" >> $STARTUP_LOG fi stop_firewall [ -n "$TEMPFILE" ] && rm -f $TEMPFILE mutex_off exit 2 } # # Run iptables/ip6tables and if an error occurs, stop/restore the firewall # run_iptables() { local status while [ 1 ]; do eval $g_tool $@ status=$? [ $status -ne 4 ] && break done if [ $status -ne 0 ]; then error_message "ERROR: Command \"$g_tool $@\" Failed" stop_firewall exit 2 fi } # # Run iptables/ip6tables retrying exit status 4 # do_iptables() { local status while [ 1 ]; do $g_tool $@ status=$? [ $status -ne 4 ] && return $status; done } # # Run ip and if an error occurs, stop/restore the firewall # run_ip() { if ! $IP -$g_family $@; then error_message "ERROR: Command \"$IP -$g_family $@\" 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 } # # 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 $g_tool -t mangle -F qt1 $g_tool -t mangle -X for chain in PREROUTING INPUT FORWARD POSTROUTING; do qt1 $g_tool -t mangle -P $chain ACCEPT done qt1 $g_tool -t raw -F qt1 $g_tool -t raw -X for chain in PREROUTING OUTPUT; do qt1 $g_tool -t raw -P $chain ACCEPT done qt1 $g_tool -t rawpost -F qt1 $g_tool -t rawpost -X for chain in POSTROUTING; do qt1 $g_tool -t rawpost -P $chain ACCEPT done qt1 $g_tool -t nat -F qt1 $g_tool -t nat -X for chain in PREROUTING POSTROUTING; do qt1 $g_tool -t nat -P $chain ACCEPT done qt1 $g_tool -t filter -F qt1 $g_tool -t filter -X for chain in INPUT FORWARD OUTPUT; do qt1 $g_tool -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 $g_tool -t $table $first $second $rest if [ $? -ne 0 ]; then error_message "ERROR: Command \"$g_tool -t $table $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 \"$g_tool $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 ;; '*'rawpost) table=rawpost ;; '*'mangle) table=mangle ;; '*'nat) table=nat ;; '*'filter) table=filter ;; esac done } interface_enabled() { return $(cat ${VARDIR}/$1.status) } distribute_load() { local interface local currentload # Total load of enabled interfaces local load # Specified load of an enabled interface local mark # Mark of an enabled interface local totalload # Total load of all interfaces - usually 1.000000 local nload # Normalized load of an enabled interface local var # Interface name to embed in a variable name totalload=$1 shift currentload=0 for interface in $@; do if interface_enabled $interface; then var=$(echo $interface | sed 's/[.-]/_/g') load=$(cat ${VARDIR}/${interface}_load) eval ${var}_load=$load mark=$(cat ${VARDIR}/${interface}_mark) eval ${var}_mark=$mark currentload=$( bc < /dev/null | grep inet\ | sed 's/\s*inet //;s/\/.*//;s/ peer.*//' } # # Find the value 'weight' in the passed arguments then echo the next value # find_weight() { while [ $# -gt 1 ]; do [ "x$1" = xweight ] && 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 -4 route list | 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 } # # Echo the name of the interface(s) that will be used to send to the # passed address # find_interface_by_address() { local dev dev="$(find_rt_interface $1)" local first local rest [ -z "$dev" ] && dev=$(find_default_interface) [ -n "$dev" ] && echo $dev } # # 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 -4 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) ;; *) [ "$address" = "${address%/*}" ] && address="${address}/32" echo $address ;; esac done } # # Get the broadcast addresses associated with an interface # get_interface_bcasts() # $1 = interface { local addresses addresses= $IP -f inet addr show dev $1 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u } # # Delete IP address # del_ip_addr() # $1 = address, $2 = interface { [ $(find_first_interface_address_if_any $2) = $1 ] || qtnoin $IP addr del $1 dev $2 } # Add IP Aliases # add_ip_aliases() # $* = List of addresses { local local local addresses local external local interface local inet local cidr local rest local val local arping 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 network # # 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" >> $VARDIR/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 the gateway through a PPP or DHCP-configured interface # detect_dynamic_gateway() { # $1 = interface local interface interface=$1 local GATEWAYS GATEWAYS= local gateway local file gateway=$(run_findgw_exit $1); if [ -z "$gateway" ]; then gateway=$( find_peer $($IP addr list $interface ) ) fi file="${VARLIB}/dhcpcd/dhcpcd-${1}.info" if [ -z "$gateway" -a -f "${file}" ]; then eval $(grep ^GATEWAYS= "${file}" 2> /dev/null) [ -n "$GATEWAYS" ] && GATEWAYS=${GATEWAYS%,*} && gateway=$GATEWAYS fi for file in \ "${VARLIB}/dhcp/dhclient-${1}.lease" \ "${VARLIB}/dhcp/dhclient.${1}.leases" do [ -n "$gateway" ] && break if [ -f "${file}" ]; then gateway=$(grep 'option routers' "${file}" | tail -n 1 | while read j1 j2 gateway; do echo "${gateway%\;}" ; return 0; done) fi done [ -n "$gateway" ] && echo $gateway } # # Detect the gateway through an interface # detect_gateway() # $1 = interface { local interface interface=$1 local gateway # # First assume that this is some sort of dynamic interface # gateway=$( detect_dynamic_gateway $interface ) # # Maybe there's a default route through this gateway already # [ -n "$gateway" ] || gateway=$(find_gateway $($IP -4 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 } # # Disable IPV6 # disable_ipv6() { local temp temp="$($IP -f inet6 addr list 2> /dev/null)" if [ -n "$temp" ]; then if [ -x "$IP6TABLES" ]; then $IP6TABLES -P FORWARD DROP $IP6TABLES -P INPUT DROP $IP6TABLES -P OUTPUT DROP $IP6TABLES -F $IP6TABLES -X for temp in $(find_loopback_interfaces); do $IP6TABLES -A OUTPUT -o $temp -j ACCEPT $IP6TABLES -A INPUT -i $temp -j ACCEPT done else error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables" fi fi } # # Add an additional gateway to the default route # add_gateway() # $1 = Delta $2 = Table Number { local route local weight local delta local dev route=`$IP -4 -o route ls table $2 | grep ^default | sed 's/default //; s/[\]//g'` if [ -z "$route" ]; then run_ip route add default scope global table $2 $1 else delta=$1 if ! echo $route | grep -qF ' nexthop '; then if echo $route | grep -qF via; then route=`echo $route | sed 's/via/nexthop via/'` else route="nexthop $route" fi dev=$(find_device $route) if [ -f ${VARDIR}/${dev}_weight ]; then weight=`cat ${VARDIR}/${dev}_weight` route="$route weight $weight" fi fi run_ip route replace default scope global table $2 $route $delta fi } # # Remove a gateway from the default route # delete_gateway() # $! = Description of the Gateway $2 = table number $3 = device { local route local gateway local dev route=`$IP -4 -o route ls table $2 | grep ^default | sed 's/[\]//g'` gateway=$1 if [ -n "$route" ]; then if echo $route | grep -qF ' nexthop '; then gateway="nexthop $gateway" eval route=\`echo $route \| sed \'s/$gateway/ /\'\` run_ip route replace table $2 $route else dev=$(find_device $route) [ "$dev" = "$3" ] && run_ip route delete default table $2 fi fi } # # Determine the MAC address of the passed IP through the passed interface # find_mac() # $1 = IP address, $2 = interface { if interface_is_usable $2 ; then qt ping -nc 1 -t 2 -I $2 $1 local result result=$($IP neigh list | awk "/^$1 / {print \$5}") case $result in \<*\>) ;; *) [ -n "$result" ] && echo $result ;; esac fi } # # Clear Proxy Arp # delete_proxyarp() { if [ -f ${VARDIR}/proxyarp ]; then while read address interface external haveroute; do qtnoin $IP -4 neigh del proxy $address dev $external [ -z "${haveroute}${g_noroutes}" ] && qtnoin $IP -4 route del $address/32 dev $interface f=/proc/sys/net/ipv4/conf/$interface/proxy_arp [ -f $f ] && echo 0 > $f done < ${VARDIR}/proxyarp rm -f ${VARDIR}/proxyarp fi } # # Remove all Shorewall-added rules # clear_firewall() { stop_firewall setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT run_iptables -F qt $IPTABLES -t raw -F echo 1 > /proc/sys/net/ipv4/ip_forward if [ -n "$DISABLE_IPV6" ]; then if [ -x $IP6TABLES ]; then $IP6TABLES -P INPUT ACCEPT 2> /dev/null $IP6TABLES -P OUTPUT ACCEPT 2> /dev/null $IP6TABLES -P FORWARD ACCEPT 2> /dev/null fi fi run_clear_exit set_state "Cleared" logger -p kern.info "$g_product Cleared" } # # Get a list of all configured broadcast addresses on the system # get_all_bcasts() { $IP -f inet addr show 2> /dev/null | grep 'inet.*brd' | grep -v '/32 ' | sed 's/inet.*brd //; s/scope.*//;' | sort -u } ?else ################################################################################# # IPv6-specific Functions ################################################################################# # # Determine if interface is usable from a Netfilter perspective # interface_is_usable() # $1 = interface { local status; status=0 if [ "$1" != lo ]; then if interface_is_up $1 && [ "$(find_first_interface_address_if_any $1)" != :: ]; then [ "$COMMAND" = enable ] || run_isusable_exit $1 status=$? else status=1 fi fi return $status } # # 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.*//' } # # 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 } # # 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 } # # Add an additional gateway to the default route # add_gateway() # $1 = Delta $2 = Table Number { local route local weight local delta local dev run_ip route add default scope global table $2 $1 } # # Remove a gateway from the default route # delete_gateway() # $! = Description of the Gateway $2 = table number $3 = device { local route local gateway local dev route=`$IP -6 -o route ls table $2 | grep ^default | sed 's/[\]//g'` gateway=$1 dev=$(find_device $route) [ "$dev" = "$3" ] && run_ip route delete default table $2 } # # Clear Proxy NDP # delete_proxyndp() { if [ -f ${VARDIR}/proxyndp ]; then while read address interface external haveroute; do qt $IP -6 neigh del proxy $address dev $external [ -z "${haveroute}${g_noroutes}" ] && qt $IP -6 route del $address/128 dev $interface f=/proc/sys/net/ipv6/conf/$interface/proxy_ndp [ -f $f ] && echo 0 > $f done < ${VARDIR}/proxyndp rm -f ${VARDIR}/proxyndp fi } # # Remove all Shorewall-added rules # clear_firewall() { stop_firewall setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT run_iptables -F qt $IP6TABLES -t raw -F echo 1 > /proc/sys/net/ipv6/conf/all/forwarding run_clear_exit set_state "Cleared" logger -p kern.info "$g_product Cleared" } ?endif # IPv6-specific functions. shorewall-5.0.4/Perl/getparams0000755000000000000000000000261112647470621015042 0ustar rootroot#!/bin/sh # # The Shoreline Firewall Packet Filtering Firewall Param File Helper - V4.4 # # (c) 2010,2011,2014 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # 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 . # # Parameters: # # $1 = Path name of params file # $2 = $CONFIG_PATH # $3 = Address family (4 or 6) # if [ "$3" = 6 ]; then PRODUCT=shorewall6 else PRODUCT=shorewall fi # # This is modified by the installer when ${SHAREDIR} != /usr/share # . /usr/share/shorewall/shorewallrc g_program=$PRODUCT g_sharedir="$SHAREDIR/shorewall" g_confdir="$CONFDIR/$PRODUCT" g_readrc=1 . $g_sharedir/lib.cli CONFIG_PATH="$2" set -a . $1 >&2 # Avoid spurious output on STDOUT set +a export -p shorewall-5.0.4/Perl/Shorewall/0000755000000000000000000000000012650243750015064 5ustar rootrootshorewall-5.0.4/Perl/Shorewall/IPAddrs.pm0000644000000000000000000005636012650243750016722 0ustar rootroot# # Shorewall 4.4 -- /usr/share/shorewall/Shorewall/IPAddrs.pm # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2007,2008,2009,2010,2011 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # 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 . # # This module provides interfaces for dealing with IPv4 addresses, protocol names, and # port names. It also exports functions for validating protocol- and port- (service) # related constructs. # package Shorewall::IPAddrs; require Exporter; use Shorewall::Config qw( :DEFAULT split_list require_capability in_hex8 numeric_value F_IPV4 F_IPV6 :protocols %config ); use Socket; use strict; our @ISA = qw(Exporter); our @EXPORT = ( qw( ALLIPv4 ALLIPv6 NILIPv4 NILIPv6 IPv4_MULTICAST IPv6_MULTICAST IPv6_LINKLOCAL IPv6_SITELOCAL IPv6_LOOPBACK IPv6_LINK_ALLNODES IPv6_LINK_ALLRTRS IPv6_SITE_ALLNODES IPv6_SITE_ALLRTRS ALLIP NILIP ALL VLSMv4 VLSMv6 VLSM valid_address validate_address validate_net decompose_net decompose_net_u32 compare_nets validate_host validate_range ip_range_explicit expand_port_range allipv4 allipv6 allip nilipv4 nilipv6 nilip rfc1918_networks resolve_proto resolve_dnsname proto_name validate_port validate_portpair validate_portpair1 validate_port_list validate_icmp validate_icmp6 ) ); our @EXPORT_OK = qw( ); our $VERSION = '4.6_13'; # # Some IPv4/6 useful stuff # our @allipv4 = ( '0.0.0.0/0' ); our @allipv6 = ( '::/0' ); our $allip; our @allip; our @nilipv4 = ( '0.0.0.0' ); our @nilipv6 = ( '::' ); our $nilip; our @nilip; our $vlsm_width; our $valid_address; our $validate_address; our $validate_net; our $resolve_dnsname; our $validate_range; our $validate_host; our $family; use constant { ALLIPv4 => '0.0.0.0/0' , ALLIPv6 => '::/0' , NILIPv4 => '0.0.0.0' , NILIPv6 => '::' , IPv4_MULTICAST => '224.0.0.0/4' , IPv6_MULTICAST => 'ff00::/8' , IPv6_LINKLOCAL => 'fe80::/10' , IPv6_SITELOCAL => 'feC0::/10' , IPv6_LOOPBACK => '::1' , IPv6_LINK_ALLNODES => 'ff01::1' , IPv6_LINK_ALLRTRS => 'ff01::2' , IPv6_SITE_ALLNODES => 'ff02::1' , IPv6_SITE_ALLRTRS => 'ff02::2' , VLSMv4 => 32, VLSMv6 => 128, }; our @rfc1918_networks = ( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ); # # Note: initialize() is declared at the bottom of the file # sub vlsm_to_mask( $ ) { my $vlsm = $_[0]; in_hex8 ( ( 0xFFFFFFFF << ( VLSMv4 - $vlsm ) ) & 0xFFFFFFFF ); } sub valid_4address( $ ) { my $address = $_[0]; my @address = split /\./, $address; return 0 unless @address == 4; for ( @address ) { return 0 unless /^\d+$/ && $_ < 256; } 1; } sub validate_4address( $$ ) { my ( $addr, $allow_name ) = @_; my @addrs = ( $addr ); unless ( valid_4address $addr ) { fatal_error "Invalid IP Address ($addr)" unless $allow_name; fatal_error "Unknown Host ($addr)" unless @addrs = gethostbyname( $addr ); if ( defined wantarray ) { shift @addrs for (1..4); for ( @addrs ) { $_ = ( inet_ntoa( $_ ) ); } } } defined wantarray ? wantarray ? @addrs : $addrs[0] : undef; } sub resolve_4dnsname( $ ) { my $net = $_[0]; my @addrs; fatal_error "Unknown Host ($net)" unless @addrs = gethostbyname( $net ); shift @addrs for (1..4); for ( @addrs ) { $_ = ( inet_ntoa( $_ ) ); } @addrs; } sub decodeaddr( $ ) { my $address = $_[0]; my @address = split /\./, $address; my $result = shift @address; for ( @address ) { $result = ( $result << 8 ) | $_; } $result; } sub encodeaddr( $ ) { my $addr = $_[0]; my $result = $addr & 0xff; for my $i ( 1..3 ) { my $a = ($addr = $addr >> 8) & 0xff; $result = "$a.$result"; } $result; } sub validate_4net( $$ ) { my ($net, $vlsm, $rest) = split( '/', $_[0], 3 ); my $allow_name = $_[1]; $net = '' unless defined $net; fatal_error "Missing address" if $net eq ''; if ( $net =~ /\+(\[?)/ ) { if ( $1 ) { fatal_error "An ipset list ($net) is not allowed in this context"; } elsif ( $net =~ /^\+[a-zA-Z][-\w]*$/ ) { fatal_error "An ipset name ($net) is not allowed in this context"; } else { fatal_error "Invalid ipset name ($net)"; } } if ( defined $vlsm ) { fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= VLSMv4; fatal_error "Invalid Network address ($_[0])" if defined $rest; fatal_error "Invalid IP address ($net)" unless valid_4address $net; } else { fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net; my $net1 = validate_4address $net, $allow_name; $net = $net1 unless $config{DEFER_DNS_RESOLUTION}; $vlsm = VLSMv4; } if ( defined wantarray ) { if ( wantarray ) { assert( ! $allow_name ); ( decodeaddr( $net ) , $vlsm ); } elsif ( valid_4address $net ) { $vlsm == VLSMv4 ? $net : "$net/$vlsm"; } else { $net; } } } sub validate_4range( $$ ) { my ( $low, $high ) = @_; validate_4address $low, 0; validate_4address $high, 0; my $first = decodeaddr $low; my $last = decodeaddr $high; fatal_error "Invalid IP Range ($low-$high)" unless $first <= $last; "$low-$high"; } sub validate_4host( $$ ) { my ( $host, $allow_name ) = $_[0]; if ( $host =~ /^(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$/ ) { validate_4range $1, $2; } else { validate_4net( $host, $allow_name ); } } sub ip_range_explicit( $ ) { my $range = $_[0]; my @result; my ( $low, $high ) = split /-/, $range; validate_4address $low, 0; push @result, $low; if ( defined $high ) { validate_4address $high, 0; my $first = decodeaddr $low; my $last = decodeaddr $high; my $diff = $last - $first; fatal_error "Invalid IP Range ($range)" unless $diff >= 0 && $diff <= 256; while ( ++$first <= $last ) { push @result, encodeaddr( $first ); } } @result; } sub decompose_net( $ ) { my $net = $_[0]; ( $net, my $vlsm ) = validate_net( $net , 0 ); ( ( $family == F_IPV4 ? encodeaddr( $net) : normalize_6addr( $net ) ) , $vlsm ); } # # This function returns an array of (mask, address) pairs. For IPv4, only one # pair is returned. For IPv6, one pair is returned for every 32 bits of the VLSM. # # For example, a /64 network will return two pairs; a /80 would return 3. # sub decompose_net_u32( $ ) { my ( $net, $vlsm ) = validate_net( $_[0] , 0 ); assert( wantarray ); if ( $family == F_IPV4 ) { $vlsm = ( 0xffffffff << ( 32 - $vlsm ) ) & 0xffffffff; return ( in_hex8( $vlsm ) , in_hex8( $net ) ); } # # Split the address into 16-bit hex numbers # my @addr = split( ':', normalize_6addr( $net ) ); # # Replace each element by its numeric value # no warnings; $_ = oct( '0x' . $_ ) for @addr; use warnings; my @result; while ( $vlsm >= 32 ) { push @result, in_hex8( 0xffffff ); push @result, in_hex8( ( shift( @addr ) << 16 ) | shift( @addr ) ); $vlsm -= 32; } if ( $vlsm ) { push @result, in_hex8( ( 0xffffffff << ( 32 - $vlsm ) ) & 0xffffffff ); push @result, in_hex8( ( $addr[0] << 16) | $addr[1] ); } @result; } sub compare_nets( $$ ) { my ( @net1, @net2 ); @net1 = decompose_net( $_[0] ); @net2 = decompose_net( $_[1] ); $net1[0] eq $net2[0] && $net1[1] == $net2[1]; } sub allipv4() { @allipv4; } sub allipv6() { @allipv6; } sub nilipv4() { @nilipv4; } sub nilipv6() { @nilipv6; } sub rfc1918_networks() { @rfc1918_networks } # # Protocol/port validation # our %nametoproto = ( all => 0, ALL => 0, icmp => 1, ICMP => 1, tcp => 6, TCP => 6, udp => 17, UDP => 17 ); our @prototoname = ( 'all', 'icmp', '', '', '', '', 'tcp', '', '', '', '', '', '', '', '', '', '', 'udp' ); # # Returns the protocol number if the passed argument is a valid protocol number or name. Returns undef otherwise # sub resolve_proto( $ ) { my $proto = $_[0]; my $number; if ( $proto =~ /^\d+$/ || $proto =~ /^0x/ ) { $number = numeric_value ( $proto ); defined $number && $number <= 255 ? $number : undef; } else { fatal_error "A protocol list ($proto) is not allowed in this context" if $proto =~ /,/; # # Allow 'icmp' as a synonym for 'ipv6-icmp' in IPv6 compilations # $proto= 'ipv6-icmp' if $proto eq 'icmp' && $family == F_IPV6; defined( $number = $nametoproto{$proto} ) ? $number : scalar getprotobyname $proto; } } sub proto_name( $ ) { my $proto = $_[0]; $proto =~ /^(\d+)$/ ? $prototoname[ $proto ] || scalar getprotobynumber $proto : $proto } sub validate_port( $$ ) { my ($proto, $port) = @_; my $value; if ( $port =~ /^(\d+)$/ || $port =~ /^0x/ ) { $port = numeric_value $port; return $port if defined $port && $port && $port <= 65535; } else { $proto = proto_name $proto if $proto =~ /^(\d+)$/; $value = getservbyname( $port, $proto ); } return $value if defined $value; fatal_error "The separator for a port range is ':', not '-' ($port)" if $port =~ /^\d+-\d+$/; fatal_error "Invalid/Unknown $proto port/service ($_[1])" unless defined $value; } sub validate_portpair( $$ ) { my ($proto, $portpair) = @_; my $what; fatal_error "Invalid port range ($portpair)" if $portpair =~ tr/:/:/ > 1; $portpair = "0$portpair" if substr( $portpair, 0, 1 ) eq ':'; $portpair = "${portpair}65535" if substr( $portpair, -1, 1 ) eq ':'; my @ports = split /:/, $portpair, 2; my $protonum = resolve_proto( $proto ) || 0; $_ = validate_port( $protonum, $_) for grep $_, @ports; if ( @ports == 2 ) { $what = 'port range'; fatal_error "Invalid port range ($portpair)" unless $ports[0] < $ports[1]; } else { $what = 'port'; } fatal_error "Using a $what ( $portpair ) requires PROTO TCP, UDP, UDPLITE, SCTP or DCCP" unless defined $protonum && ( $protonum == TCP || $protonum == UDP || $protonum == UDPLITE || $protonum == SCTP || $protonum == DCCP ); join ':', @ports; } sub validate_portpair1( $$ ) { my ($proto, $portpair) = @_; my $what; fatal_error "Invalid port range ($portpair)" if $portpair =~ tr/-/-/ > 1; $portpair = "0$portpair" if substr( $portpair, 0, 1 ) eq ':'; $portpair = "${portpair}65535" if substr( $portpair, -1, 1 ) eq ':'; my @ports = split /-/, $portpair, 2; my $protonum = resolve_proto( $proto ) || 0; $_ = validate_port( $protonum, $_) for grep $_, @ports; if ( @ports == 2 ) { $what = 'port range'; fatal_error "Invalid port range ($portpair)" unless $ports[0] < $ports[1]; } else { $what = 'port'; } fatal_error "Using a $what ( $portpair ) requires PROTO TCP, UDP, SCTP or DCCP" unless defined $protonum && ( $protonum == TCP || $protonum == UDP || $protonum == SCTP || $protonum == DCCP ); join '-', @ports; } sub validate_port_list( $$ ) { my $result = ''; my ( $proto, $list ) = @_; my @list = split_list( $list, 'port' ); if ( @list > 1 && $list =~ /:/ ) { require_capability( 'XMULTIPORT' , 'Port ranges in a port list', '' ); } $proto = proto_name $proto; for ( @list ) { my $value = validate_portpair( $proto , $_ ); $result = $result ? join ',', $result, $value : $value; } $result; } my %icmp_types = ( any => 'any', 'echo-reply' => 0, 'destination-unreachable' => 3, 'network-unreachable' => '3/0', 'host-unreachable' => '3/1', 'protocol-unreachable' => '3/2', 'port-unreachable' => '3/3', 'fragmentation-needed' => '3/4', 'source-route-failed' => '3/5', 'network-unknown' => '3/6', 'host-unknown' => '3/7', 'network-prohibited' => '3/9', 'host-prohibited' => '3/10', 'TOS-network-unreachable' => '3/11', 'TOS-host-unreachable' => '3/12', 'communication-prohibited' => '3/13', 'host-precedence-violation' => '3/14', 'precedence-cutoff' => '3/15', 'source-quench' => 4, 'redirect' => 5, 'network-redirect' => '5/0', 'host-redirect' => '5/1', 'TOS-network-redirect' => '5/2', 'TOS-host-redirect' => '5/3', 'echo-request' => '8', 'router-advertisement' => 9, 'router-solicitation' => 10, 'time-exceeded' => 11, 'ttl-zero-during-transit' => '11/0', 'ttl-zero-during-reassembly' => '11/1', 'parameter-problem' => 12, 'ip-header-bad' => '12/0', 'required-option-missing' => '12/1', 'timestamp-request' => 13, 'timestamp-reply' => 14, 'address-mask-request' => 17, 'address-mask-reply' => 18 ); sub validate_icmp( $ ) { my $type = $_[0]; my $value = $icmp_types{$type}; return $value if defined $value; if ( $type =~ /^(\d+)(\/(\d+))?$/ ) { return $type if $1 < 256 && ( ! $2 || $3 < 256 ); } fatal_error "Invalid ICMP Type ($type)" } # # Expands a port range into a minimal list of ( port, mask ) pairs. # Each port and mask are expressed as 4 hex nibbles without a leading '0x'. # # Example: # # DB<3> @foo = Shorewall::IPAddrs::expand_port_range( 6, '110:' ); print "@foo\n" # 006e fffe 0070 fff0 0080 ff80 0100 ff00 0200 fe00 0400 fc00 0800 f800 1000 f000 2000 e000 4000 c000 8000 8000 # sub expand_port_range( $$ ) { my ( $proto, $range ) = @_; if ( $range =~ /^(.*):(.*)$/ ) { my ( $first, $last ) = ( $1, $2); my @result; fatal_error "Invalid port range ($range)" unless $first ne '' or $last ne ''; # # Supply missing first/last port number # $first = 0 if $first eq ''; $last = 65535 if $last eq ''; # # Validate the ports # ( $first , $last ) = ( validate_port( $proto, $first || 1 ) , validate_port( $proto, $last ) ); $last++; #Increment last address for limit testing. # # Break the range into groups: # # - If the first port in the remaining range is odd, then the next group is ( , ffff ). # - Otherwise, find the largest power of two P that divides the first address such that # the remaining range has less than or equal to P ports. The next group is # ( , ~( P-1 ) ). # while ( ( my $ports = ( $last - $first ) ) > 0 ) { my $mask = 0xffff; #Mask for current ports in group. my $y = 2; #Next power of two to test my $z = 1; #Number of ports in current group (Previous value of $y). while ( ( ! ( $first % $y ) ) && ( $y <= $ports ) ) { $mask <<= 1; $z = $y; $y <<= 1; } # # push @result, sprintf( '%04x', $first ) , sprintf( '%04x' , $mask & 0xffff ); $first += $z; } fatal_error "Invalid port range ($range)" unless @result; # first port > last port @result; } else { ( sprintf( '%04x' , validate_port( $proto, $range ) ) , 'ffff' ); } } sub valid_6address( $ ) { my $address = $_[0]; my @address = split /:/, $address; my $max; if ( $address[-1] && $address[-1] =~ /^\d+\.\d+\.\d+\.\d+$/ ) { return 0 unless valid_4address pop @address; $max = 6; $address = join ':', @address; return 1 if $address eq ':'; } else { $max = 8; } return 0 if @address > $max; return 0 unless $address =~ /^[a-fA-F:\d]+$/; return 0 unless ( @address == $max ) || $address =~ /::/; return 0 if $address =~ /:::/ || $address =~ /::.*::/; unless ( $address =~ /^::/ ) { return 0 if $address =~ /^:/; } unless ( $address =~ /::$/ ) { return 0 if $address =~ /:$/; } for my $a ( @address ) { return 0 unless $a eq '' || ( $a =~ /^[a-fA-f\d]+$/ && length $a < 5 ); } 1; } sub validate_6address( $$ ) { my ( $addr, $allow_name ) = @_; my @addrs = ( $addr ); unless ( valid_6address $addr ) { fatal_error "Invalid IPv6 Address ($addr)" unless $allow_name; require Socket6; fatal_error "Unknown Host ($addr)" unless (@addrs = Socket6::gethostbyname2( $addr, Socket6::AF_INET6())); if ( defined wantarray ) { shift @addrs for (1..4); for ( @addrs ) { $_ = Socket6::inet_ntop( Socket6::AF_INET6(), $_ ); } } } defined wantarray ? wantarray ? @addrs : $addrs[0] : undef; } sub resolve_6dnsname( $ ) { my $net = $_[0]; my @addrs; require Socket6; fatal_error "Unknown Host ($net)" unless (@addrs = Socket6::gethostbyname2( $net, Socket6::AF_INET6())); shift @addrs for (1..4); for ( @addrs ) { $_ = Socket6::inet_ntop( Socket6::AF_INET6(), $_ ); } @addrs; } sub validate_6net( $$ ) { my ( $net, $allow_name ) = @_; if ( $net =~ /^\[(.+)]$/ ) { $net = $1; } elsif ( $net =~ /^\[(.+)\]\/(\d+)$/ ) { $net = join( '/', $1, $2 ); } fatal_error "Invalid Network Address($net)" if $net =~ /\[/; ($net, my $vlsm, my $rest) = split( '/', $net, 3 ); fatal_error 'Invalid Network Address(' . join( '/', $net, $vlsm, $rest ) if defined $rest; if ( $net =~ /\+(\[?)/ ) { if ( $1 ) { fatal_error "An ipset list ($net) is not allowed in this context"; } elsif ( $net =~ /^\+[a-zA-Z][-\w]*$/ ) { fatal_error "An ipset name ($net) is not allowed in this context"; } else { fatal_error "Invalid ipset name ($net)"; } } fatal_error "Invalid Network address ($_[0])" unless supplied $net; if ( defined $vlsm ) { fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= VLSMv6; fatal_error "Invalid Network address ($_[0])" if defined $rest; fatal_error "Invalid IPv6 address ($net)" unless valid_6address $net; } else { fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/'; my $net1 = validate_6address $net, $allow_name; $net = $net1 unless $config{DEFER_DNS_RESOLUTION}; $vlsm = VLSMv6; } if ( defined wantarray ) { if ( wantarray ) { assert( ! $allow_name ); ( $net , $vlsm ); } elsif ( valid_6address ( $net ) ) { $vlsm == VLSMv6 ? $net : "$net/$vlsm"; } else { $net; } } } # # Note: the input is assumed to be a valid IPv6 address # sub normalize_6addr( $ ) { my $addr = shift; if ( $addr eq '::' ) { '0:0:0:0:0:0:0:0'; } else { # # Suppress leading zeros # $addr =~ s/^0+//; $addr =~ s/:0+/:/g; $addr =~ s/^:/0:/; $addr =~ s/:$/:0/; $addr =~ s/::/:0::/ while $addr =~ tr/:/:/ < 7; # # Note: "s/::/:0:/g" doesn't work here # 1 while $addr =~ s/::/:0:/; $addr =~ s/^0+:/0:/; $addr; } } sub validate_6range( $$ ) { my ( $low, $high ) = @_; if ( $low =~ /^\[(.+)\]$/ ) { $low = $1; } elsif ( $low =~ /^\[(.+)\]\/(\d+)$/ ) { $low = join( '/', $1, $2 ); } if ( $high =~ /^\[(.+)\]$/ ) { $high = $1; } elsif ( $high =~ /^\[(.+)\]\/(\d+)$/ ) { $high = join( '/', $1, $2 ); } validate_6address $low, 0; validate_6address $high, 0; my @low = split ":", normalize_6addr( $low ); my @high = split ":", normalize_6addr( $high ); while ( @low ) { my ( $l, $h) = ( shift @low, shift @high ); next if hex "0x$l" == hex "0x$h"; return "$low-$high" if hex "0x$l" < hex "0x$h"; last; } fatal_error "Invalid IPv6 Range ($low-$high)"; } sub validate_6host( $$ ) { my ( $host, $allow_name ) = @_; if ( $host =~ /^(.*:.*)-(.*:.*)$/ ) { validate_6range $1, $2; } else { validate_6net( $host, $allow_name ); } } my %ipv6_icmp_types = ( any => 'any', 'destination-unreachable' => 1, 'no-route' => '1/0', 'communication-prohibited' => '1/1', 'address-unreachable' => '1/3', 'port-unreachable' => '1/4', 'packet-too-big' => 2, 'time-exceeded' => 3, 'ttl-exceeded' => 3, 'ttl-zero-during-transit' => '3/0', 'ttl-zero-during-reassembly' => '3/1', 'parameter-problem' => 4, 'bad-header' => '4/0', 'unknown-header-type' => '4/1', 'unknown-option' => '4/2', 'echo-request' => 128, 'echo-reply' => 129, 'router-solicitation' => 133, 'router-advertisement' => 134, 'neighbour-solicitation' => 135, 'neighbour-advertisement' => 136, redirect => 137 ); sub validate_icmp6( $ ) { my $type = $_[0]; my $value = $ipv6_icmp_types{$type}; return $value if defined $value; if ( $type =~ /^(\d+)(\/(\d+))?$/ ) { return $type if $1 < 256 && ( ! $2 || $3 < 256 ); } fatal_error "Invalid IPv6 ICMP Type ($type)" } sub ALLIP() { $allip; } sub allip() { @allip; } sub NILIP() { $nilip; } sub nilip() { @nilip; } sub VLSM() { $vlsm_width; } sub valid_address ( $ ) { $valid_address->(@_); } sub validate_address ( $$ ) { $validate_address->(@_); } sub validate_net ( $$ ) { $validate_net->(@_); } sub resolve_dnsname( $ ) { $resolve_dnsname->(@_); } sub validate_range ($$ ) { $validate_range->(@_); } sub validate_host ($$ ) { $validate_host->(@_); } # # Rather than initializing globals in an INIT block or during declaration, # we initialize them in a function. This is done for two reasons: # # 1. Proper initialization depends on the address family which isn't # known until the compiler has started. # # 2. The compiler can run multiple times in the same process so it has to be # able to re-initialize its dependent modules' state. # sub initialize( $ ) { $family = shift; if ( $family == F_IPV4 ) { $allip = ALLIPv4; @allip = @allipv4; $nilip = NILIPv4; @nilip = @nilipv4; $vlsm_width = VLSMv4; $valid_address = \&valid_4address; $validate_address = \&validate_4address; $validate_net = \&validate_4net; $validate_range = \&validate_4range; $validate_host = \&validate_4host; $resolve_dnsname = \&resolve_4dnsname; } else { $allip = ALLIPv6; @allip = @allipv6; $nilip = NILIPv6; @nilip = @nilipv6; $vlsm_width = VLSMv6; $valid_address = \&valid_6address; $validate_address = \&validate_6address; $validate_net = \&validate_6net; $validate_range = \&validate_6range; $validate_host = \&validate_6host; $resolve_dnsname = \&resolve_6dnsname; } } 1; shorewall-5.0.4/Perl/Shorewall/Proxyarp.pm0000644000000000000000000001403112650243750017245 0ustar rootroot# # Shorewall 4.4 -- /usr/share/shorewall/Shorewall/Proxyarp.pm # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2007,2008,2009,2011,2011 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # 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 . # # package Shorewall::Proxyarp; require Exporter; use Shorewall::Config qw(:DEFAULT :internal); use Shorewall::Zones; use strict; our @ISA = qw(Exporter); our @EXPORT = qw( setup_proxy_arp dump_proxy_arp ); our @EXPORT_OK = qw( initialize ); our $VERSION = '4.6_12'; our @proxyarp; our $family; # # Rather than initializing globals in an INIT block or during declaration, # we initialize them in a function. This is done for two reasons: # # 1. Proper initialization depends on the address family which isn't # known until the compiler has started. # # 2. The compiler can run multiple times in the same process so it has to be # able to re-initialize its dependent modules' state. # sub initialize( $ ) { $family = shift; @proxyarp = (); } sub setup_one_proxy_arp( $$$$$$$ ) { my ( $address, $interface, $physical, $external, $extphy, $haveroute, $persistent) = @_; my $proto = $family == F_IPV4 ? 'ARP' : 'NDP'; if ( "\L$haveroute" eq 'no' || $haveroute eq '-' ) { $haveroute = ''; } elsif ( "\L$haveroute" eq 'yes' ) { $haveroute = 'yes'; } else { fatal_error "Invalid value ($haveroute) for HAVEROUTE"; } if ( "\L$persistent" eq 'no' || $persistent eq '-' ) { $persistent = ''; } elsif ( "\L$persistent" eq 'yes' ) { $persistent = 'yes'; } else { fatal_error "Invalid value ($persistent) for PERSISTENT"; } unless ( $haveroute ) { fatal_error "HAVEROUTE=No requires an INTERFACE" if $interface eq '-'; if ( $family == F_IPV4 ) { emit "[ -n \"\$g_noroutes\" ] || run_ip route replace $address/32 dev $physical"; } else { emit( 'if [ -z "$g_noroutes" ]; then', " qt \$IP -6 route del $address/128 dev $physical", " run_ip route add $address/128 dev $physical", 'fi' ); } $haveroute = 1 if $persistent; } emit ( "run_ip neigh add proxy $address nud permanent dev $extphy" , qq(progress_message " Host $address connected to $interface added to $proto on $extphy"\n) ); push @proxyarp, "$address $interface $external $haveroute"; progress_message " Host $address connected to $interface added to $proto on $external"; } # # Setup Proxy ARP/NDP # sub setup_proxy_arp() { my $proto = $family == F_IPV4 ? 'arp' : 'ndp'; # Protocol my $file_opt = 'proxy' . $proto; # Name of config file and of the interface option my $proc_file = 'proxy_' . $proto; # Name of the corresponding file in /proc my $interfaces= find_interfaces_by_option $file_opt; my $fn = open_file $file_opt; if ( @$interfaces || $fn ) { my $first_entry = 1; save_progress_message 'Setting up Proxy ' . uc($proto) . '...'; my ( %set, %reset ); while ( read_a_line( NORMAL_READ ) ) { my ( $address, $interface, $external, $haveroute, $persistent ) = split_line( $file_opt . 'file ', { address => 0, interface => 1, external => 2, haveroute => 3, persistent => 4 } ); if ( $first_entry ) { progress_message2 "$doing $fn..."; $first_entry = 0; } fatal_error 'EXTERNAL must be specified' if $external eq '-'; fatal_error "Unknown interface ($external)" unless known_interface $external; fatal_error "Wildcard interface ($external) not allowed" if $external =~ /\+$/; $reset{$external} = 1 unless $set{$external}; my $extphy = get_physical $external; my $physical = '-'; if ( $interface ne '-' ) { fatal_error "Unknown interface ($interface)" unless known_interface $interface; fatal_error "Wildcard interface ($interface) not allowed" if $interface =~ /\+$/; $physical = physical_name $interface; $set{$interface} = 1; } setup_one_proxy_arp( $address, $interface, $physical, $external, $extphy, $haveroute, $persistent ); } emit ''; for my $interface ( sort keys %reset ) { unless ( $set{interface} ) { my $physical = get_physical $interface; emit ( "if [ -f /proc/sys/net/ipv$family/conf/$physical/$proc_file ]; then" , " echo 0 > /proc/sys/net/ipv$family/conf/$physical/$proc_file" ); emit "fi\n"; } } for my $interface ( sort keys %set ) { my $physical = get_physical $interface; emit ( "if [ -f /proc/sys/net/ipv$family/conf/$physical/$proc_file ]; then" , " echo 1 > /proc/sys/net/ipv$family/conf/$physical/$proc_file" ); emit ( 'else' , " error_message \" WARNING: Cannot set the '$file_opt' option for interface $physical\"" ) unless interface_is_optional( $interface ); emit "fi\n"; } for my $interface ( @$interfaces ) { my $value = get_interface_option $interface, $file_opt; my $optional = interface_is_optional $interface; $interface = get_physical $interface; emit ( "if [ -f /proc/sys/net/ipv$family/conf/$interface/$proc_file ] ; then" , " echo $value > /proc/sys/net/ipv$family/conf/$interface/$proc_file" ); emit ( 'else' , " error_message \"WARNING: Unable to set/reset the '$file_opt' option on $interface\"" ) unless $optional; emit "fi\n"; } } } sub dump_proxy_arp() { for ( @proxyarp ) { emit_unindented $_; } } 1; shorewall-5.0.4/Perl/Shorewall/Config.pm0000644000000000000000000054002112650243750016631 0ustar rootroot# # Shorewall 4.4 -- /usr/share/shorewall/Shorewall/Config.pm # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2007,2008,2009,2010,2011,2012,2013 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # 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 . # # This module is responsible for lower level configuration file handling. # It also exports functions for generating warning and error messages. # The get_configuration function parses the shorewall.conf, capabilities and # modules files during compiler startup. The module also provides the basic # output file services such as creation of temporary 'script' files, writing # into those files (emitters) and finalizing those files (renaming # them to their final name and setting their mode appropriately). # package Shorewall::Config; use strict; use warnings; use File::Basename; use File::Temp qw/ tempfile tempdir /; use Cwd qw(abs_path getcwd); use autouse 'Carp' => qw(longmess confess); use Scalar::Util 'reftype'; use FindBin; use Digest::SHA qw(sha1_hex); our @ISA = qw(Exporter); # # Imported variables should be treated as read-only by importers # our @EXPORT = qw( warning_message fatal_error assert currentlineinfo shortlineinfo clear_currentfilename validate_level progress_message progress_message_nocompress progress_message2 progress_message3 supplied split_list shorewall get_action_params get_action_chain get_action_chain_name set_action_name_to_caller get_action_logging get_action_disposition set_action_disposition set_action_param fetch_inline_matches set_inline_matches set_comment push_comment pop_comment have_capability require_capability report_used_capabilities kernel_version ); our @EXPORT_OK = qw( $shorewall_dir initialize shorewall); our %EXPORT_TAGS = ( internal => [ qw( create_temp_script generate_sha1 finalize_script enable_script disable_script numeric_value numeric_value1 normalize_hex hex_value in_hex in_hex2 in_hex3 in_hex4 in_hex8 in_hexp emit emitstd emit_unindented save_progress_message save_progress_message_short set_timestamp set_verbosity set_log set_config_path close_log set_command push_indent pop_indent copy copy1 copy2 create_temp_aux_config finalize_aux_config set_shorewall_dir set_debug find_file find_writable_file split_list split_list1 split_list2 split_line split_line1 split_line2 first_entry open_file close_file push_open pop_open push_action_params pop_action_params default_action_params read_a_line which qt ensure_config_path add_param export_params get_configuration report_capabilities propagateconfig append_file run_user_exit run_user_exit1 run_user_exit2 generate_aux_config format_warning no_comment macro_comment dump_mark_layout set_section_function clear_section_function directive_callback $product $Product $toolname $command $doing $done $currentline $currentfilename $debug $file_format $comment %config %globals %config_files %shorewallrc %shorewallrc1 %helpers %helpers_map %helpers_enabled %helpers_aliases %actparms PARMSMODIFIED USEDCALLER F_IPV4 F_IPV6 TCP UDP UDPLITE ICMP DCCP IPv6_ICMP SCTP GRE MIN_VERBOSITY MAX_VERBOSITY PLAIN_READ EMBEDDED_ENABLED EXPAND_VARIABLES STRIP_COMMENTS SUPPRESS_WHITESPACE CONFIG_CONTINUATION DO_INCLUDE DO_SECTION NORMAL_READ OPTIMIZE_POLICY_MASK OPTIMIZE_POLICY_MASK2n4 OPTIMIZE_RULESET_MASK OPTIMIZE_USE_FIRST OPTIMIZE_ALL ) , ] , protocols => [ qw ( TCP UDP UDPLITE ICMP DCCP IPv6_ICMP SCTP GRE ) , ], ); Exporter::export_ok_tags('internal'); our $VERSION = '5.0_4'; # # describe the current command, it's present progressive, and it's completion. # our ($command, $doing, $done ); # # VERBOSITY # our $verbosity; # # Logging # our ( $log, $log_verbosity ); # # Timestamp each progress message, if true. # our $timestamp; # # Script (output) file handle # our $script; # # When 'true', writes to the script are enabled. Used to catch code emission between functions # our $script_enabled; # # True, if last line emitted is blank # our $lastlineblank; # # Tabs to indent the output # our $indent1; # # Characters to indent the output # our $indent2; # # Total indentation # our $indent; # # Script's Directory and File # our ( $dir, $file ); # # Temporary output file's name # our $tempfile; # # Fully qualified name of the configuration file # our $configfile; # # Misc Globals exported to other modules # our %globals; # # From shorewall.conf file - exported to other modules. # our %config; # # Entries in shorewall.conf that have been renamed # our %renamed = ( AUTO_COMMENT => 'AUTOCOMMENT', BLACKLIST_LOGLEVEL => 'BLACKLIST_LOG_LEVEL' ); # # Config options and global settings that are to be copied to output script # our @propagateconfig = qw/ DISABLE_IPV6 MODULESDIR MODULE_SUFFIX LOAD_HELPERS_ONLY LOCKFILE SUBSYSLOCK LOG_VERBOSITY RESTART/; # # From parsing the capabilities file or detecting capabilities # our %capabilities; # # Capabilities # our %capdesc = ( NAT_ENABLED => 'NAT', MANGLE_ENABLED => 'Packet Mangling', MULTIPORT => 'Multi-port Match' , XMULTIPORT => 'Extended Multi-port Match', EMULTIPORT => 'Enhanced Multi-port Match', CONNTRACK_MATCH => 'Connection Tracking Match', OLD_CONNTRACK_MATCH => 'Old conntrack match syntax', NEW_CONNTRACK_MATCH => 'Extended Connection Tracking Match', USEPKTTYPE => 'Packet Type Match', POLICY_MATCH => 'Policy Match', PHYSDEV_MATCH => 'Physdev Match', PHYSDEV_BRIDGE => 'Physdev-is-bridged support', LENGTH_MATCH => 'Packet length Match', IPRANGE_MATCH => 'IP Range Match', RECENT_MATCH => 'Recent Match', REAP_OPTION => 'Recent Match "--reap" option', OWNER_MATCH => 'Owner Match', OWNER_NAME_MATCH => 'Owner Name Match', IPSET_MATCH => 'Ipset Match', OLD_IPSET_MATCH => 'Old Ipset Match', IPSET_MATCH_NOMATCH => 'Ipset Match nomatch', IPSET_MATCH_COUNTERS => 'Ipset Match counters', IPSET_V5 => 'Version 5 ipsets', CONNMARK => 'CONNMARK Target', XCONNMARK => 'Extended CONNMARK Target', CONNMARK_MATCH => 'Connmark Match', XCONNMARK_MATCH => 'Extended Connmark Match', RAW_TABLE => 'Raw Table', IPP2P_MATCH => 'IPP2P Match', OLD_IPP2P_MATCH => 'Old IPP2P Match Syntax', CLASSIFY_TARGET => 'CLASSIFY Target', ENHANCED_REJECT => 'Extended Reject', KLUDGEFREE => 'Repeat match', MARK => 'MARK Target', XMARK => 'Extended Mark Target', EXMARK => 'Extended Mark Target 2', MANGLE_FORWARD => 'Mangle FORWARD Chain', COMMENTS => 'Comments', ADDRTYPE => 'Address Type Match', TCPMSS_MATCH => 'TCPMSS Match', HASHLIMIT_MATCH => 'Hashlimit Match', NFQUEUE_TARGET => 'NFQUEUE Target', REALM_MATCH => 'Realm Match', HELPER_MATCH => 'Helper Match', CONNLIMIT_MATCH => 'Connlimit Match', TIME_MATCH => 'Time Match', GOTO_TARGET => 'Goto Support', LOG_TARGET => 'LOG Target', ULOG_TARGET => 'ULOG Target', NFLOG_TARGET => 'NFLOG Target', LOGMARK_TARGET => 'LOGMARK Target', IPMARK_TARGET => 'IPMARK Target', PERSISTENT_SNAT => 'Persistent SNAT', OLD_HL_MATCH => 'Old Hash Limit Match', TPROXY_TARGET => 'TPROXY Target', FLOW_FILTER => 'Flow Classifier', FWMARK_RT_MASK => 'fwmark route mask', MARK_ANYWHERE => 'Mark in the filter table', HEADER_MATCH => 'Header Match', ACCOUNT_TARGET => 'ACCOUNT Target', AUDIT_TARGET => 'AUDIT Target', RAWPOST_TABLE => 'Rawpost Table', CONDITION_MATCH => 'Condition Match', IPTABLES_S => 'iptables -S', BASIC_FILTER => 'Basic Filter', BASIC_EMATCH => 'Basic Filter ematch', CT_TARGET => 'CT Target', STATISTIC_MATCH => 'Statistics Match', IMQ_TARGET => 'IMQ Target', DSCP_MATCH => 'DSCP Match', DSCP_TARGET => 'DSCP Target', GEOIP_MATCH => 'GeoIP Match' , RPFILTER_MATCH => 'RPFilter Match', NFACCT_MATCH => 'NFAcct Match', CHECKSUM_TARGET => 'Checksum Target', ARPTABLESJF => 'Arptables JF', MASQUERADE_TGT => 'MASQUERADE Target', UDPLITEREDIRECT => 'UDPLITE Port Redirection', NEW_TOS_MATCH => 'New tos Match', TARPIT_TARGET => 'TARPIT Target', IFACE_MATCH => 'Iface Match', TCPMSS_TARGET => 'TCPMSS Target', WAIT_OPTION => 'iptables --wait option', AMANDA_HELPER => 'Amanda Helper', FTP_HELPER => 'FTP Helper', FTP0_HELPER => 'FTP-0 Helper', H323_HELPER => 'H323 Helpers', IRC_HELPER => 'IRC Helper', IRC0_HELPER => 'IRC-0 Helper', NETBIOS_NS_HELPER => 'Netbios-ns Helper', PPTP_HELPER => 'PPTP Helper', SANE_HELPER => 'SANE Helper', SANE0_HELPER => 'SANE-0 Helper', SIP_HELPER => 'SIP Helper', SIP0_HELPER => 'SIP-0 Helper', SNMP_HELPER => 'SNMP Helper', TFTP_HELPER => 'TFTP Helper', TFTP0_HELPER => 'TFTP-0 Helper', # # Constants # LOG_OPTIONS => 'Log Options', CAPVERSION => 'Capability Version', KERNELVERSION => 'Kernel Version', ); our %used; use constant { USED => 1, REQUIRED => 2 }; use constant { ICMP => 1, TCP => 6, UDP => 17, DCCP => 33, GRE => 47, IPv6_ICMP => 58, SCTP => 132, UDPLITE => 136, }; # # Optimization masks # use constant { OPTIMIZE_POLICY_MASK => 0x02 , # Call optimize_policy_chains() OPTIMIZE_POLICY_MASK2n4 => 0x06 , OPTIMIZE_RULESET_MASK => 0x1C , # Call optimize_ruleset() OPTIMIZE_ALL => 0x1F , # Maximum value for documented categories. OPTIMIZE_USE_FIRST => 0x1000 # Always use interface 'first' chains -- undocumented }; our %helpers = ( amanda => UDP, ftp => TCP, irc => TCP, 'netbios-ns' => UDP, pptp => TCP, 'Q.931' => TCP, RAS => UDP, sane => TCP, sip => UDP, snmp => UDP, tftp => UDP, ); our %helpers_map; our %helpers_names; our %helpers_aliases; our %helpers_enabled; our %config_files = ( #accounting => 1, actions => 1, blacklist => 1, clear => 1, conntrack => 1, ecn => 1, findgw => 1, hosts => 1, init => 1, initdone => 1, interfaces => 1, isusable => 1, maclist => 1, masq => 1, nat => 1, netmap => 1, params => 1, policy => 1, providers => 1, proxyarp => 1, refresh => 1, refreshed => 1, restored => 1, rawnat => 1, route_rules => 1, routes => 1, routestopped => 1, rtrules => 1, rules => 1, scfilter => 1, secmarks => 1, start => 1, started => 1, stop => 1, stopped => 1, stoppedrules => 1, tcclasses => 1, tcclear => 1, tcdevices => 1, tcfilters => 1, tcinterfaces => 1, tcpri => 1, tcrules => 1, tos => 1, tunnels => 1, zones => 1 ); # # Options that involve the the AUDIT target # our @auditoptions = qw( BLACKLIST_DISPOSITION MACLIST_DISPOSITION TCP_FLAGS_DISPOSITION ); # # Directories to search for configuration files # our @config_path; # # Stash away file references here when we encounter INCLUDE # our @includestack; # # Allow nested opens # our @openstack; # # From the params file # our %params; # # Entries that the compiler adds to %params # our %compiler_params; # # Action parameters # our %actparms; our $parmsmodified; our $usedcaller; our $inline_matches; our $currentline; # Current config file line image our $currentfile; # File handle reference our $currentfilename; # File NAME our $currentlinenumber; # Line number our $perlscript; # File Handle Reference to current temporary file being written by an in-line Perl script our $perlscriptname; # Name of that file. our $embedded; # True if we're in an embedded perl script our @tempfiles; # Files that need unlinking at END our $first_entry; # Message to output or function to call on first non-blank line of a file our $file_format; # Format of configuration file. our $max_format; # Max format value our $comment; # Current COMMENT our $comments_allowed; # True if [?]COMMENT is allowed in the current file our $nocomment; # When true, ignore [?]COMMENT in the current file our $warningcount; # Used to suppress duplicate warnings about missing COMMENT support our $checkinline; # The -i option to check/compile/etc. our $directive_callback; # Function to call in compiler_directive our $shorewall_dir; # Shorewall Directory; if non-empty, search here first for files. our $debug; # Global debugging flag our $confess; # If true, use Carp to report errors with stack trace. our $family; # Protocol family (4 or 6) our $toolname; # Name of the tool to use (iptables or iptables6) our $toolNAME; # Tool name in CAPS our $product; # Name of product that will run the generated script our $Product; # $product with initial cap. our $sillyname; # Name of temporary filter chains for testing capabilities our $sillyname1; our $iptables; # Path to iptables/ip6tables our $iptablesw; # True of iptables supports the -w option our $tc; # Path to tc our $ip; # Path to ip our $shell; # Type of shell that processed the params file use constant { BASH => 1, OLDBASH => 2, ASH => 3 }; use constant { MIN_VERBOSITY => -1, MAX_VERBOSITY => 2 , F_IPV4 => 4, F_IPV6 => 6, }; use constant { PARMSMODIFIED => 1, USEDCALLER => 2 }; our %validlevels; # Valid log levels. # # Deprecated options with their default values # our %deprecated = ( LEGACY_RESTART => 'no' ); # # Deprecated options that are eliminated via update # our %converted = ( LEGACY_RESTART => 1 ); # # Eliminated options # our %eliminated = ( LOGRATE => 1, LOGBURST => 1, EXPORTPARAMS => 1, LEGACY_FASTSTART => 1, IPSECFILE => 1, WIDE_TC_MARKS => 1, HIGH_ROUTE_MARKS => 1, BLACKLISTNEWONLY => 1, ); # # Variables involved in ?IF, ?ELSE ?ENDIF processing # our $omitting; our @ifstack; our $ifstack; # # Entries on the ifstack are a 4-tuple: # # [0] - Keyword (IF, ELSEIF, ELSE or ENDIF) # [1] - True if the outermost IF evaluated to false # [2] - True if the the last unterminated IF evaluated to false # # From .shorewallrc # our ( %shorewallrc, %shorewallrc1 ); # # read_a_line options # use constant { PLAIN_READ => 0, # No read_a_line options EMBEDDED_ENABLED => 1, # Look for embedded Shell and Perl EXPAND_VARIABLES => 2, # Expand Shell variables STRIP_COMMENTS => 4, # Remove comments SUPPRESS_WHITESPACE => 8, # Ignore blank lines CHECK_GUNK => 16, # Look for unprintable characters CONFIG_CONTINUATION => 32, # Suppress leading whitespace if # continued line ends in ',' or ':' DO_INCLUDE => 64, # Look for INCLUDE DO_SECTION => 128, # Look for [?]SECTION
NORMAL_READ => -1 # All options }; our %variables; # Symbol table for expanding shell variables our $section_function; #Function Reference for handling ?section sub process_shorewallrc($$); sub add_variables( \% ); # # Rather than initializing globals in an INIT block or during declaration, # we initialize them in a function. This is done for two reasons: # # 1. Proper initialization depends on the address family which isn't # known until the compiler has started. # # 2. The compiler can run multiple times in the same process so it has to be # able to re-initialize its dependent modules' state. # sub initialize( $;$$) { ( $family, my ( $shorewallrc, $shorewallrc1 ) ) = @_; if ( $family == F_IPV4 ) { ( $product, $Product, $toolname, $toolNAME ) = qw( shorewall Shorewall iptables IPTABLES ); } else { ( $product, $Product, $toolname, $toolNAME ) = qw( shorewall6 Shorewall6 ip6tables IP6TABLES ); } $verbosity = 0; # Verbosity setting. -1 = silent, 0 = almost silent, 1 = major progress messages only, 2 = all progress messages (very noisy) $log = undef; # File reference for log file $log_verbosity = -1; # Verbosity of log. $timestamp = ''; # If true, we are to timestamp each progress message $script = 0; # Script (output) file Handle Reference $script_enabled = 0; # Writing to output file is disabled initially $lastlineblank = 0; # Avoid extra blank lines in the output $indent1 = ''; # Current indentation tabs $indent2 = ''; # Current indentation spaces $indent = ''; # Current total indentation ( $dir, $file ) = ('',''); # Script's Directory and Filename $tempfile = ''; # Temporary File Name $sillyname = $sillyname1 = ''; # Temporary ipchains $omitting = 0; $ifstack = 0; @ifstack = (); $embedded = 0; $directive_callback = 0; # # Contents of last COMMENT line. # $comment = ''; $warningcount = 0; # # Misc Globals # %globals = ( SHAREDIRPL => '' , CONFIGDIR => '', # Compile-time configuration directory (location of $product.conf) ESTABLISHED_DISPOSITION => 'ACCEPT', LOGPARMS => '', TC_SCRIPT => '', EXPORT => 0, KLUDGEFREE => '', VERSION => "5.0.4", CAPVERSION => 50004 , BLACKLIST_LOG_TAG => '', RELATED_LOG_TAG => '', MACLIST_LOG_TAG => '', TCP_FLAGS_LOG_TAG => '', SMURF_LOG_TAG => '', RPFILTER_LOG_TAG => '', INVALID_LOG_TAG => '', UNTRACKED_LOG_TAG => '', ); # # From shorewall.conf file # %config = ( STARTUP_ENABLED => undef, VERBOSITY => undef, # # Logging # LOGFILE => undef, LOGFORMAT => undef, LOGTAGONLY => undef, LOGLIMIT => undef, LOGRATE => undef, LOGBURST => undef, LOGALLNEW => undef, BLACKLIST_LOG_LEVEL => undef, RELATED_LOG_LEVEL => undef, RFC1918_LOG_LEVEL => undef, MACLIST_LOG_LEVEL => undef, TCP_FLAGS_LOG_LEVEL => undef, SMURF_LOG_LEVEL => undef, LOG_MARTIANS => undef, LOG_VERBOSITY => undef, STARTUP_LOG => undef, SFILTER_LOG_LEVEL => undef, RPFILTER_LOG_LEVEL => undef, INVALID_LOG_LEVEL => undef, UNTRACKED_LOG_LEVEL => undef, LOG_BACKEND => undef, # # Location of Files # IP => undef, TC => undef, IPSET => undef, PERL => undef, PATH => undef, SHOREWALL_SHELL => undef, SUBSYSLOCK => undef, MODULESDIR => undef, CONFIG_PATH => undef, RESTOREFILE => undef, LOCKFILE => undef, GEOIPDIR => undef, NFACCT => undef, # # Default Actions/Macros # DROP_DEFAULT => undef, REJECT_DEFAULT => undef, ACCEPT_DEFAULT => undef, QUEUE_DEFAULT => undef, NFQUEUE_DEFAULT => undef, # # RSH/RCP Commands # RSH_COMMAND => undef, RCP_COMMAND => undef, # # Firewall Options # BRIDGING => undef, IP_FORWARDING => undef, ADD_IP_ALIASES => undef, ADD_SNAT_ALIASES => undef, RETAIN_ALIASES => undef, TC_ENABLED => undef, TC_EXPERT => undef, TC_PRIOMAP => undef, CLEAR_TC => undef, MARK_IN_FORWARD_CHAIN => undef, CLAMPMSS => undef, ROUTE_FILTER => undef, DETECT_DNAT_IPADDRS => undef, MUTEX_TIMEOUT => undef, ADMINISABSENTMINDED => undef, BLACKLIST => undef, BLACKLISTNEWONLY => undef, DELAYBLACKLISTLOAD => undef, MODULE_SUFFIX => undef, DISABLE_IPV6 => undef, DYNAMIC_ZONES => undef, PKTTYPE=> undef, MACLIST_TABLE => undef, MACLIST_TTL => undef, SAVE_IPSETS => undef, SAVE_ARPTABLES => undef, MAPOLDACTIONS => undef, FASTACCEPT => undef, IMPLICIT_CONTINUE => undef, IPSET_WARNINGS => undef, HIGH_ROUTE_MARKS => undef, USE_ACTIONS=> undef, OPTIMIZE => undef, SHOREWALL_COMPILER => undef, EXPAND_POLICIES => undef, KEEP_RT_TABLES => undef, DELETE_THEN_ADD => undef, MULTICAST => undef, DONT_LOAD => '', AUTOCOMMENT => undef , MANGLE_ENABLED => undef , RFC1918_STRICT => undef , NULL_ROUTE_RFC1918 => undef , USE_DEFAULT_RT => undef , RESTORE_DEFAULT_ROUTE => undef , FAST_STOP => undef , AUTOMAKE => undef , WIDE_TC_MARKS => undef, TRACK_PROVIDERS => undef, ZONE2ZONE => undef, ACCOUNTING => undef, OPTIMIZE_ACCOUNTING => undef, ACCOUNTING_TABLE => undef, DYNAMIC_BLACKLIST => undef, LOAD_HELPERS_ONLY => undef, REQUIRE_INTERFACE => undef, FORWARD_CLEAR_MARK => undef, COMPLETE => undef, EXPORTMODULES => undef, USE_PHYSICAL_NAMES => undef, HELPERS => undef, AUTOHELPERS => undef, RESTORE_ROUTEMARKS => undef, IGNOREUNKNOWNVARIABLES => undef, WARNOLDCAPVERSION => undef, DEFER_DNS_RESOLUTION => undef, USE_RT_NAMES => undef, CHAIN_SCRIPTS => undef, TRACK_RULES => undef, REJECT_ACTION => undef, INLINE_MATCHES => undef, BASIC_FILTERS => undef, WORKAROUNDS => undef , LEGACY_RESTART => undef , RESTART => undef , # # Packet Disposition # MACLIST_DISPOSITION => undef, TCP_FLAGS_DISPOSITION => undef, BLACKLIST_DISPOSITION => undef, SMURF_DISPOSITION => undef, SFILTER_DISPOSITION => undef, RPFILTER_DISPOSITION => undef, RELATED_DISPOSITION => undef, INVALID_DISPOSITION => undef, UNTRACKED_DISPOSITION => undef, # # Mark Geometry # TC_BITS => undef, PROVIDER_BITS => undef, PROVIDER_OFFSET => undef, MASK_BITS => undef, ZONE_BITS => undef, ); # # Valid log levels # %validlevels = ( DEBUG => 7, INFO => 6, NOTICE => 5, WARNING => 4, WARN => 4, ERR => 3, ERROR => 3, CRIT => 2, ALERT => 1, EMERG => 0, PANIC => 0, NONE => '', NFLOG => 'NFLOG', LOGMARK => 'LOGMARK', ); # # From parsing the capabilities file or capabilities detection # %capabilities = ( NAT_ENABLED => undef, MANGLE_ENABLED => undef, MULTIPORT => undef, XMULTIPORT => undef, EMULTIPORT => undef, CONNTRACK_MATCH => undef, NEW_CONNTRACK_MATCH => undef, OLD_CONNTRACK_MATCH => undef, USEPKTTYPE => undef, POLICY_MATCH => undef, PHYSDEV_MATCH => undef, PHYSDEV_BRIDGE => undef, LENGTH_MATCH => undef, IPRANGE_MATCH => undef, RECENT_MATCH => undef, OWNER_MATCH => undef, OWNER_NAME_MATCH => undef, IPSET_MATCH => undef, OLD_IPSET_MATCH => undef, IPSET_MATCH_NOMATCH => undef, IPSET_MATCH_COUNTERS => undef, IPSET_V5 => undef, CONNMARK => undef, XCONNMARK => undef, CONNMARK_MATCH => undef, XCONNMARK_MATCH => undef, RAW_TABLE => undef, RAWPOST_TABLE => undef, IPP2P_MATCH => undef, OLD_IPP2P_MATCH => undef, CLASSIFY_TARGET => undef, ENHANCED_REJECT => undef, KLUDGEFREE => undef, MARK => undef, XMARK => undef, EXMARK => undef, MANGLE_FORWARD => undef, COMMENTS => undef, ADDRTYPE => undef, TCPMSS_MATCH => undef, HASHLIMIT_MATCH => undef, NFQUEUE_TARGET => undef, REALM_MATCH => undef, HELPER_MATCH => undef, CONNLIMIT_MATCH => undef, TIME_MATCH => undef, GOTO_TARGET => undef, LOG_TARGET => 1, # Assume that we have it. ULOG_TARGET => undef, NFLOG_TARGET => undef, LOGMARK_TARGET => undef, IPMARK_TARGET => undef, TPROXY_TARGET => undef, PERSISTENT_SNAT => undef, OLD_HL_MATCH => undef, FLOW_FILTER => undef, FWMARK_RT_MASK => undef, MARK_ANYWHERE => undef, HEADER_MATCH => undef, ACCOUNT_TARGET => undef, AUDIT_TARGET => undef, CONDITION_MATCH => undef, IPTABLES_S => undef, BASIC_FILTER => undef, BASIC_EMATCH => undef, CT_TARGET => undef, STATISTIC_MATCH => undef, IMQ_TARGET => undef, DSCP_MATCH => undef, DSCP_TARGET => undef, GEOIP_MATCH => undef, RPFILTER_MATCH => undef, NFACCT_MATCH => undef, CHECKSUM_TARGET => undef, ARPTABLESJF => undef, MASQUERADE_TGT => undef, UDPLITEREDIRECT => undef, NEW_TOS_MATCH => undef, REAP_OPTION => undef, TARPIT_TARGET => undef, IFACE_MATCH => undef, TCPMSS_TARGET => undef, WAIT_OPTION => undef, AMANDA_HELPER => undef, FTP_HELPER => undef, FTP0_HELPER => undef, H323_HELPER => undef, IRC_HELPER => undef, IRC0_HELPER => undef, NETBIOS_NS_HELPER => undef, PPTP_HELPER => undef, SANE_HELPER => undef, SANE0_HELPER => undef, SIP_HELPER => undef, SIP0_HELPER => undef, SNMP_HELPER => undef, TFTP_HELPER => undef, TFTP0_HELPER => undef, CAPVERSION => undef, LOG_OPTIONS => 1, KERNELVERSION => undef, ); # # Directories to search for configuration files # @config_path = (); # # Stash away file references here when we encounter INCLUDE # @includestack = (); # # Allow nested opens # @openstack = (); $currentline = ''; # Line image $currentfile = undef; # File handle reference $currentfilename = ''; # File NAME $currentlinenumber = 0; # Line number $first_entry = 0; # Message to output or function to call on first non-blank file entry $max_format = 1; $comments_allowed = 0; $nocomment = 0; $shorewall_dir = ''; #Shorewall Directory $debug = 0; $confess = 0; %params = (); %compiler_params = (); %actparms = ( 0 => 0, loglevel => '', logtag => '', chain => '', disposition => '', caller => '' ); $parmsmodified = 0; $usedcaller = 0; %helpers_enabled = ( amanda => 1, ftp => 1, 'ftp-0' => 1, h323 => 1, irc => 1, 'irc-0' => 1, 'netbios-ns' => 1, pptp => 1, sane => 1, 'sane-0' => 1, sip => 1, 'sip-0' => 1, snmp => 1, tftp => 1, 'tftp-0' => 1, ); %helpers_map = ( amanda => 'AMANDA_HELPER', ftp => 'FTP_HELPER', irc => 'IRC_HELPER', 'netbios-ns' => 'NETBIOS_NS_HELPER', pptp => 'PPTP_HELPER', 'Q.931' => 'H323_HELPER', RAS => 'H323_HELPER', sane => 'SANE_HELPER', sip => 'SIP_HELPER', snmp => 'SNMP_HELPER', tftp => 'TFTP_HELPER', ); %helpers_aliases = ( amanda => 'amanda', ftp => 'ftp', irc => 'irc', 'netbios-ns' => 'netbios-ns', pptp => 'pptp', 'Q.931' => 'Q.931', RAS => 'RAS', sane => 'sane', sip => 'sip', snmp => 'snmp', tftp => 'tftp', ); %shorewallrc = ( SHAREDIR => '/usr/share/', CONFDIR => '/etc/', ); %variables = %ENV; # # If we are compiling for export, process the shorewallrc from the remote system # if ( $shorewallrc1 ) { process_shorewallrc( $shorewallrc1, $family == F_IPV4 ? 'shorewall-lite' : 'shorewall6-lite' ); %shorewallrc1 = %shorewallrc; %shorewallrc = ( SHAREDIR => '/usr/share/', CONFDIR => '/etc/', ); } # # Process the global shorewallrc file # # Note: The build file executes this function passing only the protocol family # process_shorewallrc( $shorewallrc, $family == F_IPV4 ? 'shorewall' : 'shorewall6' ) if defined $shorewallrc; $globals{SHAREDIRPL} = "$shorewallrc{SHAREDIR}/shorewall/"; $globals{SAVED_IPSETS} = []; if ( $family == F_IPV4 ) { $globals{SHAREDIR} = "$shorewallrc{SHAREDIR}/shorewall"; $globals{PRODUCT} = 'shorewall'; $config{IPTABLES} = undef; $config{ARPTABLES} = undef; $validlevels{ULOG} = 'ULOG'; } else { $globals{SHAREDIR} = "$shorewallrc{SHAREDIR}/shorewall6"; $globals{PRODUCT} = 'shorewall6'; $config{IP6TABLES} = undef; delete $config{ARPTABLES}; } %shorewallrc1 = %shorewallrc unless $shorewallrc1; add_variables %shorewallrc1; } my @abbr = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ); # # Create 'currentlineinfo' # sub currentlineinfo() { my $linenumber = $currentlinenumber || 1; if ( $currentfile ) { my $lineinfo = " $currentfilename "; if ( $linenumber eq 'EOF' ) { $lineinfo .= '(EOF)' } else { $lineinfo .= "(line $linenumber)"; } # # Unwind the current include stack # for ( my $i = @includestack - 1; $i >= 0; $i-- ) { my $info = $includestack[$i]; $linenumber = $info->[2] || 1; $lineinfo .= "\n from $info->[1] (line $linenumber)"; } # # Now unwind the open stack; each element is an include stack # for ( my $i = @openstack - 1; $i >= 0; $i-- ) { my $istack = $openstack[$i]; for ( my $j = ( @$istack - 1 ); $j >= 0; $j-- ) { my $info = $istack->[$j]; $linenumber = $info->[2] || 1; $lineinfo .= "\n from $info->[1] (line $linenumber)"; } } $lineinfo; } else { ''; } } sub shortlineinfo( $ ) { if ( $config{TRACK_RULES} ) { if ( $currentfile ) { my $comment = '@@@ '. join( ':', $currentfilename, $currentlinenumber ) . ' @@@'; $comment = '@@@ ' . join( ':' , basename($currentfilename), $currentlinenumber) . ' @@@' if length $comment > 255; $comment = '@@@ Filename Too Long @@@' if length $comment > 255; $comment; } else { # # Alternate lineinfo may have been passed # $_[0] || '' } } } sub handle_first_entry(); # # Issue a Warning Message # sub warning_message { my $currentlineinfo = currentlineinfo; our @localtime; handle_first_entry if $first_entry; $| = 1; #Reset output buffering (flush any partially filled buffers). if ( $log ) { @localtime = localtime; printf $log '%s %2d %02d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; } if ( $confess ) { print STDERR longmess( " WARNING: @_$currentlineinfo" ); print $log longmess( " WARNING: @_$currentlineinfo\n" ) if $log; } else { print STDERR " WARNING: @_$currentlineinfo\n"; print $log " WARNING: @_$currentlineinfo\n" if $log; } $| = 0; #Re-allow output buffering } # # Q[uie]t version of system(). Returns true for success # sub qt( $ ) { if ( $debug ) { print "SYS----> @_\n"; system( "@_ 2>&1 < /dev/null" ) == 0; } else { system( "@_ > /dev/null 2>&1 < /dev/null" ) == 0; } } sub qt0( $ ) { if ( $debug ) { print "SYS----> @_\n"; system( "@_ 2>&1 < /dev/null" ); } else { system( "@_ > /dev/null 2>&1 < /dev/null" ); } } sub qt1( $ ) { 1 while qt0( "@_" ) == 4; $? == 0; } # # Delete the test chains # sub cleanup_iptables() { qt1( "$iptables $iptablesw -F $sillyname" ); qt1( "$iptables $iptablesw -X $sillyname" ); qt1( "$iptables $iptablesw -F $sillyname1" ); qt1( "$iptables $iptablesw -X $sillyname1" ); if ( $capabilities{MANGLE_ENABLED} ) { qt1( "$iptables $iptablesw -t mangle -F $sillyname" ); qt1( "$iptables $iptablesw -t mangle -X $sillyname" ); } if ( $capabilities{NAT_ENABLED} ) { qt1( "$iptables $iptablesw -t nat -F $sillyname" ); qt1( "$iptables $iptablesw -t nat -X $sillyname" ); } if ( $capabilities{RAW_TABLE} ) { qt1( "$iptables $iptablesw -t raw -F $sillyname" ); qt1( "$iptables $iptablesw -t raw -X $sillyname" ); } $sillyname = $sillyname1 = ''; } # # Clean up after the compiler exits # sub cleanup() { # # Close files first in case we're running under Cygwin # close $script, $script = undef if $script; close $perlscript, $perlscript = undef if $perlscript; close $log, $log = undef if $log; if ( $currentfile ) { # # We have a current input file; close it # close $currentfile; # # Unwind the current include stack # for ( my $i = @includestack - 1; $i >= 0; $i-- ) { my $info = $includestack[$i]; close $info->[0]; } # # Now unwind the open stack; each element is an include stack # for ( my $i = @openstack - 1; $i >= 0; $i-- ) { my $istack = $openstack[$i]; for ( my $j = ( @$istack - 1 ); $j >= 0; $j-- ) { my $info = $istack->[$j][0]; close $info if $info; } } } # # Unlink temporary files # unlink ( $tempfile ), $tempfile = undef if $tempfile; unlink ( $perlscriptname ), $perlscriptname = undef if $perlscriptname; unlink ( @tempfiles ), @tempfiles = () if @tempfiles; # # Delete temporary chains # cleanup_iptables if $sillyname; } # # Issue fatal error message and die # sub fatal_error { my $currentlineinfo = currentlineinfo; handle_first_entry if $first_entry; $| = 1; #Reset output buffering (flush any partially filled buffers). if ( $log ) { our @localtime = localtime; printf $log '%s %2d %02d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; if ( $confess ) { print $log longmess( " ERROR: @_$currentlineinfo\n" ); } else { print $log " ERROR: @_$currentlineinfo\n"; } close $log; $log = undef; } cleanup; if ( $embedded ) { confess "@_$currentlineinfo" if $confess; die "@_$currentlineinfo\n"; } else { confess " ERROR: @_$currentlineinfo" if $confess; die " ERROR: @_$currentlineinfo\n"; } } sub fatal_error1 { handle_first_entry if $first_entry; $| = 1; if ( $log ) { our @localtime = localtime; printf $log '%s %2d %02d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; if ( $debug ) { print $log longmess( " ERROR: @_\n" ); } else { print $log " ERROR: @_\n"; } close $log; $log = undef; } cleanup; confess " ERROR: @_" if $debug; die " ERROR: @_\n"; } # # C/C++-like assertion checker -- the optional arguments are not used but will # appear in the stack trace # sub assert( $;@ ) { unless ( $_[0] ) { my @caller0 = caller 0; # Where assert() was called my @caller1 = caller 1; # Who called assert() $confess = 1; fatal_error "Internal error in $caller1[3] at $caller0[1] line $caller0[2]"; } } # # Convert value to decimal number # sub numeric_value ( $ ) { my $mark = lc $_[0]; my $negative = ( $mark =~ s/^-// ); return undef unless $mark =~ /^(0x[a-f0-9]+|0[0-7]*|[1-9]\d*)$/; no warnings; $mark = ( $mark =~ /^0/ ? oct $mark : $mark ); use warnings; $negative ? - $mark : $mark; } sub numeric_value1 ( $ ) { no warnings; my $val = numeric_value $_[0]; fatal_error "Invalid Number ($_[0])" unless defined $val; $val; use warnings; } sub hex_value( $ ) { my $val = lc $_[0]; return undef unless $val =~ /^[a-fA-F0-9]+$/; no warnings; oct '0x' . $val; use warnings; } # # Strip off superfluous leading zeros from a hex number # sub normalize_hex( $ ) { my $val = lc shift; $val =~ s/^0// while $val =~ /^0/ && length $val > 1; $val; } # # Return the argument expressed in Hex # sub in_hex( $ ) { my $value = $_[0]; $value =~ /^0x/ ? $value : sprintf '0x%x', $_[0]; } sub in_hex2( $ ) { sprintf '0x%02x', $_[0]; } sub in_hex3( $ ) { sprintf '%03x', $_[0]; } sub in_hex4( $ ) { sprintf '0x%04x', $_[0]; } sub in_hex8( $ ) { sprintf '0x%08x', $_[0]; } sub in_hexp( $ ) { sprintf '%x', $_[0]; } # # Write the arguments to the script file (if any) with the current indentation. # # Replaces leading spaces with tabs as appropriate and suppresses consecutive blank lines. # sub emit { assert( $script_enabled ); if ( $script || $debug ) { # # 'compile' as opposed to 'check' # for ( @_ ) { unless ( /^\s*$/ ) { my $line = $_; # This copy is necessary because the actual arguments are almost always read-only. $line =~ s/^\n// if $lastlineblank; $line =~ s/^/$indent/gm if $indent; $line =~ s/ /\t/gm; print $script "$line\n" if $script; $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); if ( $debug ) { $line =~ s/^\n//; $line =~ s/\n/\nGS-----> /g; print "GS-----> $line\n"; } } else { unless ( $lastlineblank ) { print $script "\n" if $script; print "GS-----> \n" if $debug; } $lastlineblank = 1; } } } } # # Version of emit() that writes to standard out unconditionally # sub emitstd { for ( @_ ) { unless ( /^\s*$/ ) { my $line = $_; # This copy is necessary because the actual arguments are almost always read-only. $line =~ s/^\n// if $lastlineblank; $line =~ s/^/$indent/gm if $indent; $line =~ s/ /\t/gm; print "$line\n"; $lastlineblank = ( substr( $line, -1, 1 ) eq "\n" ); } else { print "\n" unless $lastlineblank; $lastlineblank = 1; } } } # # Write passed message to the script with newline but no indentation. # sub emit_unindented( $;$ ) { assert( $script_enabled ); print $script $_[1] ? "$_[0]" : "$_[0]\n" if $script; } # # Write a progress_message2 command with surrounding blank lines to the output file. # sub save_progress_message( $ ) { emit "\nprogress_message2 @_\n" if $script; } # # Write a progress_message command to the output file. # sub save_progress_message_short( $ ) { emit "progress_message $_[0]" if $script; } # # Set $timestamp # sub set_timestamp( $ ) { $timestamp = shift; } # # Set $verbosity # sub set_verbosity( $ ) { $verbosity = shift; } # # Set $log and $log_verbosity # sub set_log ( $$ ) { my ( $l, $v ) = @_; if ( defined $v ) { my $value = numeric_value( $v ); fatal_error "Invalid Log Verbosity ( $v )" unless defined($value) && ( $value >= -1 ) && ( $value <= 2); $log_verbosity = $value; } if ( $l && $log_verbosity >= 0 ) { unless ( open $log , '>>' , $l ) { $log = undef; fatal_error "Unable to open STARTUP_LOG ($l) for writing: $!"; } } else { $log_verbosity = -1; } } sub close_log() { close $log, $log = undef if $log; } # # Set $command, $doing and $done # sub set_command( $$$ ) { ($command, $doing, $done) = @_; } # # Print the current TOD to STDOUT. # sub get_localtime() { our @localtime = localtime; printf '%02d:%02d:%02d ', @localtime[2,1,0]; } # # Write a message if $verbosity >= 2. # sub progress_message { my $havelocaltime = 0; if ( $verbosity > 1 || $log_verbosity > 1 ) { my $line = "@_"; my $leading = $line =~ /^(\s+)/ ? $1 : ''; $line =~ s/\s+/ /g; if ( $verbosity > 1 ) { get_localtime, $havelocaltime = 1 if $timestamp; # # We use this function to display messages containing raw config file images which may contains tabs (including multiple tabs in succession). # The following makes such messages look more readable and uniform # print "${leading}${line}\n"; } if ( $log_verbosity > 1 ) { our @localtime; @localtime = localtime unless $havelocaltime; printf $log '%s %2d %2d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; print $log "${leading}${line}\n"; } } } sub progress_message_nocompress { my $havelocaltime = 0; if ( $verbosity > 1 ) { get_localtime, $havelocaltime = 1 if $timestamp; print "@_\n"; } if ( $log_verbosity > 1 ) { our @localtime; @localtime = localtime unless $havelocaltime; printf $log '%s %2d %2d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; print $log "@_\n"; } } # # Write a message if $verbosity >= 1 # sub progress_message2 { my $havelocaltime = 0; if ( $verbosity > 0 ) { get_localtime, $havelocaltime = 1 if $timestamp; print "@_\n"; } if ( $log_verbosity > 0 ) { our @localtime; @localtime = localtime unless $havelocaltime; printf $log '%s %2d %2d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; print $log "@_\n"; } } # # Write a message if $verbosity >= 0 # sub progress_message3 { my $havelocaltime = 0; if ( $verbosity >= 0 ) { get_localtime, $havelocaltime = 1 if $timestamp; print "@_\n"; } if ( $log_verbosity >= 0 ) { our @localtime; @localtime = localtime unless $havelocaltime; printf $log '%s %2d %2d:%02d:%02d ', $abbr[$localtime[4]], @localtime[3,2,1,0]; print $log "@_\n"; } } # # Push/Pop Indent # sub push_indent(;$) { my $times = shift || 1; while ( $times-- ) { if ( $indent2 ) { $indent2 = ''; $indent = $indent1 = $indent1 . "\t"; } else { $indent2 = ' '; $indent = $indent1 . $indent2; } } } sub pop_indent(;$) { my $times = shift || 1; while ( $times-- ) { if ( $indent2 ) { $indent2 = ''; $indent = $indent1; } else { $indent1 = substr( $indent1 , 0, -1 ); $indent2 = ' '; $indent = $indent1 . $indent2; } } } # # Create the temporary script file -- the passed file name is the name of the final file. # We create a temporary file in the same directory so that we can use rename to finalize it. # sub create_temp_script( $$ ) { my ( $scriptfile, $export ) = @_; my $suffix; if ( $scriptfile eq '-' ) { $verbosity = -1; $script = undef; open( $script, '>&STDOUT' ) or fatal_error "Open of STDOUT failed"; $file = '-'; return 1; } eval { ( $file, $dir, $suffix ) = fileparse( $scriptfile ); }; cleanup, die if $@; fatal_error "$dir is a Symbolic Link" if -l $dir; fatal_error "Directory $dir does not exist" unless -d _; fatal_error "Directory $dir is not writable" unless -w _; fatal_error "$scriptfile is a Symbolic Link" if -l $scriptfile; fatal_error "$scriptfile is a Directory" if -d _; fatal_error "$scriptfile exists and is not a compiled script" if -e _ && ! -x _; fatal_error "An exported \u$globals{PRODUCT} compiled script may not be named '$globals{PRODUCT}'" if $export && "$file" eq $globals{PRODUCT} && $suffix eq ''; eval { $dir = abs_path $dir unless $dir =~ m|^/|; # Work around http://rt.cpan.org/Public/Bug/Display.html?id=13851 ( $script, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); }; fatal_error "Unable to create temporary file in directory $dir" if $@; $file = "$file.$suffix" if $suffix; $dir .= '/' unless substr( $dir, -1, 1 ) eq '/'; $file = $dir . $file; } # Generate the SHA1 digest of the (incomplete) script # sub generate_sha1() { my $data = `cat $tempfile`; sha1_hex $data; } # # Finalize the script file # sub finalize_script( $ ) { my $export = $_[0]; close $script; $script = 0; if ( $file ne '-' ) { my $sha1sum = generate_sha1; my $sha1sum1 = join( '-', 'sha-lh', substr( $sha1sum, 0, 20 ) ); my $sha1sum2 = join( '-', 'sha-rh', substr( $sha1sum, -20 ) ); @ARGV = ( $tempfile ); $^I = ''; while ( <> ) { s/g_sha1sum1=/g_sha1sum1=$sha1sum1/; s/g_sha1sum2=/g_sha1sum2=$sha1sum2/; print; } rename $tempfile, $file or fatal_error "Cannot Rename $tempfile to $file: $!"; chmod 0700, $file or fatal_error "Cannot secure $file for execute access"; progress_message3 "Shorewall configuration compiled to $file" unless $export; } } # # Create the temporary aux config file. # sub create_temp_aux_config() { eval { ( $script, $tempfile ) = tempfile ( 'tempfileXXXX' , DIR => $dir ); }; cleanup, die if $@; } # # Finalize the aux config file. # sub finalize_aux_config() { close $script; $script = 0; rename $tempfile, "$file.conf" or fatal_error "Cannot Rename $tempfile to $file.conf: $!"; progress_message3 "Shorewall configuration compiled to $file"; } # # Enable writes to the script file # sub enable_script() { $script_enabled = 1; } # # Disable writes to the script file # sub disable_script() { $script_enabled = 0; } # # Set $config{CONFIG_PATH} # sub set_config_path( $ ) { $config{CONFIG_PATH} = shift; } # # Set $debug and $confess # sub set_debug( $$ ) { $debug = shift; $confess = shift; $confess ||= $debug; } # # Search the CONFIG_PATH for the passed file # sub find_file($) { my ( $filename, $nosearch ) = @_; return $filename if $filename =~ '/'; for my $directory ( @config_path ) { my $file = "$directory$filename"; return $file if -f $file; } "$config_path[0]$filename"; } sub find_writable_file($) { my ( $filename, $nosearch ) = @_; return $filename if $filename =~ '/'; for my $directory ( @config_path ) { next if $directory =~ m|^$globals{SHAREDIR}/configfiles/?$| || $directory =~ m|^$shorewallrc{SHAREDIR}/doc/default-config/?$|; my $file = "$directory$filename"; return $file if -f $file && -w _; } "$config_path[0]$filename"; } # # Split a comma-separated list into a Perl array # sub split_list( $$;$ ) { my ($list, $type, $origlist ) = @_; fatal_error( "Invalid $type list (" . ( $origlist ? $origlist : $list ) . ')' ) if $list =~ /^,|,$|,,|!,|,!$/; split /,/, $list; } # # This version handles parenthetical list elements with embedded commas. It removes the parentheses # sub split_list1( $$;$ ) { my ($list, $type, $keepparens ) = @_; fatal_error "Invalid $type list ($list)" if $list =~ /^,|,$|,,|!,|,!$/; my @list1 = split /,/, $list; my @list2; my $element = ''; for ( @list1 ) { my $count; if ( ( $count = tr/(/(/ ) > 0 ) { fatal_error "Invalid $type list ($list)" if $element || $count > 1; s/\(// unless $keepparens; if ( ( $count = tr/)/)/ ) > 0 ) { fatal_error "Invalid $type list ($list)" if $count > 1; s/\)// unless $keepparens; push @list2 , $_; } else { $element = $_; } } elsif ( ( $count = tr/)/)/ ) > 0 ) { fatal_error "Invalid $type list ($list)" unless $element && $count == 1; s/\)// unless $keepparens; push @list2, join ',', $element, $_; $element = ''; } elsif ( $element ) { $element = join ',', $element , $_; } else { push @list2 , $_; } } @list2; } # # The next two functions split a list which contain arbitrarily deep paren nesting. # The first splits on ':' and the second on ','. # sub split_list2( $$ ) { my ($list, $type ) = @_; fatal_error "Invalid $type ($list)" if $list =~ /^:|::/; my @list1 = split /:/, $list; my @list2; my $element = ''; my $opencount = 0; for ( @list1 ) { my $count; if ( ( $count = tr/(/(/ ) > 0 ) { $opencount += $count; if ( $element eq '' ) { $element = $_; } else { $element = join( ':', $element, $_ ); } if ( ( $count = tr/)/)/ ) > 0 ) { if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Invalid $type ($list)" if $opencount < 0; } } } elsif ( ( $count = tr/)/)/ ) > 0 ) { fatal_error "Invalid $type ($list)" if $element eq ''; $element = join (':', $element, $_ ); if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Invalid $type ($list)" if $opencount < 0; } } elsif ( $element eq '' ) { push @list2 , $_; } else { $element = join ':', $element , $_; } } unless ( $opencount == 0 ) { fatal_error "Invalid $type ($list)"; } @list2; } sub split_list3( $$ ) { my ($list, $type ) = @_; # # We allow omitted arguments in action invocations. # $list =~ s/^,/-,/; $list =~ s/,$/,-/; $list =~ s/,,/,-,/g; my @list1 = split /,/, $list; my @list2; my $element = ''; my $opencount = 0; for ( @list1 ) { my $count; if ( ( $count = tr/(/(/ ) > 0 ) { $opencount += $count; if ( $element eq '' ) { $element = $_; } else { $element = join( ',', $element, $_ ); } if ( ( $count = tr/)/)/ ) > 0 ) { if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Invalid $type ($list)" if $opencount < 0; } } } elsif ( ( $count = tr/)/)/ ) > 0 ) { fatal_error "Invalid $type ($list)" if $element eq ''; $element = join (',', $element, $_ ); if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Invalid $type ($list)" if $opencount < 0; } } elsif ( $element eq '' ) { push @list2 , $_; } else { $element = join ',', $element , $_; } } unless ( $opencount == 0 ) { fatal_error "Invalid $type ($list)"; } @list2; } # # Splits the columns of a config file record # sub split_columns( $ ) { my ($list) = @_; return split ' ', $list unless $list =~ /\(/; my @list1 = split ' ', $list; my @list2; my $element = ''; my $opencount = 0; for ( @list1 ) { my $count; if ( ( $count = tr/(/(/ ) > 0 ) { $opencount += $count; if ( $element eq '' ) { $element = $_; } else { $element = join( ' ', $element, $_ ); } if ( ( $count = tr/)/)/ ) > 0 ) { if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Mismatched parentheses ($_)" if $opencount < 0; } } } elsif ( ( $count = tr/)/)/ ) > 0 ) { $element = join (' ', $element, $_ ); if ( ! ( $opencount -= $count ) ) { push @list2 , $element; $element = ''; } else { fatal_error "Mismatched parentheses ($_)" if $opencount < 0; } } elsif ( $element eq '' ) { push @list2 , $_; } else { $element = join ' ', $element , $_; } } unless ( $opencount == 0 ) { fatal_error "Mismatched parentheses ($list)"; } @list2; } # # Determine if a value has been supplied # sub supplied( $ ) { my $val = shift; defined $val && $val ne ''; } # # Pre-process a line from a configuration file. # ensure that it has an appropriate number of columns. # supply '-' in omitted trailing columns. # Handles all of the supported forms of column/pair specification # Handles segragating raw iptables input in INLINE rules # sub split_line2( $$;$$$ ) { my ( $description, $columnsref, $nopad, $maxcolumns, $inline ) = @_; my $inlinematches = $config{INLINE_MATCHES}; my ( $columns, $pairs, $rest ); my $currline = $currentline; unless ( defined $maxcolumns ) { my @maxcolumns = ( keys %$columnsref ); $maxcolumns = @maxcolumns; } $inline_matches = ''; # # First, see if there are double semicolons on the line; what follows will be raw iptables input # if ( $inline ) { ( $columns, $pairs, $rest ) = split ';;', $currline; if ( defined $pairs ) { fatal_error "Only one set of double semicolons (';;') allowed on a line" if defined $rest; $currline = $columns; $inline_matches = $pairs; # # Don't look for matches below # $inline = $inlinematches = ''; } } # # Next, see if there is a semicolon on the line; what follows will be column/value pairs or raw iptables input # ( $columns, $pairs, $rest ) = split( ';', $currline ); if ( supplied $pairs ) { # # Found it -- be sure there wasn't more than one. # fatal_error "Only one semicolon (';') allowed on a line" if defined $rest; if ( $inlinematches ) { fatal_error "The $description does not support inline matches (INLINE_MATCHES=Yes)" unless $inline; $inline_matches = $pairs; if ( $columns =~ /^(\s*|.*[^&@%]){(.*)}\s*$/ ) { # # Pairs are enclosed in curly brackets. # $columns = $1; $pairs = $2; } else { $pairs = ''; } } elsif ( $inline ) { # # This file supports INLINE or IPTABLES # if ( $currline =~ /^\s*INLINE(?:\(.*\)(:.*)?|:.*)?\s/ || $currline =~ /^\s*IP6?TABLES(?:\(.*\)|:.*)?\s/ ) { $inline_matches = $pairs; if ( $columns =~ /^(\s*|.*[^&@%]){(.*)}\s*$/ ) { # # Pairs are enclosed in curly brackets. # $columns = $1; $pairs = $2; } else { warning_message "This entry needs to be changed before INLINE_MATCHES can be set to Yes" if $checkinline; $pairs = ''; } } } elsif ( $checkinline ) { warning_message "This entry needs to be changed before INLINE_MATCHES can be set to Yes"; } } elsif ( $currline =~ /^(\s*|.*[^&@%]){(.*)}$/ ) { # # Pairs are enclosed in curly brackets. # $columns = $1; $pairs = $2; } else { $pairs = ''; } unless ( $currline =~ /^\s*IP6?TABLES\(.*\)/ ) { fatal_error "Shorewall Configuration file entries may not contain double quotes, single back quotes or backslashes" if $columns =~ /["`\\]/; } fatal_error "Non-ASCII gunk in file" if $columns =~ /[^\s[:print:]]/; my @line = split_columns( $columns ); $nopad = {} unless $nopad; my $first = supplied $line[0] ? $line[0] : '-'; my $npcolumns = $nopad->{$first}; if ( defined $npcolumns ) { fatal_error "Invalid $first entry" if $npcolumns && @line != $npcolumns; return @line } fatal_error "Shorewall Configuration file entries may not contain single quotes" if $currline =~ /'/; my $line = @line; fatal_error "Invalid $description entry (too many columns)" if $line > $maxcolumns; $line-- while $line > 0 && $line[$line-1] eq '-'; push @line, '-' while @line < $maxcolumns; if ( supplied $pairs ) { $pairs =~ s/^\s*//; $pairs =~ s/\s*$//; my @pairs = split( /,?\s+/, $pairs ); for ( @pairs ) { fatal_error "Invalid column/value pair ($_)" unless /^(\w+)(?:=>?|:)(.+)$/; my ( $column, $value ) = ( lc( $1 ), $2 ); fatal_error "Unknown column ($1)" unless exists $columnsref->{$column}; $column = $columnsref->{$column}; fatal_error "Non-ASCII gunk in file" if $columns =~ /[^\s[:print:]]/; $value = $1 if $value =~ /^"([^"]+)"$/; fatal_error "Column values may not contain embedded double quotes, single back quotes or backslashes" if $columns =~ /["`\\]/; fatal_error "Non-ASCII gunk in the value of the $column column" if $columns =~ /[^\s[:print:]]/; $line[$column] = $value; } } @line; } sub split_line1( $$;$$ ) { &split_line2( @_, undef ); } sub split_line($$) { &split_line1( @_, {} ); } sub have_capability( $;$ ); # # Returns True if there is a current COMMENT or if COMMENTS are not available. # sub no_comment() { $comment ? 1 : ! have_capability( 'COMMENTS' ); } # # Clear the $comment variable and the comment stack # sub clear_comment() { $comment = ''; $nocomment = 0; } # # Set the current comment # sub set_comment( $ ) { ( $comment ) = @_; } # # Push and Pop comment stack # sub push_comment() { my $return = $comment; $comment = ''; $return; } sub pop_comment( $ ) { $comment = $_[0]; } # # Set $comment to the passed unless there is a current comment # sub macro_comment( $ ) { my $macro = $_[0]; $comment = $macro unless $comment || ! ( have_capability( 'COMMENTS' ) && $config{AUTOCOMMENT} ); } # # Set/clear $section_function # sub set_section_function( \& ) { $section_function = $_[0]; } sub clear_section_function() { $section_function = undef; } # # Open a file, setting $currentfile. Returns the file's absolute pathname if the file # exists, is non-empty and was successfully opened. Terminates with a fatal error # if the file exists, is non-empty, but the open fails. # sub do_open_file( $ ) { my $fname = $_[0]; open $currentfile, '<', $fname or fatal_error "Unable to open $fname: $!"; $currentlinenumber = 0; $ifstack = @ifstack; $currentfilename = $fname; } # # Arguments are: # # - file name # - Maximum value allowed in ?FORMAT directives # - ?COMMENT allowed in this file # - Ignore ?COMMENT in ths file # sub open_file( $;$$$$ ) { my ( $fname, $mf, $ca, $nc, $cf ) = @_; $fname = find_file $fname; assert( ! defined $currentfile ); if ( -f $fname && -s _ ) { $first_entry = 0; $file_format = supplied $cf ? $cf : 1; $max_format = supplied $mf ? $mf : 1; $comments_allowed = supplied $ca ? $ca : 0; $nocomment = $nc; do_open_file $fname;; } else { $ifstack = @ifstack; ''; } } # # Push open-specific globals onto the include stack # sub push_include() { push @includestack, [ $currentfile, $currentfilename, $currentlinenumber, $ifstack, $file_format, $max_format, $comment, $nocomment, $section_function ]; } # # Pop the include stack # sub pop_include() { my $arrayref = pop @includestack; unless ( $ifstack == @ifstack ) { my $lastref = $ifstack[-1]; $currentlinenumber = 'EOF'; fatal_error qq(Missing "?ENDIF" to match ?IF at line number $lastref->[2]) } if ( $arrayref ) { ( $currentfile, $currentfilename, $currentlinenumber, $ifstack, $file_format, $max_format, $comment, $nocomment, $section_function ) = @$arrayref; } else { $currentfile = undef; $currentlinenumber = 'EOF'; clear_comment; clear_section_function; } } # # This function is normally called below in read_a_line() when EOF is reached. Clients of the # module may also call the function to close the file before EOF # sub close_file() { if ( $currentfile ) { my $result = close $currentfile; pop_include; fatal_error "SHELL Script failed" unless $result; $first_entry = 0; } } # # Clear the current filename # sub clear_currentfilename() { $currentfilename = ''; } # # Process an ?IF, ?ELSIF, ?ELSE or ?END directive # # # Report an error or warning from process_compiler_directive() # sub directive_error( $$$ ) { $currentfilename = $_[1]; $currentlinenumber = $_[2]; fatal_error $_[0]; } sub directive_warning( $$$ ) { my ( $savefilename, $savelineno ) = ( $currentfilename, $currentlinenumber ); ( my $warning, $currentfilename, $currentlinenumber ) = @_; warning_message $warning; ( $currentfilename, $currentlinenumber ) = ( $savefilename, $savelineno ); } # # Add quotes to the passed value if the passed 'first part' has an odd number of quotes # Return an expression that concatenates $first, $val and $rest # sub join_parts( $$$ ) { my ( $first, $val, $rest ) = @_; $val = '' unless defined $val; $val = "'$val'" unless ( $val =~ /^-?\d+$/ || # Value is numeric ( ( ( $first =~ tr/"/"/ ) & 1 ) || # There are an odd number of double quotes preceding the value ( ( $first =~ tr/'/'/ ) & 1 ) ) ); # There are an odd number of single quotes preceding the value join( '', $first, $val, $rest ); } # # Evaluate an expression in an ?IF, ?ELSIF or ?SET directive # sub evaluate_expression( $$$ ) { my ( $expression , $filename , $linenumber ) = @_; my $val; my $count = 0; my $chain = $actparms{chain}; # $1 $2 $3 - $4 while ( $expression =~ m( ^(.*?) \$({)? (\d+|[a-zA-Z_]\w*) (?(2)}) (.*)$ )x ) { my ( $first, $var, $rest ) = ( $1, $3, $4); if ( $var =~ /^\d+$/ ) { fatal_error "Action parameters (\$$var) may only be referenced within the body of an action" unless $chain; $val = $var ? $actparms{$var} : $actparms{0}->{name}; } else { $val = ( exists $variables{$var} ? $variables{$var} : exists $capdesc{$var} ? have_capability( $var ) : '' ); } $expression = join_parts( $first, $val, $rest ); directive_error( "Variable Expansion Loop" , $filename, $linenumber ) if ++$count > 100; } if ( $chain ) { # $1 $2 $3 - $4 while ( $expression =~ m( ^(.*?) \@({)? (\d+|[a-zA-Z]\w*) (?(2)}) (.*)$ )x ) { my ( $first, $var, $rest ) = ( $1, $3, $4); $var = numeric_value( $var ) if $var =~ /^\d/; $val = $var ? $actparms{$var} : $chain; $usedcaller = USEDCALLER if $var eq 'caller'; $expression = join_parts( $first, $val, $rest ); directive_error( "Variable Expansion Loop" , $filename, $linenumber ) if ++$count > 100; } } # $1 $2 $3 - $4 while ( $expression =~ m( ^(.*?) __({)? (\w+) (?(2)}) (.*)$ )x ) { my ( $first, $cap, $rest ) = ( $1, $3, $4); if ( exists $capdesc{$cap} ) { $val = have_capability( $cap ); if ( defined $val ) { $val = "'$val'" unless $val =~ /^-?\d+$/; } else { $val = 0; } } elsif ( $cap =~ /^IPV([46])$/ ) { $val = ( $family == $1 ) || 0; } else { directive_error "Unknown capability ($cap)", $filename, $linenumber; } $expression = join( '', $first, $val, $rest ); } $expression =~ s/^\s*(.+)\s*$/$1/; print "EXPR=> $expression\n" if $debug; if ( $expression =~ /^\d+$/ ) { $val = $expression } else { # # Not a simple one-term expression -- compile it # $val = eval qq(package Shorewall::User;\nuse strict;\n# line $linenumber "$filename"\n$expression); unless ( $val ) { directive_error( "Couldn't parse expression ($expression): $@" , $filename, $linenumber ) if $@; $val = '' unless defined $val; } } $val; } # # Set callback # sub directive_callback( $ ) { $directive_callback = shift; } # # Each entry in @ifstack consists of a 4-tupple # # [0] = The keyword (IF,ELSIF or ELSE) # [1] = True if we were already omitting at the last IF directive # [2] = True if we have included any block of the current IF...ELSEIF....ELSEIF... sequence. # [3] = The line number of the directive # sub process_compiler_directive( $$$$ ) { my ( $omitting, $line, $filename, $linenumber ) = @_; print "CD===> $line\n" if $debug; directive_error( "Invalid compiler directive ($line)" , $filename, $linenumber ) unless $line =~ /^\s*\?(IF\s+|ELSE|ELSIF\s+|ENDIF|SET\s+|RESET\s+|FORMAT\s+|COMMENT\s*)(.*)$/i; my ($keyword, $expression) = ( uc $1, $2 ); $keyword =~ s/\s*$//; if ( supplied $expression ) { $expression =~ s/#.*//; $expression =~ s/\s*$//; } else { $expression = ''; } my ( $lastkeyword, $prioromit, $included, $lastlinenumber ) = @ifstack ? @{$ifstack[-1]} : ('', 0, 0, 0 ); my %directives = ( IF => sub() { directive_error( "Missing IF expression" , $filename, $linenumber ) unless supplied $expression; my $nextomitting = $omitting || ! evaluate_expression( $expression , $filename, $linenumber ); push @ifstack, [ 'IF', $omitting, ! $nextomitting, $linenumber ]; $omitting = $nextomitting; } , ELSIF => sub() { directive_error( "?ELSIF has no matching ?IF" , $filename, $linenumber ) unless @ifstack > $ifstack && $lastkeyword =~ /IF/; directive_error( "Missing IF expression" , $filename, $linenumber ) unless $expression; if ( $omitting && ! $included ) { # # We can only change to including if we were previously omitting # $omitting = $prioromit || ! evaluate_expression( $expression , $filename, $linenumber ); $included = ! $omitting; } else { # # We have already included -- so we don't want to include this part # $omitting = 1; } $ifstack[-1] = [ 'ELSIF', $prioromit, $included, $lastlinenumber ]; } , ELSE => sub() { directive_error( "Invalid ?ELSE" , $filename, $linenumber ) unless $expression eq ''; directive_error( "?ELSE has no matching ?IF" , $filename, $linenumber ) unless @ifstack > $ifstack && $lastkeyword =~ /IF/; $omitting = $included || ! $omitting unless $prioromit; $ifstack[-1] = [ 'ELSE', $prioromit, 1, $lastlinenumber ]; } , ENDIF => sub() { directive_error( "Invalid ?ENDIF" , $filename, $linenumber ) unless $expression eq ''; directive_error( q(Unexpected "?ENDIF" without matching ?IF or ?ELSE) , $filename, $linenumber ) if @ifstack <= $ifstack; $omitting = $prioromit; pop @ifstack; } , SET => sub() { unless ( $omitting ) { directive_error( "Missing SET variable", $filename, $linenumber ) unless supplied $expression; ( my $var , $expression ) = split ' ', $expression, 2; directive_error( "Invalid SET variable ($var)", $filename, $linenumber) unless $var =~ /^(\$)?([a-zA-Z]\w*)$/ || $var =~ /^(@)(\d+|[a-zA-Z]\w*)/; directive_error( "Missing SET expression" , $filename, $linenumber) unless supplied $expression; if ( ( $1 || '' ) eq '@' ) { $var = $2; $var = numeric_value( $var ) if $var =~ /^\d/; $var = $2 || 'chain'; directive_error( "Shorewall variables may only be SET in the body of an action", $filename, $linenumber ) unless $actparms{0}; my $val = $actparms{$var} = evaluate_expression ( $expression, $filename, $linenumber ); $parmsmodified = PARMSMODIFIED; } else { $variables{$2} = evaluate_expression( $expression, $filename, $linenumber ); } } } , 'FORMAT' => sub() { unless ( $omitting ) { directive_error( "?FORMAT is not allowed in this file", $filename, $linenumber ) unless $max_format > 1; directive_error( "Missing format", $filename, $linenumber ) unless supplied $expression; directive_error( "Invalid format ($expression)", $filename, $linenumber ) unless $expression =~ /^\d+$/; directive_error( "Format must be between 1 and $max_format", $filename, $linenumber ) unless $expression && $expression <= $max_format; $file_format = $expression; } } , RESET => sub() { unless ( $omitting ) { my $var = $expression; directive_error( "Missing RESET variable", $filename, $linenumber) unless supplied $var; directive_error( "Invalid RESET variable ($var)", $filename, $linenumber) unless $var =~ /^(\$)?([a-zA-Z]\w*)$/ || $var =~ /^(@)(\d+|[a-zA-Z]\w*)/; if ( ( $1 || '' ) eq '@' ) { $var = numeric_value( $var ) if $var =~ /^\d/; $var = $2 || 'chain'; directive_error( "Shorewall variables may only be RESET in the body of an action", $filename, $linenumber ) unless $actparms{0}; if ( exists $actparms{$var} ) { if ( $var =~ /^loglevel|logtag|chain|disposition|caller$/ ) { $actparms{$var} = ''; } else { delete $actparms{$var} } } else { directive_warning( "Shorewall variable $2 does not exist", $filename, $linenumber ); } } else { if ( exists $variables{$2} ) { delete $variables{$2}; } else { directive_warning( "Shell variable $2 does not exist", $filename, $linenumber ); } } } } , COMMENT => sub() { unless ( $omitting ) { if ( $comments_allowed ) { unless ( $nocomment ) { if ( have_capability( 'COMMENTS' ) ) { ( $comment = $line ) =~ s/^\s*\?COMMENT\s*//; $comment =~ s/\s*$//; } else { directive_warning( "COMMENTs ignored -- require comment support in iptables/Netfilter" , $filename, $linenumber ) unless $warningcount++; } } } else { directive_error ( "?COMMENT is not allowed in this file", $filename, $linenumber ); } } } ); if ( my $function = $directives{$keyword} ) { $function->(); } else { assert( 0, $keyword ); } if ( $directive_callback ) { $directive_callback->( $keyword, $line ) } else { $omitting; } } # # Functions for copying a file into the script # sub copy( $ ) { assert( $script_enabled ); if ( $script ) { my $file = $_[0]; my $omitting = 0; my $save_ifstack = $ifstack; my $lineno = 0; $ifstack = @ifstack; open IF , $file or fatal_error "Unable to open $file: $!"; while ( ) { chomp; $lineno++; if ( /^\s*\?/ ) { $omitting = process_compiler_directive( $omitting, $_, $file, $lineno ); next; } next if $omitting; if ( /^\s*$/ ) { print $script "\n" unless $lastlineblank; $lastlineblank = 1; } else { if ( $indent ) { s/^(\s*)/$indent1$1$indent2/; s/ /\t/ if $indent2; } print $script $_; print $script "\n"; $lastlineblank = 0; } } if ( $ifstack < @ifstack ) { $currentlinenumber = 'EOF'; $currentfilename = $file; fatal_error "Missing ?ENDIF to match the ?IF at line $ifstack[-1]->[3]"; } else { $ifstack = $save_ifstack; } close IF; } } # # This variant of copy handles line continuation, 'here documents' and INCLUDE # sub copy1( $ ) { assert( $script_enabled ); my $result = 0; if ( $script || $debug ) { my ( $do_indent, $here_documents ) = ( 1, ''); open_file( $_[0] ); while ( $currentfile ) { while ( <$currentfile> ) { $currentlinenumber++; chomp; if ( /^\s*\?/ ) { $omitting = process_compiler_directive( $omitting, $_, $currentfilename, $currentlinenumber ); next; } if ( /^${here_documents}\s*$/ ) { if ( $script ) { print $script $here_documents if $here_documents; print $script "\n"; } if ( $debug ) { print "GS-----> $here_documents" if $here_documents; print "GS----->\n"; } $do_indent = 1; $here_documents = ''; next; } if ( $do_indent && /.*<<\s*([^ ]+)s*(.*)/ ) { $here_documents = $1; s/^(\s*)/$indent1$1$indent2/; s/ /\t/ if $indent2; $do_indent = 0; if ( $script ) { print $script $_; print $script "\n"; } if ( $debug ) { s/\n/\nGS-----> /g; print "GS-----> $_\n"; } $result = 1; next; } if ( $do_indent ) { if ( /^\s*INCLUDE\b/ ) { my @line = split / /; fatal_error "Invalid INCLUDE command" if @line != 2; fatal_error "INCLUDEs nested too deeply" if @includestack >= 4; my $filename = find_file $line[1]; warning_message "Reserved filename ($1) in INCLUDE directive" if $filename =~ '/(.*)' && $config_files{$1}; fatal_error "INCLUDE file $filename not found" unless -f $filename; fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _; if ( -s _ ) { push_include; $currentfile = undef; do_open_file $filename; } else { $currentlinenumber = 0; } next; } if ( $indent ) { s/^(\s*)/$indent1$1$indent2/; s/ /\t/ if $indent2; } } if ( $script ) { print $script $_; print $script "\n"; } $do_indent = ! ( $here_documents || /\\$/ ); $result = 1 unless $result || /^\s*$/ || /^\s*#/; if ( $debug ) { s/\n/\nGS-----> /g; print "GS-----> $_\n"; } } close_file; } } $lastlineblank = 0; $result; } # # This one drops header comments and replaces them with a three-line banner # sub copy2( $$ ) { my ( $file, $trace ) = @_; assert( $script_enabled ); my $empty = 1; if ( $script || $trace ) { my $file = $_[0]; my $omitting = 0; my $save_ifstack = $ifstack; my $lineno = 0; open IF , $file or fatal_error "Unable to open $file: $!"; while ( ) { $lineno++; $empty = 0, last unless /^#/; } unless ( $empty ) { emit < ) { $lineno++; chomp; if ( /^\s*\?/ ) { $omitting = process_compiler_directive( $omitting, $_, $file, $lineno ); next; } next if $omitting; if ( /^\s*$/ ) { unless ( $lastlineblank ) { print $script "\n" if $script; print "GS----->\n" if $trace; } $lastlineblank = 1; } else { if ( $indent ) { s/^(\s*)/$indent1$1$indent2/; s/ /\t/ if $indent2; } if ( $script ) { print $script $_; print $script "\n"; } if ( $trace ) { s/\n/GS-----> \n/g; print "GS-----> $_\n"; } $lastlineblank = 0; } } unless ( $lastlineblank ) { print $script "\n" if $script; print "GS----->\n" if $trace; } emit( '################################################################################', "# End of imports from $file", '################################################################################' ); } if ( $ifstack < @ifstack ) { $currentfilename = $file; $currentlinenumber = 'EOF'; fatal_error "Missing ?ENDIF to match the ?IF at line $ifstack[-1]->[3]"; } else { $ifstack = $save_ifstack; } close IF; } } # # The following two functions allow module clients to nest opens. This happens frequently # in the Rules module. # sub push_open( $;$$$$ ) { my ( $file, $max , $ca, $nc, $cf ) = @_; push_include; clear_section_function; my @a = @includestack; push @openstack, \@a; @includestack = (); $currentfile = undef; open_file( $file , $max, $comments_allowed || $ca, $nc , $cf ); } sub pop_open() { @includestack = @{pop @openstack}; pop_include; } # # This function is called by in-line PERL to generate a line of input for the current file. # If the in-line PERL returns an indication of success, then the generated lines will be # processed as regular file input. # sub shorewall { unless ( $perlscript ) { fatal_error "shorewall() may not be called in this context" unless $currentfile; $dir ||= '/tmp/'; eval { ( $perlscript, $perlscriptname ) = tempfile ( 'perlscriptXXXX' , DIR => $dir ); }; fatal_error "Unable to create temporary file in directory $dir" if $@; } print $perlscript "@_\n"; } # # We don't announce that we are checking/compiling a file until we determine that the file contains # at least one non-blank, non-commentary line. # # The argument to this function may be either a scalar or a function reference. When the first # non-blank/non-commentary line is reached: # # - if a function reference was passed to first_entry(), that function is called # - otherwise, the argument to first_entry() is passed to progress_message2(). # # We do this processing in read_a_line() rather than in the higher-level routines because # Embedded Shell/Perl scripts are processed out of read_a_line(). If we were to defer announcement # until we get back to the caller of read_a_line(), we could issue error messages about parsing and # running scripts in the file before we'd even indicated that we are processing it. # sub first_entry( $ ) { $first_entry = shift; my $reftype = reftype $first_entry; assert( $reftype eq 'CODE' ) if $reftype; } sub read_a_line($); sub embedded_shell( $ ) { my $multiline = shift; fatal_error "INCLUDEs nested too deeply" if @includestack >= 4; my ( $command, $linenumber ) = ( "/bin/sh -c '$currentline", $currentlinenumber ); $directive_callback->( 'SHELL', $currentline ) if $directive_callback; if ( $multiline ) { # # Multi-line script # fatal_error "Invalid BEGIN SHELL directive" unless $currentline =~ /^\s*$/; $command .= "\n"; my $last = 0; while ( read_a_line( PLAIN_READ ) ) { $directive_callback->( 'SHELL', $currentline ) if $directive_callback; last if $last = $currentline =~ s/^\s*\??END(\s+SHELL)?\s*(?:;\s*)?$//i; $command .= "$currentline\n"; } fatal_error ( "Missing END SHELL" ) unless $last; fatal_error ( "Invalid END SHELL directive" ) unless $currentline =~ /^\s*$/; } $command .= q('); push_include; $currentfile = undef; open $currentfile , '-|', $command or fatal_error qq(Shell Command failed); $currentfilename = "SHELL\@$currentfilename:$currentlinenumber"; $currentline = ''; $currentlinenumber = 0; $ifstack = @ifstack; } sub embedded_perl( $ ) { my $multiline = shift; my ( $command , $linenumber ) = ( qq(package Shorewall::User;\nno strict;\nuse Shorewall::Config (qw/shorewall/);\n# line $currentlinenumber "$currentfilename"\n$currentline), $currentlinenumber ); $directive_callback->( 'PERL', $currentline ) if $directive_callback; if ( $multiline ) { # # Multi-line script # fatal_error "Invalid BEGIN PERL directive" unless $currentline =~ /^\s*$/; $command .= "\n"; my $last = 0; while ( read_a_line( PLAIN_READ ) ) { $directive_callback->( 'PERL', $currentline ) if $directive_callback; last if $last = $currentline =~ s/^\s*\??END(\s+PERL)?\s*(?:;\s*)?//i; $command .= "$currentline\n"; } fatal_error ( "Missing END PERL" ) unless $last; fatal_error ( "Invalid END PERL directive" ) unless $currentline =~ /^\s*$/; } else { $currentline = ''; } $embedded++; unless (my $return = eval $command ) { # # Perl found the script offensive or the script itself died # if ( $@ ) { $@ =~ s/, <\$currentfile> line \d+//g; fatal_error1 "$@"; } unless ( defined $return ) { fatal_error "Perl Script failed: $!" if $!; fatal_error "Perl Script failed"; } fatal_error "Perl Script Returned False"; } $embedded--; if ( $perlscript ) { fatal_error "INCLUDEs nested too deeply" if @includestack >= 4; assert( close $perlscript ); $perlscript = undef; push_include; $currentfile = undef; open $currentfile, '<', $perlscriptname or fatal_error "Unable to open Perl Script $perlscriptname"; push @tempfiles, $perlscriptname unless unlink $perlscriptname; #unlink fails on Cygwin $perlscriptname = ''; $currentfilename = "PERL\@$currentfilename:$linenumber"; $currentline = ''; $currentlinenumber = 0; $ifstack = @ifstack; } } # # Return inline matches # sub fetch_inline_matches() { "$inline_matches "; } sub set_inline_matches( $ ) { $inline_matches = $_[0]; } # # Push/pop acton params # sub push_action_params( $$$$$$ ) { my ( $action, $chainref, $parms, $loglevel, $logtag, $caller ) = @_; my @parms = ( undef , split_list3( $parms , 'parameter' ) ); $actparms{modified} = $parmsmodified; $actparms{usedcaller} = $usedcaller; my %oldparms = %actparms; $parmsmodified = 0; $usedcaller = 0; %actparms = (); for ( my $i = 1; $i < @parms; $i++ ) { my $val = $parms[$i]; $actparms{$i} = $val eq '-' ? '' : $val eq '--' ? '-' : $val; } $actparms{0} = $chainref; $actparms{action} = $action; $actparms{loglevel} = $loglevel; $actparms{logtag} = $logtag; $actparms{caller} = $caller; $actparms{disposition} = '' if $chainref->{action}; # # The Shorewall variable '@chain' has the non-word charaters removed # ( $actparms{chain} = $chainref->{name} ) =~ s/[^\w]//g; \%oldparms; } # # Pop the action parameters using the passed hash reference # Return: # 1 if the popped parameters were modified # 2 if the action used @CALLER # sub pop_action_params( $ ) { my $oldparms = shift; %actparms = %$oldparms; my $return = $parmsmodified | $usedcaller; ( $parmsmodified ) = delete $actparms{modified} || 0; ( $usedcaller ) = delete $actparms{usedcaller} || 0; $return; } sub default_action_params { my $action = shift; my ( $val, $i ); for ( $i = 1; 1; $i++ ) { last unless defined ( $val = shift ); my $curval = $actparms{$i}; $actparms{$i} = $val unless supplied( $curval ); } fatal_error "Too Many arguments to action $action" if defined $actparms{$i}; } sub get_action_params( $ ) { my $num = shift; fatal_error "Invalid argument to get_action_params()" unless $num =~ /^\d+$/ && $num > 0; my @return; for ( my $i = 1; $i <= $num; $i++ ) { my $val = $actparms{$i}; push @return, defined $val ? $val eq '-' ? '' : $val eq '--' ? '-' : $val : $val; } @return; } # # Returns the Level and Tag for the current action chain # sub get_action_logging() { @actparms{ 'loglevel', 'logtag' }; } sub get_action_chain() { $actparms{0}; } sub get_action_chain_name() { $actparms{chain}; } sub set_action_name_to_caller() { $actparms{chain} = $actparms{caller}; } sub get_action_disposition() { $actparms{disposition}; } sub set_action_disposition($) { $actparms{disposition} = $_[0]; } sub set_action_param( $$ ) { my $i = shift; fatal_error "Parameter numbers must be numeric" unless $i =~ /^\d+$/ && $i > 0; $actparms{$i} = shift; } # # Expand Shell Variables in the passed buffer using %actparms, %params, %shorewallrc1 and %config, # sub expand_variables( \$ ) { my ( $lineref, $count ) = ( $_[0], 0 ); my $chain = $actparms{chain}; # $1 $2 $3 - $4 while ( $$lineref =~ m( ^(.*?) \$({)? (\d+|[a-zA-Z_]\w*) (?(2)}) (.*)$ )x ) { my ( $first, $var, $rest ) = ( $1, $3, $4); my $val; if ( $var =~ /^\d+$/ ) { fatal_error "Action parameters (\$$var) may only be referenced within the body of an action" unless $chain; if ( $config{IGNOREUNKNOWNVARIABLES} ) { fatal_error "Invalid action parameter (\$$var)" if ( length( $var ) > 1 && $var =~ /^0/ ); } else { fatal_error "Undefined parameter (\$$var)" unless ( defined $actparms{$var} && ( length( $var ) == 1 || $var !~ /^0/ ) ); } $val = $var ? $actparms{$var} : $actparms{0}->{name}; } elsif ( exists $variables{$var} ) { $val = $variables{$var}; } elsif ( exists $actparms{$var} ) { $val = $actparms{$var}; $usedcaller = USEDCALLER if $var eq 'caller'; } else { fatal_error "Undefined shell variable (\$$var)" unless $config{IGNOREUNKNOWNVARIABLES} || exists $config{$var}; } $val = '' unless defined $val; $$lineref = join( '', $first , $val , $rest ); fatal_error "Variable Expansion Loop" if ++$count > 100; } if ( $chain ) { # # We're in an action body -- allow escaping at signs (@) for u32 # $$lineref =~ s/\\@/??/g; # $1 $2 $3 - $4 while ( $$lineref =~ m( ^(.*?) \@({)? (\d+|[a-zA-Z_]\w*) (?(2)}) (.*)$ )x ) { my ( $first, $var, $rest ) = ( $1, $3, $4); my $val = $var ? $actparms{$var} : $actparms{chain}; $usedcaller = USEDCALLER if $var eq 'caller'; $val = '' unless defined $val; $$lineref = join( '', $first , $val , $rest ); fatal_error "Variable Expansion Loop" if ++$count > 100; } $$lineref =~ s/\?\?/@/g; } } sub expand_shorewallrc_variables( \$ ) { my ( $lineref, $count ) = ( $_[0], 0 ); # $1 $2 $3 - $4 while ( $$lineref =~ m( ^(.*?) \$({)? (\d+|[a-zA-Z]\w*) (?(2)}) (.*)$ )x ) { my ( $first, $var, $rest ) = ( $1, $3, $4); my $val; if ( exists $shorewallrc{$var} ) { $val = $shorewallrc{$var} } $val = '' unless defined $val; $$lineref = join( '', $first , $val , $rest ); fatal_error "Variable Expansion Loop" if ++$count > 100; } } # # Handle first-entry processing # sub handle_first_entry() { # # $first_entry can contain either a function reference or a message. If it # contains a reference, call the function -- otherwise issue the message # my $entry = $first_entry; $first_entry = 0; reftype( $entry ) ? $entry->() : progress_message2( $entry ); } # # Read a line from the current include stack. Based on the passed options, it will conditionally: # # - Ignore blank or comment-only lines. # - Remove trailing comments. # - Handle Line Continuation # - Handle embedded SHELL and PERL scripts # - Expand shell variables from %params and %ENV. # - Handle INCLUDE # - Handle ?IF, ?ELSE, ?ENDIF # sub read_a_line($) { my $options = $_[0]; while ( $currentfile ) { $currentline = ''; $currentlinenumber = 0; while ( <$currentfile> ) { chomp; # # Handle conditionals # if ( /^\s*\?(?:IF|ELSE|ELSIF|ENDIF|SET|RESET|FORMAT|COMMENT)/i ) { $omitting = process_compiler_directive( $omitting, $_, $currentfilename, $. ); next; } if ( $omitting ) { print "OMIT=> $_\n" if $debug; next; } $currentlinenumber = $. unless $currentlinenumber; # # Suppress leading whitespace in certain continuation lines # s/^\s*// if $currentline =~ /[,:]$/ && $options & CONFIG_CONTINUATION; # # If this is a continued line with a trailing comment, remove comment. Note that # the result will now end in '\'. # s/\s*#.*$// if ($options & STRIP_COMMENTS) && /[\\]\s*#.*$/; # # Continuation # chop $currentline, next if ($currentline .= $_) =~ /\\$/; # # Must check for shell/perl before doing variable expansion # if ( $options & EMBEDDED_ENABLED ) { if ( $currentline =~ s/^\s*\??(BEGIN\s+)SHELL\s*;?//i || $currentline =~ s/^\s*\?SHELL\s*//i || $currentline =~ s/^\s*SHELL\s+// ) { handle_first_entry if $first_entry; embedded_shell( $1 ); next; } if ( $currentline =~ s/^\s*\??(BEGIN\s+)PERL\s*;?//i || $currentline =~ s/^\s*\??PERL\s*//i ) { handle_first_entry if $first_entry; embedded_perl( $1 ); next; } } # # Now remove concatinated comments if asked # $currentline =~ s/\s*#.*$// if $options & STRIP_COMMENTS; if ( $options & SUPPRESS_WHITESPACE ) { # # Ignore (concatinated) blank lines # $currentline = '', $currentlinenumber = 0, next if $currentline =~ /^\s*$/; # # Eliminate trailing whitespace # $currentline =~ s/\s*$//; } # # Line not blank -- Handle any first-entry message/capabilities check # handle_first_entry if $first_entry; # # Expand Shell Variables using %params and %actparms # expand_variables( $currentline ) if $options & EXPAND_VARIABLES; if ( ( $options & DO_INCLUDE ) && $currentline =~ /^\s*\??INCLUDE\s/ ) { my @line = split ' ', $currentline; fatal_error "Invalid INCLUDE command" if @line != 2; fatal_error "INCLUDEs/Scripts nested too deeply" if @includestack >= 4; my $filename = find_file $line[1]; fatal_error "INCLUDE file $filename not found" unless -f $filename; fatal_error "Directory ($filename) not allowed in INCLUDE" if -d _; if ( -s _ ) { push_include; $currentfile = undef; do_open_file $filename; } else { $currentlinenumber = 0; } $currentline = ''; } elsif ( ( $options & DO_SECTION ) && $currentline =~ /^\s*\?SECTION\s+(.*)/i ) { my $sectionname = $1; fatal_error "Invalid SECTION name ($sectionname)" unless $sectionname =~ /^[-_\da-zA-Z]+$/; fatal_error "This file does not allow ?SECTION" unless $section_function; $section_function->($sectionname); $directive_callback->( 'SECTION', $currentline ) if $directive_callback; $currentline = ''; } else { fatal_error "Non-ASCII gunk in file" if ( $options && CHECK_GUNK ) && $currentline =~ /[^\s[:print:]]/; print "IN===> $currentline\n" if $debug; return 1; } } close_file; } } sub process_shorewallrc( $$ ) { my ( $shorewallrc , $product ) = @_; $shorewallrc{PRODUCT} = $product; if ( open_file $shorewallrc ) { while ( read_a_line( STRIP_COMMENTS | SUPPRESS_WHITESPACE | CHECK_GUNK ) ) { if ( $currentline =~ /^([a-zA-Z]\w*)=(.*)$/ ) { my ($var, $val) = ($1, $2); $val = $1 if $val =~ /^\"([^\"]*)\"$/; expand_shorewallrc_variables($val) if supplied $val; $shorewallrc{$var} = $val; } else { fatal_error "Unrecognized shorewallrc entry"; } } } else { fatal_error "Failed to open $shorewallrc: $!"; } if ( supplied $shorewallrc{VARDIR} ) { if ( ! supplied $shorewallrc{VARLIB} ) { $shorewallrc{VARLIB} = $shorewallrc{VARDIR}; $shorewallrc{VARDIR} = "$shorewallrc{VARLIB}/$product"; } } elsif ( supplied $shorewallrc{VARLIB} ) { $shorewallrc{VARDIR} = "$shorewallrc{VARLIB}/$product" unless supplied $shorewallrc{VARDIR}; } } # # Provide the passed default value for the passed configuration variable # sub default ( $$ ) { my ( $var, $val ) = @_; $config{$var} = $val unless supplied( $config{$var} ); } # # Provide a default value for a yes/no configuration variable. # sub default_yes_no ( $$;$ ) { my ( $var, $val, $other ) = @_; my $result = 1; my $curval = $config{$var}; if ( supplied $curval ) { $curval = lc $curval; if ( $curval eq 'no' ) { $config{$var} = ''; } elsif ( defined( $other ) ) { if ( $other eq '*' ) { if ( $curval eq 'yes' ) { $config{$var} = 'Yes'; } else { $result = 0; } } elsif ( $curval eq $other ) { # # Downshift value for later comparison # $config{$var} = $curval; } } else { fatal_error "Invalid value for $var ($curval)" unless $curval eq 'yes'; # # Make Case same as default # $config{$var} = 'Yes'; } } else { $config{$var} = $val; } $result; } sub default_yes_no_ipv4 ( $$ ) { my ( $var, $val ) = @_; default_yes_no( $var, $val ); warning_message "$var=Yes is ignored for IPv6" if $family == F_IPV6 && $config{$var}; } sub numeric_option( $$$ ) { my ( $option, $default, $min ) = @_; my $value = $config{$option}; my $val = $default; if ( supplied $value ) { $val = numeric_value $value; fatal_error "Invalid value ($value) for '$option'" unless defined $val && $val <= 32; } $val = $min if $val < $min; $config{$option} = $val; } sub make_mask( $ ) { 0xffffffff >> ( 32 - $_[0] ); } my @suffixes = qw(group range threshold nlgroup cprange qthreshold); # # Validate a log level -- Drop the trailing '!' and translate to numeric value if appropriate" # sub level_error( $;$ ) { my ( $level , $option ) = @_; if ( $option ) { fatal_error "Invalid log level ($level) for option $option"; } else { fatal_error "Invalid log level ($_[0])"; } } my %logoptions = ( tcp_sequence => '--log-tcp-sequence', ip_options => '--log-ip-options', tcp_options => '--log-tcp-options', uid => '--log-uid', macdecode => '--log-macdecode', # # Because a level can pass through validate_level() more than once, # the full option names are also included here. # '--log-tcp-sequence' => '--log-tcp-sequence', '--log-ip-options' => '--log-ip-options', '--log-tcp-options' => '--log-tcp-options', '--log-uid' => '--log-uid', '--log-macdecode' => '--log-macdecode', ); sub validate_level( $;$ ) { my ( $rawlevel, $option ) = @_; my $level = uc $rawlevel; if ( supplied ( $level ) ) { $level =~ s/!$//; my $value = $level; my $qualifier; unless ( $value =~ /^[0-7]$/ ) { } if ( $value =~ /^([0-7])(.*)$/ ) { $value = $1; $qualifier = $2; } elsif ( $value =~ /^([A-Za-z0-7]+)(.*)$/ ) { level_error( $level, $option ) unless defined( $value = $validlevels{$1} ); $qualifier = $2; } if ( $value =~ /^[0-7]$/ ) { # # Syslog Level # if ( supplied $qualifier ) { my $options = ''; my %options; level_error ( $rawlevel , $option ) unless $qualifier =~ /^\((.*)\)$/; for ( split_list lc $1, "log options" ) { my $option = $logoptions{$_}; fatal_error "Unknown LOG option ($_)" unless $option; unless ( $options{$option} ) { if ( $options ) { $options = join( ',', $options, $option ); } else { $options = $option; } $options{$option} = 1; } } $value .= "($options)" if $options; } if ( $option ) { require_capability ( 'LOG_TARGET' , "Log level $level for option $option", 's' ); } else { require_capability ( 'LOG_TARGET' , "Log level $level", 's' ); } return $value; } return '' unless $value; if ( $option ) { require_capability( "${value}_TARGET", "Log level $level for option $option", 's' ); } else { require_capability( "${value}_TARGET", "Log level $level", 's' ); } if ( $value =~ /^(NFLOG|ULOG)$/ ) { my $olevel = $value; if ( $qualifier =~ /^[(](.*)[)]$/ ) { my @options = split /,/, $1; my $prefix = lc $olevel; my $index = $prefix eq 'ulog' ? 3 : 0; level_error( $rawlevel , $option ) if @options > 3; for ( @options ) { if ( supplied( $_ ) ) { level_error( $rawlevel , $option ) unless /^\d+/; $olevel .= " --${prefix}-$suffixes[$index] $_"; } $index++; } } elsif ( $qualifier =~ /^ --/ ) { return $rawlevel; } else { level_error( $rawlevel , $option ) if $qualifier; } return $olevel; } # # Must be LOGMARK # my $sublevel; if ( supplied $qualifier ) { return $rawlevel if $qualifier =~ /^ --/; if ( $qualifier =~ /[(](.+)[)]$/ ) { $sublevel = $1; $sublevel = $validlevels{$sublevel} unless $sublevel =~ /^[0-7]$/; level_error( $rawlevel , $option ) unless defined $sublevel && $sublevel =~ /^[0-7]$/; } else { level_error( $rawlevel , $option ); } } else { $sublevel = 6; # info } return "LOGMARK --log-level $sublevel"; } ''; } # # Validate a log level and supply default # sub default_log_level( $$ ) { my ( $level, $default ) = @_; my $value = $config{$level}; unless ( supplied $value ) { $config{$level} = validate_level $default, $level; } else { ( $value, my $tag ) = split( ':', $value , 2 ); $config{$level} = validate_level $value, $level; if ( supplied $tag ) { my $tag_name = $level; $tag_name =~ s/_LEVEL/_TAG/; $globals{$tag_name} = $tag; } } } # # Check a tri-valued variable # sub check_trivalue( $$ ) { my ( $var, $default) = @_; my $val = $config{$var}; if ( defined $val ) { $val = lc $val; if ( $val eq 'yes' || $val eq 'on' ) { $config{$var} = 'on'; } elsif ( $val eq 'no' || $val eq 'off' ) { $config{$var} = 'off'; } elsif ( $val eq 'keep' ) { $config{$var} = ''; } elsif ( $val eq '' ) { $config{$var} = $default } else { fatal_error "Invalid value ($val) for $var"; } } else { $config{$var} = $default } } # # Produce a report of the detected capabilities # sub report_capability( $ ) { my $cap = $_[0]; print " $capdesc{$cap}: "; if ( $cap eq 'CAPVERSION' || $cap eq 'KERNELVERSION') { my $version = $capabilities{$cap}; printf "%d.%d.%d\n", int( $version / 10000 ) , int ( ( $version % 10000 ) / 100 ) , int ( $version % 100 ); } else { print $capabilities{$cap} ? "Available\n" : "Not Available\n"; } } sub report_capabilities() { if ( $verbosity > 1 ) { print "Shorewall has detected the following capabilities:\n"; for my $cap ( sort { $capdesc{$a} cmp $capdesc{$b} } keys %capabilities ) { report_capability $cap; } } } # # Search the current PATH for the passed executable # sub which( $ ) { my $prog = $_[0]; for ( split /:/, $config{PATH} ) { return "$_/$prog" if -x "$_/$prog"; } ''; } # # Load the kernel modules defined in the 'modules' file. # sub load_kernel_modules( ) { my $moduleloader = which( 'modprobe' ) || ( which 'insmod' ); my $modulesdir = $config{MODULESDIR}; unless ( $modulesdir ) { my $uname = `uname -r`; fatal_error "The command 'uname -r' failed" unless $? == 0; chomp $uname; $modulesdir = "/lib/modules/$uname/kernel/net/ipv4/netfilter:/lib/modules/$uname/kernel/net/ipv6/netfilter:/lib/modules/$uname/kernel/net/netfilter:/lib/modules/$uname/extra:/lib/modules/$uname/extra/ipset"; } my @moduledirectories; for ( split /:/, $modulesdir ) { push @moduledirectories, $_ if -d $_; } if ( $moduleloader && @moduledirectories && open_file( $config{LOAD_HELPERS_ONLY} ? 'helpers' : 'modules' ) ) { my %loadedmodules; $loadedmodules{$_}++ for split_list( $config{DONT_LOAD}, 'module' ); progress_message2 "Loading Modules..."; open LSMOD , '-|', 'lsmod' or fatal_error "Can't run lsmod"; while ( ) { my $module = ( split( /\s+/, $_, 2 ) )[0]; $loadedmodules{$module}++ unless $module eq 'Module' } close LSMOD; $config{MODULE_SUFFIX} = 'o gz xz ko o.gz o.xz ko.gz ko.xz' unless $config{MODULE_SUFFIX}; my @suffixes = split /\s+/ , $config{MODULE_SUFFIX}; while ( read_a_line( NORMAL_READ ) ) { fatal_error "Invalid modules file entry" unless ( $currentline =~ /^loadmodule\s+([a-zA-Z]\w*)\s*(.*)$/ ); my ( $module, $arguments ) = ( $1, $2 ); unless ( $loadedmodules{ $module } ) { for my $directory ( @moduledirectories ) { for my $suffix ( @suffixes ) { my $modulefile = "$directory/$module.$suffix"; if ( -f $modulefile ) { if ( $moduleloader eq 'insmod' ) { system ("insmod $modulefile $arguments" ); } else { system( "modprobe $module $arguments" ); } $loadedmodules{ $module } = 1; } } } } } } } # # Get the current kernel version # sub determine_kernelversion() { my $kernelversion=`uname -r`; if ( $kernelversion =~ /^(\d+)\.(\d+).(\d+)/ ) { $capabilities{KERNELVERSION} = sprintf "%d%02d%02d", $1 , $2 , $3; } elsif ( $kernelversion =~ /^(\d+)\.(\d+)/ ) { $capabilities{KERNELVERSION} = sprintf "%d%02d00", $1 , $2; } else { fatal_error "Unrecognized Kernel Version Format ($kernelversion)"; } } # # Capability Reporting and detection. # sub Nat_Enabled() { qt1( "$iptables $iptablesw -t nat -L -n" ); } sub Persistent_Snat() { have_capability( 'NAT_ENABLED' ) || return ''; my $result = ''; my $address = $family == F_IPV4 ? '1.2.3.4' : '2001::1'; if ( qt1( "$iptables $iptablesw -t nat -N $sillyname" ) ) { $result = qt1( "$iptables $iptablesw -t nat -A $sillyname -j SNAT --to-source $address --persistent" ); qt1( "$iptables $iptablesw -t nat -F $sillyname" ); qt1( "$iptables $iptablesw -t nat -X $sillyname" ); } $result; } sub Masquerade_Tgt() { have_capability( 'NAT_ENABLED' ) || return ''; my $result = ''; my $address = $family == F_IPV4 ? '1.2.3.4' : '2001::1'; if ( qt1( "$iptables $iptablesw -t nat -N $sillyname" ) ) { $result = qt1( "$iptables $iptablesw -t nat -A $sillyname -j MASQUERADE" ); qt1( "$iptables $iptablesw -t nat -F $sillyname" ); qt1( "$iptables $iptablesw -t nat -X $sillyname" ); } $result; } sub Udpliteredirect() { have_capability( 'NAT_ENABLED' ) || return ''; my $result = ''; my $address = $family == F_IPV4 ? '1.2.3.4' : '2001::1'; if ( qt1( "$iptables $iptablesw -t nat -N $sillyname" ) ) { $result = qt1( "$iptables $iptablesw -t nat -A $sillyname -p udplite -m multiport --dports 33 -j REDIRECT --to-port 22" ); qt1( "$iptables $iptablesw -t nat -F $sillyname" ); qt1( "$iptables $iptablesw -t nat -X $sillyname" ); } $result; } sub Mangle_Enabled() { if ( qt1( "$iptables $iptablesw -t mangle -L -n" ) ) { system( "$iptables $iptablesw -t mangle -N $sillyname" ) == 0 || fatal_error "Cannot Create Mangle chain $sillyname"; } } sub Conntrack_Match() { if ( $family == F_IPV4 ) { qt1( "$iptables $iptablesw -A $sillyname -m conntrack --ctorigdst 192.168.1.1 -j ACCEPT" ); } else { qt1( "$iptables $iptablesw -A $sillyname -m conntrack --ctorigdst ::1 -j ACCEPT" ); } } sub New_Conntrack_Match() { have_capability( 'CONNTRACK_MATCH' ) && qt1( "$iptables $iptablesw -A $sillyname -m conntrack -p tcp --ctorigdstport 22 -j ACCEPT" ); } sub Old_Conntrack_Match() { ! qt1( "$iptables $iptablesw -A $sillyname -m conntrack ! --ctorigdst 1.2.3.4" ); } sub Multiport() { qt1( "$iptables $iptablesw -A $sillyname -p tcp -m multiport --dports 21,22 -j ACCEPT" ); } sub Kludgefree1() { have_capability( 'MULTIPORT' ) && qt1( "$iptables $iptablesw -A $sillyname -p tcp -m multiport --sports 60 -m multiport --dports 99 -j ACCEPT" ); } sub Kludgefree2() { have_capability( 'PHYSDEV_MATCH' ) && qt1( "$iptables $iptablesw -A $sillyname -m physdev --physdev-in eth0 -m physdev --physdev-out eth0 -j ACCEPT" ); } sub Kludgefree3() { if ( $family == F_IPV4 ) { qt1( "$iptables $iptablesw -A $sillyname -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" ); } else { qt1( "$iptables $iptablesw -A $sillyname -m iprange --src-range ::1-::2 -m iprange --dst-range 192.168.1.5-192.168.1.124 -j ACCEPT" ); } } sub Kludgefree() { Kludgefree1 || Kludgefree2 || Kludgefree3; } sub Xmultiport() { qt1( "$iptables $iptablesw -A $sillyname -p tcp -m multiport --dports 21:22 -j ACCEPT" ); } sub Emultiport() { qt1( "$iptables $iptablesw -A $sillyname -p sctp -m multiport --dports 21,22 -j ACCEPT" ); } sub Policy_Match() { qt1( "$iptables $iptablesw -A $sillyname -m policy --pol ipsec --mode tunnel --dir in -j ACCEPT" ); } sub Physdev_Match() { qt1( "$iptables $iptablesw -A $sillyname -m physdev --physdev-in eth0 -j ACCEPT" ); } sub Physdev_Bridge() { qt1( "$iptables $iptablesw -A $sillyname -m physdev --physdev-is-bridged --physdev-in eth0 --physdev-out eth1 -j ACCEPT" ); } sub IPRange_Match() { if ( $family == F_IPV4 ) { qt1( "$iptables $iptablesw -A $sillyname -m iprange --src-range 192.168.1.5-192.168.1.124 -j ACCEPT" ); } else { qt1( "$iptables $iptablesw -A $sillyname -m iprange --src-range ::1-::2 -j ACCEPT" ); } } sub Recent_Match() { qt1( "$iptables $iptablesw -A $sillyname -m recent --update -j ACCEPT" ); } sub Reap_Option() { ( have_capability( 'RECENT_MATCH' ) && qt1( "$iptables $iptablesw -A $sillyname -m recent --rcheck --seconds 10 --reap" ) ); } sub Owner_Match() { qt1( "$iptables $iptablesw -A $sillyname -m owner --uid-owner 0 -j ACCEPT" ); } sub Owner_Name_Match() { if ( my $name = `id -un 2> /dev/null` ) { chomp $name; qt1( "$iptables $iptablesw -A $sillyname -m owner --uid-owner $name -j ACCEPT" ); } } sub Connmark_Match() { qt1( "$iptables $iptablesw -A $sillyname -m connmark --mark 2 -j ACCEPT" ); } sub Xconnmark_Match() { have_capability( 'CONNMARK_MATCH' ) && qt1( "$iptables $iptablesw -A $sillyname -m connmark --mark 2/0xFF -j ACCEPT" ); } sub Ipp2p_Match() { qt1( "$iptables $iptablesw -A $sillyname -p tcp -m ipp2p --edk -j ACCEPT" ); } sub Old_Ipp2p_Match() { qt1( "$iptables $iptablesw -A $sillyname -p tcp -m ipp2p --ipp2p -j ACCEPT" ) if $capabilities{IPP2P_MATCH}; } sub Length_Match() { qt1( "$iptables $iptablesw -A $sillyname -m length --length 10:20 -j ACCEPT" ); } sub Enhanced_Reject() { if ( $family == F_IPV6 ) { qt1( "$iptables $iptablesw -A $sillyname -j REJECT --reject-with icmp6-adm-prohibited" ); } else { qt1( "$iptables $iptablesw -A $sillyname -j REJECT --reject-with icmp-host-prohibited" ); } } sub Comments() { qt1( qq($iptables -A $sillyname -j ACCEPT -m comment --comment "This is a comment" ) ); } sub Hashlimit_Match() { if ( qt1( "$iptables $iptablesw -A $sillyname -m hashlimit --hashlimit-upto 3/min --hashlimit-burst 3 --hashlimit-name $sillyname --hashlimit-mode srcip -j ACCEPT" ) ) { ! ( $capabilities{OLD_HL_MATCH} = 0 ); } else { have_capability 'OLD_HL_MATCH'; } } sub Old_Hashlimit_Match() { qt1( "$iptables $iptablesw -A $sillyname -m hashlimit --hashlimit 3/min --hashlimit-burst 3 --hashlimit-name $sillyname --hashlimit-mode srcip -j ACCEPT" ); } sub Mark() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j MARK --set-mark 1" ); } sub Xmark() { have_capability( 'MARK' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j MARK --and-mark 0xFF" ); } sub Exmark() { have_capability( 'MARK' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j MARK --set-mark 1/0xFF" ); } sub Connmark() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j CONNMARK --save-mark" ); } sub Xconnmark() { have_capability( 'XCONNMARK_MATCH' ) && have_capability( 'XMARK' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j CONNMARK --save-mark --mask 0xFF" ); } sub New_Tos_Match() { qt1( "$iptables $iptablesw -t mangle -A $sillyname -m tos --tos 0x10/0xff" ); } sub Classify_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j CLASSIFY --set-class 1:1" ); } sub IPMark_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j IPMARK --addr src" ); } sub Tproxy_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -p tcp -j TPROXY --on-port 0 --tproxy-mark 1" ); } sub Mangle_Forward() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -L FORWARD -n" ); } sub Raw_Table() { qt1( "$iptables $iptablesw -t raw -L -n" ); } sub Rawpost_Table() { qt1( "$iptables $iptablesw -t rawpost -L -n" ); } sub Old_IPSet_Match() { my $ipset = $config{IPSET} || 'ipset'; my $result = 0; $ipset = which $ipset unless $ipset =~ '/'; if ( $ipset && -x $ipset ) { qt( "$ipset -X $sillyname" ); if ( qt( "$ipset -N $sillyname iphash" ) ) { if ( qt1( "$iptables $iptablesw -A $sillyname -m set --set $sillyname src -j ACCEPT" ) ) { qt1( "$iptables $iptablesw -F $sillyname" ); $result = $capabilities{IPSET_MATCH} = 1; } qt( "$ipset -X $sillyname" ); } } $result; } sub IPSet_Match() { my $ipset = $config{IPSET} || 'ipset'; my $result = 0; my $fam = $family == F_IPV4 ? 'inet' : 'inet6'; $ipset = which $ipset unless $ipset =~ '/'; $capabilities{IPSET_MATCH_NOMATCH} = $capabilities{IPSET_MATCH_COUNTERS} = 0; if ( $ipset && -x $ipset ) { qt( "$ipset -X $sillyname" ); if ( qt( "$ipset -N $sillyname hash:ip family $fam" ) || qt( "$ipset -N $sillyname iphash" ) ) { if ( qt1( "$iptables $iptablesw -A $sillyname -m set --match-set $sillyname src -j ACCEPT" ) ) { $capabilities{IPSET_MATCH_NOMATCH} = qt1( "$iptables $iptablesw -A $sillyname -m set --match-set $sillyname src --return-nomatch -j ACCEPT" ); $capabilities{IPSET_MATCH_COUNTERS} = qt1( "$iptables $iptablesw -A $sillyname -m set --match-set $sillyname src --packets-lt 100 -j ACCEPT" ); qt1( "$iptables $iptablesw -F $sillyname" ); $result = ! ( $capabilities{OLD_IPSET_MATCH} = 0 ); } else { $result = have_capability 'OLD_IPSET_MATCH'; } qt( "$ipset -X $sillyname" ); } } $result; } sub IPSet_Match_Nomatch() { have_capability 'IPSET_MATCH' && $capabilities{IPSET_MATCH_NOMATCH}; } sub IPSet_Match_Counters() { have_capability 'IPSET_MATCH' && $capabilities{IPSET_MATCH_COUNTERS}; } sub IPSET_V5() { my $ipset = $config{IPSET} || 'ipset'; my $result = 0; $ipset = which $ipset unless $ipset =~ '/'; if ( $ipset && -x $ipset ) { qt( "$ipset -X $sillyname" ); if ( qt( "$ipset -N $sillyname hash:ip family inet" ) ) { $result = 1; qt( "$ipset -X $sillyname" ); } } $result; } sub Usepkttype() { qt1( "$iptables $iptablesw -A $sillyname -m pkttype --pkt-type broadcast -j ACCEPT" ); } sub Addrtype() { qt1( "$iptables $iptablesw -A $sillyname -m addrtype --src-type BROADCAST -j ACCEPT" ); } sub Tarpit_Target() { qt1( "$iptables $iptablesw -A $sillyname -p tcp -j TARPIT" ); } sub Tcpmss_Match() { qt1( "$iptables $iptablesw -A $sillyname -p tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1000:1500 -j ACCEPT" ); } sub Nfqueue_Target() { qt1( "$iptables $iptablesw -A $sillyname -j NFQUEUE --queue-num 4" ); } sub Realm_Match() { qt1( "$iptables $iptablesw -A $sillyname -m realm --realm 1" ); } sub Helper_Match() { qt1( "$iptables $iptablesw -A $sillyname -p tcp --dport 21 -m helper --helper ftp" ); } sub have_helper( $$$ ) { my ( $helper, $proto, $port ) = @_; my $helper_base = $helper; $helper_base =~ s/-\d$//; $helper_base = $helpers_map{$helper_base}; $helper_base =~ s/_HELPER//; $helper_base =~ s/_/-/; if ( $helpers_enabled{lc $helper_base} ) { if ( have_capability 'CT_TARGET' ) { qt1( "$iptables $iptablesw -t raw -A $sillyname -p $proto --dport $port -j CT --helper $helper" ); } else { have_capability 'HELPER_MATCH'; } } } sub Amanda_Helper() { have_helper( 'amanda', 'udp', 10080 ); } sub FTP0_Helper() { have_helper( 'ftp-0', 'tcp', 21 ) and $helpers_aliases{ftp} = 'ftp-0'; } sub FTP_Helper() { have_helper( 'ftp', 'tcp', 21 ) || have_capability 'FTP0_HELPER'; } sub H323_Helpers() { have_helper( 'RAS', 'udp', 1719 ); } sub IRC0_Helper() { have_helper( 'irc-0', 'tcp', 6667 ) and $helpers_aliases{irc} = 'irc-0'; } sub IRC_Helper() { have_helper( 'irc', 'tcp', 6667 ) || IRC0_Helper; } sub Netbios_ns_Helper() { have_helper( 'netbios-ns', 'udp', 137 ); } sub PPTP_Helper() { have_helper( 'pptp', 'tcp', 1729 ); } sub SANE0_Helper() { have_helper( 'sane-0', 'tcp', 6566 ) and $helpers_aliases{sane} = 'sane-0'; } sub SANE_Helper() { have_helper( 'sane', 'tcp', 6566 ) || have_capability 'SANE0_HELPER'; } sub SIP0_Helper() { have_helper( 'sip-0', 'udp', 5060 ) and $helpers_aliases{sip} = 'sip-0'; } sub SIP_Helper() { have_helper( 'sip', 'udp', 5060 ) || have_capability 'SIP0_HELPER'; } sub SNMP_Helper() { have_helper( 'snmp', 'udp', 161 ); } sub TFTP0_Helper() { have_helper( 'tftp-0', 'udp', 69 ) and $helpers_aliases{tftp} = 'tftp-0'; } sub TFTP_Helper() { have_helper( 'tftp', 'udp', 69 ) || have_capability 'TFTP0_HELPER'; } sub Connlimit_Match() { qt1( "$iptables $iptablesw -A $sillyname -m connlimit --connlimit-above 8" ); } sub Time_Match() { qt1( "$iptables $iptablesw -A $sillyname -m time --timestart 11:00" ); } sub Goto_Target() { qt1( "$iptables $iptablesw -A $sillyname -g $sillyname1" ); } sub Log_Target() { qt1( "$iptables $iptablesw -A $sillyname -j LOG" ); } sub Ulog_Target() { qt1( "$iptables $iptablesw -A $sillyname -j ULOG" ); } sub NFLog_Target() { qt1( "$iptables $iptablesw -A $sillyname -j NFLOG" ); } sub Logmark_Target() { qt1( "$iptables $iptablesw -A $sillyname -j LOGMARK" ); } sub Flow_Filter() { $tc && system( "$tc filter add flow help 2>&1 | grep -q ^Usage" ) == 0; } sub Basic_Filter() { $tc && system( "$tc filter add basic help 2>&1 | grep -q ^Usage" ) == 0; } sub Basic_Ematch() { $tc && have_capability( 'BASIC_FILTER' ) && system( "$tc filter add basic help 2>&1 | egrep -q match" ) == 0; } sub Fwmark_Rt_Mask() { $ip && system( "$ip rule add help 2>&1 | grep -q /MASK" ) == 0; } sub Mark_Anywhere() { qt1( "$iptables $iptablesw -A $sillyname -j MARK --set-mark 5" ); } sub Header_Match() { qt1( "$iptables $iptablesw -A $sillyname -m ipv6header --header 255 -j ACCEPT" ); } sub Account_Target() { if ( $family == F_IPV4 ) { qt1( "$iptables $iptablesw -A $sillyname -j ACCOUNT --addr 192.168.1.0/29 --tname $sillyname" ); } else { qt1( "$iptables $iptablesw -A $sillyname -j ACCOUNT --addr 1::/122 --tname $sillyname" ); } } sub Condition_Match() { qt1( "$iptables $iptablesw -A $sillyname -m condition --condition foo" ); } sub Audit_Target() { qt1( "$iptables $iptablesw -A $sillyname -j AUDIT --type drop" ); } sub Iptables_S() { qt1( "$iptables $iptablesw -S INPUT" ) } sub Ct_Target() { my $ct_target; if ( have_capability 'RAW_TABLE' ) { qt1( "$iptables $iptablesw -t raw -N $sillyname" ); $ct_target = qt1( "$iptables $iptablesw -t raw -A $sillyname -j CT --notrack" ); } $ct_target; } sub Statistic_Match() { qt1( "$iptables $iptablesw -A $sillyname -m statistic --mode nth --every 2 --packet 1" ); } sub Imq_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j IMQ --todev 0" ); } sub Dscp_Match() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -m dscp --dscp 0" ); } sub Dscp_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j DSCP --set-dscp 0" ); } sub RPFilter_Match() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -m rpfilter" ); } sub NFAcct_Match() { my $result; if ( qt1( "nfacct add $sillyname" ) ) { $result = qt1( "$iptables $iptablesw -A $sillyname -m nfacct --nfacct-name $sillyname" ); qt( "$iptables -D $sillyname -m nfacct --nfacct-name $sillyname" ); qt( "nfacct del $sillyname" ); } $result; } sub GeoIP_Match() { qt1( "$iptables $iptablesw -A $sillyname -m geoip --src-cc US" ); } sub Checksum_Target() { have_capability( 'MANGLE_ENABLED' ) && qt1( "$iptables $iptablesw -t mangle -A $sillyname -j CHECKSUM --checksum-fill" ); } sub Arptables_JF() { my $arptables = $config{ARPTABLES}; $arptables = which( 'arptables' ) unless supplied $arptables; if ( $arptables && -f $arptables && -x _ ) { $config{ARPTABLES} = $arptables; qt( "$arptables -L OUT" ); } } sub Iface_Match() { qt1( "$iptables $iptablesw -A $sillyname -m iface --iface lo --loopback" ); } sub Tcpmss_Target() { qt1( "$iptables $iptablesw -A $sillyname -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu" ); } our %detect_capability = ( ACCOUNT_TARGET =>\&Account_Target, AMANDA_HELPER => \&Amanda_Helper, ARPTABLESJF => \&Arptables_JF, AUDIT_TARGET => \&Audit_Target, ADDRTYPE => \&Addrtype, BASIC_FILTER => \&Basic_Filter, BASIC_EMATCH => \&Basic_Ematch, CHECKSUM_TARGET => \&Checksum_Target, CLASSIFY_TARGET => \&Classify_Target, CONDITION_MATCH => \&Condition_Match, COMMENTS => \&Comments, CONNLIMIT_MATCH => \&Connlimit_Match, CONNMARK => \&Connmark, CONNMARK_MATCH => \&Connmark_Match, CONNTRACK_MATCH => \&Conntrack_Match, CT_TARGET => \&Ct_Target, DSCP_MATCH => \&Dscp_Match, DSCP_TARGET => \&Dscp_Target, ENHANCED_REJECT => \&Enhanced_Reject, EMULTIPORT => \&Emultiport, EXMARK => \&Exmark, FLOW_FILTER => \&Flow_Filter, FTP_HELPER => \&FTP_Helper, FTP0_HELPER => \&FTP0_Helper, FWMARK_RT_MASK => \&Fwmark_Rt_Mask, GEOIP_MATCH => \&GeoIP_Match, GOTO_TARGET => \&Goto_Target, H323_HELPER => \&H323_Helpers, HASHLIMIT_MATCH => \&Hashlimit_Match, HEADER_MATCH => \&Header_Match, HELPER_MATCH => \&Helper_Match, IFACE_MATCH => \&Iface_Match, IMQ_TARGET => \&Imq_Target, IPMARK_TARGET => \&IPMark_Target, IPP2P_MATCH => \&Ipp2p_Match, IPRANGE_MATCH => \&IPRange_Match, IPSET_MATCH => \&IPSet_Match, IPSET_MATCH_NOMATCH => \&IPSet_Match_Nomatch, IPSET_MATCH_COUNTERS => \&IPSet_Match_Counters, IRC_HELPER => \&IRC_Helper, IRC0_HELPER => \&IRC0_Helper, OLD_IPSET_MATCH => \&Old_IPSet_Match, IPSET_V5 => \&IPSET_V5, IPTABLES_S => \&Iptables_S, KLUDGEFREE => \&Kludgefree, LENGTH_MATCH => \&Length_Match, LOGMARK_TARGET => \&Logmark_Target, LOG_TARGET => \&Log_Target, ULOG_TARGET => \&Ulog_Target, NFLOG_TARGET => \&NFLog_Target, MANGLE_ENABLED => \&Mangle_Enabled, MANGLE_FORWARD => \&Mangle_Forward, MARK => \&Mark, MARK_ANYWHERE => \&Mark_Anywhere, MASQUERADE_TGT => \&Masquerade_Tgt, MULTIPORT => \&Multiport, NAT_ENABLED => \&Nat_Enabled, NETBIOS_NS_HELPER => \&Netbios_ns_Helper, NEW_CONNTRACK_MATCH => \&New_Conntrack_Match, NFACCT_MATCH => \&NFAcct_Match, NFQUEUE_TARGET => \&Nfqueue_Target, OLD_CONNTRACK_MATCH => \&Old_Conntrack_Match, OLD_HL_MATCH => \&Old_Hashlimit_Match, OLD_IPP2P_MATCH => \&Old_Ipp2p_Match, NEW_TOS_MATCH => \&New_Tos_Match, OWNER_MATCH => \&Owner_Match, OWNER_NAME_MATCH => \&Owner_Name_Match, PERSISTENT_SNAT => \&Persistent_Snat, PHYSDEV_BRIDGE => \&Physdev_Bridge, PHYSDEV_MATCH => \&Physdev_Match, POLICY_MATCH => \&Policy_Match, PPTP_HELPER => \&PPTP_Helper, RAW_TABLE => \&Raw_Table, RAWPOST_TABLE => \&Rawpost_Table, REALM_MATCH => \&Realm_Match, REAP_OPTION => \&Reap_Option, RECENT_MATCH => \&Recent_Match, RPFILTER_MATCH => \&RPFilter_Match, SANE_HELPER => \&SANE_Helper, SANE0_HELPER => \&SANE0_Helper, SIP_HELPER => \&SIP_Helper, SIP0_HELPER => \&SIP0_Helper, SNMP_HELPER => \&SNMP_Helper, STATISTIC_MATCH => \&Statistic_Match, TARPIT_TARGET => \&Tarpit_Target, TCPMSS_MATCH => \&Tcpmss_Match, TCPMSS_TARGET => \&Tcpmss_Target, TFTP_HELPER => \&TFTP_Helper, TFTP0_HELPER => \&TFTP0_Helper, TIME_MATCH => \&Time_Match, TPROXY_TARGET => \&Tproxy_Target, UDPLITEREDIRECT => \&Udpliteredirect, USEPKTTYPE => \&Usepkttype, XCONNMARK_MATCH => \&Xconnmark_Match, XCONNMARK => \&Xconnmark, XMARK => \&Xmark, XMULTIPORT => \&Xmultiport, ); sub detect_capability( $ ) { my $capability = shift; my $function = $detect_capability{ $capability }; assert( ( reftype( $function ) || '' ) eq 'CODE' ); $function->(); } # # Report the passed capability # sub have_capability( $;$ ) { my ( $capability, $required ) = @_; our %detect_capability; my $setting = $capabilities{ $capability }; $setting = $capabilities{ $capability } = detect_capability( $capability ) unless defined $setting; $used{$capability} = $required ? 2 : 1 if $setting; $setting; } # # Determine which optional facilities are supported by iptables/netfilter # sub determine_capabilities() { my $pid = $$; $capabilities{CAPVERSION} = $globals{CAPVERSION}; $capabilities{WAIT_OPTION} = $iptablesw; determine_kernelversion; $sillyname = "fooX$pid"; $sillyname1 = "foo1X$pid"; qt1( "$iptables $iptablesw -N $sillyname" ); qt1( "$iptables $iptablesw -N $sillyname1" ); fatal_error 'Your kernel/iptables do not include state match support. No version of Shorewall will run on this system' unless qt1( "$iptables $iptablesw -A $sillyname -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT") || qt1( "$iptables $iptablesw -A $sillyname -m state --state ESTABLISHED,RELATED -j ACCEPT");; $globals{KLUDGEFREE} = $capabilities{KLUDGEFREE} = detect_capability 'KLUDGEFREE'; unless ( $config{ LOAD_HELPERS_ONLY } ) { # # Using 'detect_capability()' is a bit less efficient than calling the individual detection # functions but it ensures that %detect_capability is initialized properly. # $capabilities{NAT_ENABLED} = detect_capability( 'NAT_ENABLED' ); $capabilities{PERSISTENT_SNAT} = detect_capability( 'PERSISTENT_SNAT' ); $capabilities{MANGLE_ENABLED} = detect_capability( 'MANGLE_ENABLED' ); if ( $capabilities{CONNTRACK_MATCH} = detect_capability( 'CONNTRACK_MATCH' ) ) { $capabilities{NEW_CONNTRACK_MATCH} = detect_capability( 'NEW_CONNTRACK_MATCH' ); $capabilities{OLD_CONNTRACK_MATCH} = detect_capability( 'OLD_CONNTRACK_MATCH' ); } else { $capabilities{NEW_CONNTRACK_MATCH} = ''; $capabilities{OLD_CONNTRACK_MATCH} = ''; } $capabilities{ MULTIPORT } = detect_capability( 'MULTIPORT' ); $capabilities{XMULTIPORT} = detect_capability( 'XMULTIPORT' ); $capabilities{EMULTIPORT} = detect_capability( 'EMULTIPORT' ); $capabilities{POLICY_MATCH} = detect_capability( 'POLICY_MATCH' ); if ( $capabilities{PHYSDEV_MATCH} = detect_capability( 'PHYSDEV_MATCH' ) ) { $capabilities{PHYSDEV_BRIDGE} = detect_capability( 'PHYSDEV_BRIDGE' ); } else { $capabilities{PHYSDEV_BRIDGE} = ''; } $capabilities{IPRANGE_MATCH} = detect_capability( 'IPRANGE_MATCH' ); $capabilities{RECENT_MATCH} = detect_capability( 'RECENT_MATCH' ); $capabilities{REAP_OPTION} = detect_capability( 'REAP_OPTION' ); $capabilities{OWNER_MATCH} = detect_capability( 'OWNER_MATCH' ); $capabilities{OWNER_NAME_MATCH} = detect_capability( 'OWNER_NAME_MATCH' ); $capabilities{CONNMARK_MATCH} = detect_capability( 'CONNMARK_MATCH' ); $capabilities{XCONNMARK_MATCH} = detect_capability( 'XCONNMARK_MATCH' ); $capabilities{IPP2P_MATCH} = detect_capability( 'IPP2P_MATCH' ); $capabilities{OLD_IPP2P_MATCH} = detect_capability( 'OLD_IPP2P_MATCH' ); $capabilities{LENGTH_MATCH} = detect_capability( 'LENGTH_MATCH' ); $capabilities{ENHANCED_REJECT} = detect_capability( 'ENHANCED_REJECT' ); $capabilities{COMMENTS} = detect_capability( 'COMMENTS' ); $capabilities{OLD_HL_MATCH} = detect_capability( 'OLD_HL_MATCH' ); $capabilities{HASHLIMIT_MATCH} = detect_capability( 'HASHLIMIT_MATCH' ); $capabilities{MARK} = detect_capability( 'MARK' ); $capabilities{XMARK} = detect_capability( 'XMARK' ); $capabilities{EXMARK} = detect_capability( 'EXMARK' ); $capabilities{CONNMARK} = detect_capability( 'CONNMARK' ); $capabilities{XCONNMARK} = detect_capability( 'XCONNMARK' ); $capabilities{CLASSIFY_TARGET} = detect_capability( 'CLASSIFY_TARGET' ); $capabilities{IPMARK_TARGET} = detect_capability( 'IPMARK_TARGET' ); $capabilities{TPROXY_TARGET} = detect_capability( 'TPROXY_TARGET' ); $capabilities{MANGLE_FORWARD} = detect_capability( 'MANGLE_FORWARD' ); $capabilities{RAW_TABLE} = detect_capability( 'RAW_TABLE' ); $capabilities{RAWPOST_TABLE} = detect_capability( 'RAWPOST_TABLE' ); $capabilities{IPSET_MATCH} = detect_capability( 'IPSET_MATCH' ); $capabilities{USEPKTTYPE} = detect_capability( 'USEPKTTYPE' ); $capabilities{ADDRTYPE} = detect_capability( 'ADDRTYPE' ); $capabilities{TCPMSS_MATCH} = detect_capability( 'TCPMSS_MATCH' ); $capabilities{NFQUEUE_TARGET} = detect_capability( 'NFQUEUE_TARGET' ); $capabilities{REALM_MATCH} = detect_capability( 'REALM_MATCH' ); $capabilities{CONNLIMIT_MATCH} = detect_capability( 'CONNLIMIT_MATCH' ); $capabilities{TIME_MATCH} = detect_capability( 'TIME_MATCH' ); $capabilities{GOTO_TARGET} = detect_capability( 'GOTO_TARGET' ); $capabilities{LOG_TARGET} = detect_capability( 'LOG_TARGET' ); $capabilities{ULOG_TARGET} = detect_capability( 'ULOG_TARGET' ); $capabilities{NFLOG_TARGET} = detect_capability( 'NFLOG_TARGET' ); $capabilities{LOGMARK_TARGET} = detect_capability( 'LOGMARK_TARGET' ); $capabilities{FLOW_FILTER} = detect_capability( 'FLOW_FILTER' ); $capabilities{FWMARK_RT_MASK} = detect_capability( 'FWMARK_RT_MASK' ); $capabilities{MARK_ANYWHERE} = detect_capability( 'MARK_ANYWHERE' ); $capabilities{ACCOUNT_TARGET} = detect_capability( 'ACCOUNT_TARGET' ); $capabilities{HEADER_MATCH} = detect_capability( 'HEADER_MATCH' ); $capabilities{AUDIT_TARGET} = detect_capability( 'AUDIT_TARGET' ); $capabilities{IPSET_V5} = detect_capability( 'IPSET_V5' ); $capabilities{CONDITION_MATCH} = detect_capability( 'CONDITION_MATCH' ); $capabilities{IPTABLES_S} = detect_capability( 'IPTABLES_S' ); $capabilities{BASIC_FILTER} = detect_capability( 'BASIC_FILTER' ); $capabilities{BASIC_EMATCH} = detect_capability( 'BASIC_EMATCH' ); $capabilities{CT_TARGET} = detect_capability( 'CT_TARGET' ); $capabilities{STATISTIC_MATCH} = detect_capability( 'STATISTIC_MATCH' ); $capabilities{IMQ_TARGET} = detect_capability( 'IMQ_TARGET' ); $capabilities{DSCP_MATCH} = detect_capability( 'DSCP_MATCH' ); $capabilities{DSCP_TARGET} = detect_capability( 'DSCP_TARGET' ); $capabilities{GEOIP_MATCH} = detect_capability( 'GEOIP_MATCH' ); $capabilities{RPFILTER_MATCH} = detect_capability( 'RPFILTER_MATCH' ); $capabilities{NFACCT_MATCH} = detect_capability( 'NFACCT_MATCH' ); $capabilities{CHECKSUM_TARGET} = detect_capability( 'CHECKSUM_TARGET' ); $capabilities{ARPTABLESJF} = detect_capability( 'ARPTABLESJF' ); $capabilities{MASQUERADE_TGT} = detect_capability( 'MASQUERADE_TGT' ); $capabilities{UDPLITEREDIRECT} = detect_capability( 'UDPLITEREDIRECT' ); $capabilities{NEW_TOS_MATCH} = detect_capability( 'NEW_TOS_MATCH' ); $capabilities{TARPIT_TARGET} = detect_capability( 'TARPIT_TARGET' ); $capabilities{IFACE_MATCH} = detect_capability( 'IFACE_MATCH' ); $capabilities{TCPMSS_TARGET} = detect_capability( 'TCPMSS_TARGET' ); unless ( have_capability 'CT_TARGET' ) { $capabilities{HELPER_MATCH} = detect_capability 'HELPER_MATCH'; } } } # # Require the passed capability # sub require_capability( $$$ ) { my ( $capability, $description, $singular ) = @_; fatal_error "$description require${singular} $capdesc{$capability} in your kernel and iptables" unless have_capability $capability, 1; } # # Return Kernel Version # sub kernel_version() { $capabilities{KERNELVERSION} } # # Set default config path # sub ensure_config_path() { my $f = "$globals{SHAREDIR}/configpath"; unless ( $config{CONFIG_PATH} ) { fatal_error "$f does not exist" unless -f $f; open_file $f; while ( read_a_line( NORMAL_READ ) ) { if ( $currentline =~ /^\s*([a-zA-Z]\w*)=(.*?)\s*$/ ) { my ($var, $val) = ($1, $2); $config{$var} = ( $val =~ /\"([^\"]*)\"$/ ? $1 : $val ) if exists $config{$var}; } else { fatal_error "Unrecognized entry"; } } fatal_error "CONFIG_PATH not found in $f" unless $config{CONFIG_PATH}; } @config_path = split /:/, $config{CONFIG_PATH}; for ( @config_path ) { $_ .= '/' unless m|/$|; } if ( $shorewall_dir ) { $shorewall_dir = getcwd if $shorewall_dir =~ m|^(\./*)+$|; $shorewall_dir .= '/' unless $shorewall_dir =~ m|/$|; unshift @config_path, $shorewall_dir if $shorewall_dir ne $config_path[0]; $config{CONFIG_PATH} = join ':', @config_path; } } # # Set $shorewall_dir # sub set_shorewall_dir( $ ) { $shorewall_dir = shift; fatal_error "$shorewall_dir is not an existing directory" unless -d $shorewall_dir; ensure_config_path; } # # Update the configuration file # sub conditional_quote( $ ) { my $val = shift; unless ( $val =~ /^[-\w\/\.]*$/ ) { # # Funny characters (including whitespace) -- use double quotes unless the thing is single-quoted # $val = qq("$val") unless $val =~ /^'.+'$/; } $val; } # # Update the shorewall[6].conf file. Save the current file with a .bak suffix. # sub update_default($$) { my ( $var, $val ) = @_; $config{$var} = $val unless defined $config{$var}; } sub update_config_file( $ ) { my ( $annotate ) = @_; sub is_set( $ ) { my $value = $_[0]; defined( $value ) && lc( $value ) eq 'yes'; } my $wide = is_set $config{WIDE_TC_MARKS}; my $high = is_set $config{HIGH_ROUTE_MARKS}; # # Establish default values for the mark layout items # $config{TC_BITS} = ( $wide ? 14 : 8 ) unless defined $config{TC_BITS}; $config{MASK_BITS} = ( $wide ? 16 : 8 ) unless defined $config{MASK_BITS}; $config{PROVIDER_OFFSET} = ( $high ? $wide ? 16 : 8 : 0 ) unless defined $config{PROVIDER_OFFSET}; $config{PROVIDER_BITS} = 8 unless defined $config{PROVIDER_BITS}; unless ( supplied $config{LOGLIMIT} ) { if ( $config{LOGRATE} || $config{LOGBURST} ) { my $limit; if ( supplied $config{LOGRATE} ) { fatal_error"Invalid LOGRATE ($config{LOGRATE})" unless $config{LOGRATE} =~ /^\d+\/(second|minute)$/; $limit = $config{LOGRATE}; } if ( supplied $config{LOGBURST} ) { fatal_error"Invalid LOGBURST ($config{LOGBURST})" unless $config{LOGBURST} =~ /^\d+$/; $limit .= ":$config{LOGBURST}"; } $config{LOGLIMIT} = $limit; } } unless ( supplied $config{BLACKLIST} ) { if ( $config{BLACKLISTNEWONLY} ) { default_yes_no 'BLACKLISTNEWONLY' , ''; if ( have_capability 'RAW_TABLE' ) { $globals{BLACKLIST_STATES} = $config{BLACKLISTNEWONLY} ? 'NEW,INVALID,UNTRACKED' : 'NEW,ESTABLISHED,INVALID,UNTRACKED'; } else { $globals{BLACKLIST_STATES} = $config{BLACKLISTNEWONLY} ? 'NEW,INVALID' : 'NEW,ESTABLISHED,INVALID'; } $config{BLACKLIST} = $globals{BLACKLIST_STATES}; } } update_default( 'USE_DEFAULT_RT', 'No' ); update_default( 'EXPORTMODULES', 'No' ); update_default( 'RESTART', 'reload' ); my $fn; unless ( -d "$globals{SHAREDIR}/configfiles/" ) { # # Debian or derivative # $fn = $annotate ? "$shorewallrc{SHAREDIR}/doc/${product}/default-config/${product}.conf.annotated" : "$shorewallrc{SHAREDIR}/doc/${product}/default-config/${product}.conf"; } else { # # The rest of the World # $fn = $annotate ? "$globals{SHAREDIR}/configfiles/${product}.conf.annotated" : "$globals{SHAREDIR}/configfiles/${product}.conf"; } if ( -f $fn ) { my ( $template, $output ); open $template, '<' , $fn or fatal_error "Unable to open $fn: $!"; unless ( open $output, '>', "$configfile.updated" ) { close $template; fatal_error "Unable to open $configfile.updated for output: $!"; } while ( <$template> ) { if ( /^(\w+)="?(.*?)"?$/ ) { # # Option assignment -- get value and default # my ($var, $val, $default ) = ( $1, $config{$1}, $2 ); unless ( supplied $val ) { # # Value is either undefined (option not in config file) or is '' # if ( defined $val ) { # # OPTION='' - use default if 'Yes' or 'No' # $config{$var} = $val = $default if $default eq 'Yes' || $default eq 'No'; } else { # # Wasn't mentioned in old file - use default value # $config{$var} = $val = $default; } } $val = conditional_quote $val; $_ = "$var=$val\n"; } print $output "$_"; } close $template; my $heading_printed; for ( grep ! $converted{$_} , keys %deprecated ) { if ( supplied( my $val = $config{$_} ) ) { if ( lc $val ne $deprecated{$_} ) { unless ( $heading_printed ) { print $output <<'EOF'; ################################################################################# # D E P R E C A T E D # O P T I O N S ################################################################################# EOF $heading_printed = 1; } $val = conditional_quote $val; print $output "$_=$val\n\n"; warning_message "Deprecated option $_ is being set in your $product.conf file"; } } } close $output; fatal_error "Can't rename $configfile to $configfile.bak: $!" unless rename $configfile, "$configfile.bak"; fatal_error "Can't rename $configfile.updated to $configfile: $!" unless rename "$configfile.updated", $configfile; if ( system( "diff -q $configfile $configfile.bak > /dev/null" ) ) { progress_message3 "Configuration file $configfile updated - old file renamed $configfile.bak"; } else { if ( rename "$configfile.bak", $configfile ) { progress_message3 "No update required to configuration file $configfile; $configfile.bak not saved"; } else { warning_message "Unable to rename $configfile.bak to $configfile"; progress_message3 "No update required to configuration file $configfile"; } } } else { fatal_error "$fn does not exist"; } } # # Small functions called by get_configuration. We separate them so profiling is more useful # sub process_shorewall_conf( $$ ) { my ( $update, $annotate ) = @_; my $file = find_file "$product.conf"; if ( -f $file ) { $globals{CONFIGDIR} = $configfile = $file; $globals{CONFIGDIR} =~ s/$product.conf//; if ( -r _ ) { open_file $file; first_entry "Processing $file..."; # # Don't expand shell variables or allow embedded scripting # while ( read_a_line( STRIP_COMMENTS | SUPPRESS_WHITESPACE | CHECK_GUNK ) ) { if ( $currentline =~ /^\s*([a-zA-Z]\w*)=(.*?)\s*$/ ) { my ($var, $val) = ($1, $2); if ( exists $config{$var} ) { if ( $eliminated{$var} && ! $update ) { fatal_error "The $var configuration option has been superceded - please run '$product update'"; } } elsif ( exists $renamed{$var} ) { $var = $renamed{$var}; } else { if ( $eliminated{$var} ) { warning_message "The $var configuration option is no longer supported"; } else { warning_message "Unknown configuration option ($var) ignored"; } next; } $config{$var} = ( $val =~ /\"([^\"]*)\"$/ ? $1 : $val ); warning_message "Option $var=$val is deprecated" if $deprecated{$var} && supplied $val && lc $config{$var} ne $deprecated{$var}; } else { fatal_error "Unrecognized $product.conf entry"; } } } else { fatal_error "Cannot read $file (Hint: Are you root?)"; } } else { fatal_error "$file does not exist!"; } # # Now update the config file if asked # update_config_file( $annotate ) if $update; # # Config file update requires that the option values not have # Shell variables expanded. We do that now. # for ( values %config ) { if ( supplied $_ ) { expand_variables( $_ ) unless /^'(.+)'$/; } } } # # Process the records in the capabilities file # sub read_capabilities() { while ( read_a_line( STRIP_COMMENTS | SUPPRESS_WHITESPACE | CHECK_GUNK ) ) { if ( $currentline =~ /^([a-zA-Z]\w*)=(.*)$/ ) { my ($var, $val) = ($1, $2); unless ( exists $capabilities{$var} ) { warning_message "Unknown capability ($var) ignored"; next; } $val = $val =~ /^\"([^\"]*)\"$/ ? $1 : $val; $capabilities{$var} = $var =~ /VERSION$/ ? $val : $val ne ''; } else { fatal_error "Unrecognized capabilities entry"; } } unless ( $capabilities{KERNELVERSION} ) { warning_message "Your capabilities file does not contain a Kernel Version -- using 2.6.30"; $capabilities{KERNELVERSION} = 20630; } # # Assume that this is available when using an old capabilities files # $capabilities{TCPMSS_TARGET} = 1 if ( ( $capabilities{CAPVERSION} || 0 ) < 40609 ); $helpers_aliases{ftp} = 'ftp-0', $capabilities{FTP_HELPER} = 1 if $capabilities{FTP0_HELPER}; $helpers_aliases{irc} = 'irc-0', $capabilities{IRC_HELPER} = 1 if $capabilities{IRC0_HELPER}; $helpers_aliases{sane} = 'sane-0', $capabilities{SANE_HELPER} = 1 if $capabilities{SANE0_HELPER}; $helpers_aliases{sip} = 'sip-0', $capabilities{SIP_HELPER} = 1 if $capabilities{SIP0_HELPER}; $helpers_aliases{tftp} = 'tftp-0', $capabilities{TFTP_HELPER} = 1 if $capabilities{TFTP0_HELPER}; for ( keys %capabilities ) { $capabilities{$_} = '' unless defined $capabilities{$_}; } $globals{KLUDGEFREE} = $capabilities{KLUDGEFREE}; $iptablesw = '-w' if $capabilities{WAIT_OPTION}; } # # Get the system's capabilities by probing # sub get_capabilities($) { $iptables = $config{$toolNAME}; if ( $iptables ) { fatal_error "$toolNAME=$iptables does not exist or is not executable" unless -x $iptables; } else { fatal_error "Can't find $toolname executable" unless $iptables = which $toolname; } # # Determine if iptables supports the -w option # $iptablesw = qt1( "$iptables -w -L -n") ? '-w' : ''; my $iptables_restore=$iptables . '-restore'; fatal_error "$iptables_restore does not exist or is not executable" unless -x $iptables_restore; $tc = $config{TC} || which 'tc'; if ( $tc ) { fatal_error "TC=$tc does not exist or is not executable" unless -x $tc; } $ip = $config{IP} || which 'ip'; if ( $ip ) { fatal_error "IP=$ip does not exist or is not executable" unless -x $ip; } load_kernel_modules; determine_capabilities unless $_[0]; } # # Deal with options that we no longer support # sub unsupported_yes_no( $ ) { my $option = shift; default_yes_no $option, ''; fatal_error "$option=Yes is not supported by Shorewall $globals{VERSION}" if $config{$option}; } sub unsupported_yes_no_warning( $ ) { my $option = shift; default_yes_no $option, ''; warning_message "$option=Yes is not supported by Shorewall $globals{VERSION}" if $config{$option}; } # # Process the params file # sub get_params( $ ) { my $export = $_[0]; my $cygwin = ( $shorewallrc{HOST} eq 'cygwin' ); my $fn = find_file 'params'; my %reserved = ( COMMAND => 1, CONFDIR => 1, SHAREDIR => 1, VARDIR => 1 ); if ( -f $fn ) { my $shellpath = $export ? '/bin/sh' : $config{SHOREWALL_SHELL} || '/bin/sh'; $shellpath = which( $shellpath ) unless $shellpath =~ '/'; fatal_error "SHOREWALL_SHELL ($shellpath) is not found or is not executable" unless -x $shellpath; progress_message2 "Processing $fn ..."; my $command = "$shellpath $FindBin::Bin/getparams $fn " . join( ':', @config_path ) . " $family"; # # getparams silently sources the params file under 'set -a', then executes 'export -p' # my @params = `$command`; fatal_error "Processing of $fn failed" if $?; if ( $debug ) { print "Params:\n"; print $_ for @params; } my ( $variable , $bug ); if ( $params[0] =~ /^declare/ ) { # # getparams was interpreted by bash # # - Variable names are preceded by 'declare -x ' # - Param values are delimited by double quotes # - Embedded double quotes are escaped with '\\' # - Valueless variables are supported (e.g., 'declare -x foo') # $shell = BASH; for ( @params ) { chomp; if ( $cygwin && /^declare -x (.*?)="(.*)"$/ ) { $params{$1} = $2 unless $1 eq '_'; } elsif ( /^declare -x (.*?)="(.*[^\\])"$/ ) { $params{$1} = $2 unless $1 eq '_'; } elsif ( /^declare -x (.*?)="(.*)$/ ) { $params{$variable=$1} = $2 eq '"' ? '' : "${2}\n"; } elsif ( /^declare -x (.*)\s+$/ || /^declare -x (.*)=""$/ ) { $params{$1} = ''; } else { if ($variable) { s/"$//; $params{$variable} .= $_; } else { warning_message "Param line ($_) ignored" unless $bug++; } } } } elsif ( $params[0] =~ /^export .*?="/ || $params[0] =~ /^export [^\s=]+\s*$/ ) { # # getparams interpreted by older (e.g., RHEL 5) Bash # # - Variable names preceded by 'export ' # - Variable values are delimited by double quotes # - Embedded double quotes are escaped with '\' # - Valueless variables ( e.g., 'export foo') are supported # $shell = OLDBASH; for ( @params ) { chomp; if ( $cygwin && /^export (.*?)="(.*)"$/ ) { $params{$1} = $2 unless $1 eq '_'; } elsif ( /^export (.*?)="(.*[^\\])"$/ ) { $params{$1} = $2 unless $1 eq '_'; } elsif ( /^export (.*?)="(.*)$/ ) { $params{$variable=$1} = $2 eq '"' ? '' : "${2}\n"; } elsif ( /^export ([^\s=]+)\s*$/ || /^export (.*)=""$/ ) { $params{$1} = ''; } else { if ($variable) { s/"$//; $params{$variable} .= $_; } else { warning_message "Param line ($_) ignored" unless $bug++; } } } } else { # # getparams was interpreted by dash/ash/busybox # # - Variable name preceded by 'export ' # - Param values are delimited by single quotes. # - Embedded single quotes are transformed to the five characters '"'"' # $shell = ASH; for ( @params ) { chomp; if ( /^export (.*?)='(.*'"'"')$/ ) { $params{$variable=$1}="${2}\n"; } elsif ( /^export (.*?)='(.*)'$/ ) { $params{$1} = $2 unless $1 eq '_'; } elsif ( /^export (.*?)='(.*)$/ ) { $params{$variable=$1}="${2}\n"; } else { if ($variable) { s/'$//; $params{$variable} .= $_; } else { warning_message "Param line ($_) ignored" unless $bug++; } } } } for ( keys %params ) { if ( /[^\w]/ ) { # # Useless variable with special characters in its name # delete $params{$_}; } elsif ( /^(?:SHLVL|OLDPWD)$/ ) { # # The shell running getparams generates those # delete $params{$_}; } else { unless ( $_ eq 'SHOREWALL_INIT_SCRIPT' ) { fatal_error "The variable name $_ is reserved and may not be set in the params file" if /^SW_/ || /^SHOREWALL_/ || ( exists $config{$_} && ! exists $ENV{$_} ) || exists $reserved{$_}; } $params{$_} = '' unless defined $params{$_}; } } if ( $debug ) { print "PARAMS:\n"; my $value; while ( ($variable, $value ) = each %params ) { print " $variable='$value'\n" unless $compiler_params{$variable}; } } } add_variables %params; } # # Add an entry to %param, %variabless and to %compiler_params # sub add_param( $$ ) { my ( $param, $value ) = @_; $params{$param} = $value; $variables{$param} = $value; $compiler_params{$param} = 1; } # # Add variables from a hash # sub add_variables( \% ) { while ( my ( $var, $val ) = each %{$_[0]} ) { $variables{$var} = $val; } } # # emit param=value for each param set in the params file # sub export_params() { my $count = 0; for my $param ( sort keys %params ) { # # Don't export params added by the compiler # next if exists $compiler_params{$param}; my $value = $params{$param}; chomp $value; # # Values in %params are generated from the output of 'export -p'. # The different shells have different conventions for delimiting # the value and for escaping embedded instances of the delimiter. # The following logic removes the escape characters. # if ( $shell == BASH ) { $value =~ s/\\"/"/g; } elsif ( $shell == OLDBASH ) { $value =~ s/\\'/'/g; $value =~ s/\\"/"/g; $value =~ s/\\\\/\\/g; } else { $value =~ s/'"'"'/'/g; } # # Don't export pairs from %ENV # if ( defined $ENV{$param} ) { next if $value eq $ENV{$param}; } elsif ( exists $ENV{$param} ) { next unless supplied $value; } emit "#\n# From the params file\n#" unless $count++; # # We will use double quotes and escape embedded quotes with \. # if ( $value =~ /^"[^"]*"$/ ) { emit "$param=$value"; } elsif ( $value =~ /[\s()['"]/ ) { $value =~ s/"/\\"/g; emit "$param='$value'"; } else { emit "$param=$value"; } } } # # Walk the CONFIG_PATH converting FORMAT and COMMENT lines to compiler directives # sub convert_to_directives() { my $sharedir = $shorewallrc{SHAREDIR}; # # Make a copy of @config_path so that the for-loop below doesn't clobber that list # my @path = @config_path; $sharedir =~ s|/+$||; my $dirtest = qr|^$sharedir/+shorewall6?(?:/.*)?$|; progress_message3 "Converting 'FORMAT', 'SECTION' and 'COMMENT' lines to compiler directives..."; for my $dir ( @path ) { unless ( $dir =~ /$dirtest/ ) { if ( ! -w $dir ) { warning_message "$dir not processed (not writeable)"; } else { $dir =~ s|/+$||; opendir( my $dirhandle, $dir ) || fatal_error "Cannot open directory $dir for reading:$!"; while ( my $file = readdir( $dirhandle ) ) { unless ( $file eq 'capabilities' || $file eq 'params' || $file =~ /^shorewall6?.conf$/ || $file =~ /\.bak$/ ) { $file = "$dir/$file"; if ( -f $file && -w _ ) { # # writeable regular file # my $result = system << "EOF"; perl -pi.bak -e '/^\\s*FORMAT\\s+/ && s/FORMAT/?FORMAT/; /^\\s*SECTION\\s+/ && s/SECTION/?SECTION/; if ( /^\\s*COMMENT\\s+/ ) { s/COMMENT/?COMMENT/; } elsif ( /^\\s*COMMENT\\s*\$/ ) { s/COMMENT/?COMMENT/; }' $file EOF if ( $result == 0 ) { if ( system( "diff -q $file ${file}.bak > /dev/null" ) ) { progress_message3 " File $file updated - old file renamed ${file}.bak"; } elsif ( rename "${file}.bak" , $file ) { progress_message " File $file not updated -- no bare 'COMMENT', 'SECTION' or 'FORMAT' lines found"; progress_message " File $file not updated -- no bare 'COMMENT' or 'FORMAT' lines found"; } else { warning message "Unable to rename ${file}.bak to $file:$!"; } } else { warning_message ("Unable to update file $file" ); } } else { warning_message( "$file skipped (not writeable)" ) unless -d _; } } } closedir $dirhandle; } } } } # # - Process the params file # - Read the shorewall.conf file # - Read the capabilities file, if any # - establish global hashes %params, %config , %globals and %capabilities # sub get_configuration( $$$$ ) { ( my ( $export, $update, $annotate ) , $checkinline ) = @_; $globals{EXPORT} = $export; our ( $once, @originalinc ); @originalinc = @INC unless $once++; ensure_config_path; my $default_path = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin'; default 'PATH' , $default_path; if ( supplied( $ENV{PATH} ) ) { $ENV{PATH} .= ":$default_path"; } else { $ENV{PATH} = $default_path; } my $have_capabilities; if ( $export || $> != 0 ) { # # Compiling for export or user not root -- must use a capabilties file # We read it before processing the .conf file so that 'update' has # the capabilities. # unless ( open_file 'capabilities' ) { fatal_error "The -e compiler option requires a capabilities file" if $export; fatal_error "Compiling under non-root uid requires a capabilities file"; } read_capabilities; $have_capabilities = 1; } elsif ( open_file 'capabilities' ) { read_capabilities; $have_capabilities = 1; } get_params( $export ); process_shorewall_conf( $update, $annotate ); ensure_config_path; @INC = @originalinc; unshift @INC, @config_path; # # get_capabilities requires that the true settings of these options be established # default 'MODULE_PREFIX', 'ko ko.gz o o.gz gz'; default_yes_no 'LOAD_HELPERS_ONLY' , 'Yes'; if ( ! $export && $> == 0 ) { get_capabilities($have_capabilities); } my ( $val, $all ); if ( supplied ( $val = $config{HELPERS} ) ) { if ( $val eq 'none' ) { $val = ''; } } else { $val = join( ',', grep $_ !~ /-0$/, keys %helpers_enabled ); $all = 1; } if ( supplied $val ) { my %helpers_temp = %helpers_enabled; $helpers_temp{$_} = 0 for keys %helpers_temp; my @helpers = split_list ( $val, 'helper' ); for ( @helpers ) { my $name = $_; if ( exists $helpers_enabled{$name} ) { s/-/_/; if ( $all ) { $helpers_temp{$name} = 1 if have_capability uc( $_ ) . '_HELPER' , 1; } else { require_capability( uc( $_ ) . '_HELPER' , "The $name helper", 's' ); $helpers_temp{$name} = 1; } } else { fatal_error "Unknown Helper ($_)"; } } %helpers_enabled = %helpers_temp; while ( my ( $helper, $enabled ) = each %helpers_enabled ) { unless ( $enabled ) { $helper =~ s/-0/0/; $helper =~ s/-/_/; $capabilities{uc($helper) . '_HELPER'} = 0; } } } elsif ( have_capability 'CT_TARGET' ) { $helpers_enabled{$_} = 0 for keys %helpers_enabled; $capabilities{$_} = 0 for grep /_HELPER/ , keys %capabilities; } report_capabilities unless $config{LOAD_HELPERS_ONLY}; # # Now initialize the used capabilities hash # %used = (); if ( have_capability 'CONNTRACK_MATCH') { $used{CONNTRACK_MATCH} = REQUIRED; } else { $used{STATE_MATCH} = REQUIRED; } # # The following is not documented as it is not likely useful to the user base in general # Going forward, it allows me to create a configuration that will work on multiple # Shorewall versions. TME # $config{VERSION} = sprintf "%d%02d%02d", $1, $2, $3 if $globals{VERSION} =~ /^(\d+)\.(\d+)\.(\d+)/; if ( my $rate = $config{LOGLIMIT} ) { my $limit; if ( $rate =~ /^[sd]:/ ) { require_capability 'HASHLIMIT_MATCH', 'Per-ip log rate limiting' , 's'; $limit = "-m hashlimit "; my $match = have_capability( 'OLD_HL_MATCH' ) ? 'hashlimit' : 'hashlimit-upto'; my $units; if ( $rate =~ /^[sd]:((\d+)(\/(sec|min|second|minute|hour|day))):(\d+)$/ ) { fatal_error "Invalid rate ($1)" unless $2; fatal_error "Invalid burst value ($5)" unless $5; $limit .= "--$match $1 --hashlimit-burst $5 --hashlimit-name lograte --hashlimit-mode "; $units = $4; } elsif ( $rate =~ /^[sd]:((\d+)(\/(sec|min|second|minute|hour|day))?)$/ ) { fatal_error "Invalid rate ($1)" unless $2; $limit .= "--$match $1 --hashlimit-name lograte --hashlimit-mode "; $units = $4; } else { fatal_error "Invalid rate ($rate)"; } $limit .= $rate =~ /^s:/ ? 'srcip ' : 'dstip '; if ( $units && $units ne 'sec' ) { my $expire = 60000; # 1 minute in milliseconds if ( $units ne 'min' ) { $expire *= 60; #At least an hour $expire *= 24 if $units eq 'day'; } $limit .= "--hashlimit-htable-expire $expire "; } } elsif ( $rate =~ /^((\d+)(\/(sec|min|second|minute|hour|day))):(\d+)$/ ) { fatal_error "Invalid rate ($1)" unless $2; fatal_error "Invalid burst value ($5)" unless $5; $limit = "-m limit --limit $1 --limit-burst $5 "; } elsif ( $rate =~ /^(\d+)(\/(sec|min|second|minute|hour|day))?$/ ) { fatal_error "Invalid rate (${1}${2})" unless $1; $limit = "-m limit --limit $rate "; } else { fatal_error "Invalid rate ($rate)"; } $globals{LOGLIMIT} = $limit; } else { $globals{LOGLIMIT} = ''; } if ( $globals{LOGLIMIT} ) { my $loglimit = $globals{LOGLIMIT}; $loglimit =~ s/ $//; my @loglimit = ( split ' ', $loglimit, 3 )[1,2]; $globals{LOGILIMIT} = \@loglimit; } else { $globals{LOGILIMIT} = []; } check_trivalue ( 'IP_FORWARDING', 'on' ); if ( have_capability( 'KERNELVERSION' ) < 20631 ) { check_trivalue ( 'ROUTE_FILTER', '' ); } else { $val = $config{ROUTE_FILTER}; if ( supplied $val ) { if ( $val =~ /\d+/ ) { fatal_error "Invalid value ($val) for ROUTE_FILTER" unless $val < 3; } else { check_trivalue( 'ROUTE_FILTER', '' ); } } else { check_trivalue( 'ROUTE_FILTER', '' ); } } if ( $family == F_IPV6 ) { $val = $config{ROUTE_FILTER}; fatal_error "ROUTE_FILTER=$val is not supported in IPv6" if $val && $val ne 'off'; } if ( $family == F_IPV4 ) { check_trivalue ( 'LOG_MARTIANS', 'on' ); } else { check_trivalue ( 'LOG_MARTIANS', 'off' ); fatal_error "LOG_MARTIANS=On is not supported in IPv6" if $config{LOG_MARTIANS} eq 'on'; } default 'SHOREWALL_SHELL', '/bin/sh'; default 'STARTUP_LOG' , ''; if ( $config{STARTUP_LOG} ne '' ) { if ( supplied $config{LOG_VERBOSITY} ) { if ( $config{LOG_VERBOSITY} eq '' ) { $config{LOG_VERBOSITY} = 2; } else { my $val = numeric_value( $config{LOG_VERBOSITY} ); fatal_error "Invalid LOG_VERBOSITY ($config{LOG_VERBOSITY} )" unless defined( $val ) && ( $val >= -1 ) && ( $val <= 2 ); $config{STARTUP_LOG} = '' if $config{LOG_VERBOSITY} < 0; $config{LOG_VERBOSITY} = $val; } } else { $config{LOG_VERBOSITY} = 2; } } else { $config{LOG_VERBOSITY} = -1; } default_yes_no 'ADD_IP_ALIASES' , 'Yes'; default_yes_no 'ADD_SNAT_ALIASES' , ''; default_yes_no 'DETECT_DNAT_IPADDRS' , ''; default_yes_no 'DETECT_DNAT_IPADDRS' , ''; default_yes_no 'CLEAR_TC' , $family == F_IPV4 ? 'Yes' : ''; if ( supplied( $val = $config{CLAMPMSS} ) ) { default_yes_no 'CLAMPMSS' , '' unless $val =~ /^\d+$/; require_capability 'TCPMSS_TARGET', "CLAMPMSS=$val", 's' if $config{CLAMPMSS}; } else { $config{CLAMPMSS} = ''; } unless ( $config{ADD_IP_ALIASES} || $config{ADD_SNAT_ALIASES} ) { $config{RETAIN_ALIASES} = ''; } else { default_yes_no_ipv4 'RETAIN_ALIASES' , ''; } default_yes_no 'ADMINISABSENTMINDED' , ''; default_yes_no 'DISABLE_IPV6' , ''; unsupported_yes_no_warning 'DYNAMIC_ZONES'; unsupported_yes_no 'BRIDGING'; unsupported_yes_no_warning 'RFC1918_STRICT'; unless (default_yes_no 'SAVE_IPSETS', '', '*' ) { $val = $config{SAVE_IPSETS}; unless ( $val eq 'ipv4' ) { my @sets = split_list( $val , 'ipset' ); $globals{SAVED_IPSETS} = \@sets; require_capability 'IPSET_V5', 'A saved ipset list', 's'; $config{SAVE_IPSETS} = ''; } } default_yes_no 'SAVE_ARPTABLES' , ''; default_yes_no 'STARTUP_ENABLED' , 'Yes'; default_yes_no 'DELAYBLACKLISTLOAD' , ''; default_yes_no 'MAPOLDACTIONS' , 'Yes'; warning_message 'DELAYBLACKLISTLOAD=Yes is not supported by Shorewall ' . $globals{VERSION} if $config{DELAYBLACKLISTLOAD}; default_yes_no 'LOGTAGONLY' , ''; $globals{LOGTAGONLY} = $config{LOGTAGONLY}; default_yes_no 'FASTACCEPT' , ''; if ( supplied( $val = $config{BLACKLIST} ) ) { my %states; if ( $val eq 'ALL' ) { $globals{BLACKLIST_STATES} = 'ALL'; } else { for ( split_list $val, 'BLACKLIST' ) { fatal_error "Invalid BLACKLIST state ($_)" unless /^(?:NEW|RELATED|ESTABLISHED|INVALID|UNTRACKED)$/; fatal_error "Duplicate BLACKLIST state($_)" if $states{$_}; $states{$_} = 1; } fatal_error "ESTABLISHED state may not be specified when FASTACCEPT=Yes" if $config{FASTACCEPT} && $states{ESTABLISHED}; require_capability 'RAW_TABLE', 'UNTRACKED state', 's' if $states{UNTRACKED}; # # Place the states in a predictable order # my @states; for ( qw( NEW ESTABLISHED RELATED INVALID UNTRACKED ) ) { push @states, $_ if $states{$_}; } $globals{BLACKLIST_STATES} = join ',', @states; } } else { if ( have_capability 'RAW_TABLE' ) { $globals{BLACKLIST_STATES} = $config{FASTACCEPT} ? 'NEW,INVALID,UNTRACKED' : 'NEW,ESTABLISHED,INVALID,UNTRACKED'; } else { $globals{BLACKLIST_STATES} = $config{FASTACCEPT} ? 'NEW,INVALID' : 'NEW,INVALID,ESTABLISHED'; } } default_yes_no 'IMPLICIT_CONTINUE' , ''; default_yes_no 'HIGH_ROUTE_MARKS' , ''; default_yes_no 'TC_EXPERT' , ''; default_yes_no 'USE_ACTIONS' , 'Yes'; warning_message 'USE_ACTIONS=No is not supported by Shorewall ' . $globals{VERSION} unless $config{USE_ACTIONS}; default_yes_no 'EXPORTPARAMS' , ''; default_yes_no 'EXPAND_POLICIES' , ''; default_yes_no 'KEEP_RT_TABLES' , ''; default_yes_no 'USE_RT_NAMES' , ''; default_yes_no 'DELETE_THEN_ADD' , 'Yes'; default_yes_no 'AUTOCOMMENT' , 'Yes'; default_yes_no 'MULTICAST' , ''; default_yes_no 'MARK_IN_FORWARD_CHAIN' , ''; default_yes_no 'CHAIN_SCRIPTS' , 'Yes'; default_yes_no 'TRACK_RULES' , ''; default_yes_no 'INLINE_MATCHES' , ''; default_yes_no 'BASIC_FILTERS' , ''; default_yes_no 'WORKAROUNDS' , 'Yes'; if ( supplied( $val = $config{RESTART} ) ) { fatal_error "Invalid value for RESTART ($val)" unless $val =~ /^(restart|reload)$/; } elsif (supplied $config{LEGACY_RESTART} ) { default_yes_no 'LEGACY_RESTART' , 'Yes'; $config{RESTART} = $config{LEGACY_RESTART} ? 'reload' : 'restart'; } else { $config{RESTART} = 'reload'; } require_capability( 'BASIC_EMATCH', 'BASIC_FILTERS=Yes', 's' ) if $config{BASIC_FILTERS}; if ( $val = $config{REJECT_ACTION} ) { fatal_error "Invalid Reject Action Name ($val)" unless $val =~ /^[a-zA-Z][\w-]*$/; } else { $config{REJECT_ACTION} = ''; } require_capability 'COMMENTS', 'TRACK_RULES=Yes', 's' if $config{TRACK_RULES}; default_yes_no 'MANGLE_ENABLED' , have_capability( 'MANGLE_ENABLED' ) ? 'Yes' : ''; default_yes_no 'USE_DEFAULT_RT' , ''; default_yes_no 'RESTORE_DEFAULT_ROUTE' , 'Yes'; default_yes_no 'AUTOMAKE' , ''; default_yes_no 'TRACK_PROVIDERS' , ''; unless ( ( $config{NULL_ROUTE_RFC1918} || '' ) =~ /^(?:blackhole|unreachable|prohibit)$/ ) { default_yes_no( 'NULL_ROUTE_RFC1918', '' ); $config{NULL_ROUTE_RFC1918} = 'blackhole' if $config{NULL_ROUTE_RFC1918}; } default_yes_no 'ACCOUNTING' , 'Yes'; default_yes_no 'OPTIMIZE_ACCOUNTING' , ''; if ( supplied $config{ACCOUNTING_TABLE} ) { my $value = $config{ACCOUNTING_TABLE}; fatal_error "Invalid ACCOUNTING_TABLE setting ($value)" unless $value eq 'filter' || $value eq 'mangle'; } else { $config{ACCOUNTING_TABLE} = 'filter'; } default_yes_no 'DYNAMIC_BLACKLIST' , 'Yes'; default_yes_no 'REQUIRE_INTERFACE' , ''; default_yes_no 'FORWARD_CLEAR_MARK' , have_capability( 'MARK' ) ? 'Yes' : ''; default_yes_no 'COMPLETE' , ''; default_yes_no 'EXPORTMODULES' , ''; default_yes_no 'USE_PHYSICAL_NAMES' , ''; default_yes_no 'IPSET_WARNINGS' , 'Yes'; default_yes_no 'AUTOHELPERS' , 'Yes'; default_yes_no 'RESTORE_ROUTEMARKS' , 'Yes'; default_yes_no 'IGNOREUNKNOWNVARIABLES' , 'Yes'; default_yes_no 'WARNOLDCAPVERSION' , 'Yes'; default_yes_no 'DEFER_DNS_RESOLUTION' , 'Yes'; $config{IPSET} = '' if supplied $config{IPSET} && $config{IPSET} eq 'ipset'; require_capability 'MARK' , 'FORWARD_CLEAR_MARK=Yes', 's', if $config{FORWARD_CLEAR_MARK}; numeric_option 'TC_BITS' , 8, 0; numeric_option 'MASK_BITS' , 8, 0; numeric_option 'PROVIDER_OFFSET' , 0, 0; numeric_option 'PROVIDER_BITS' , 8, 0; numeric_option 'ZONE_BITS' , 0, 0; require_capability 'MARK_ANYWHERE', 'A non-zero ZONE_BITS setting', 's' if $config{ZONE_BITS}; if ( $config{PROVIDER_OFFSET} ) { $config{PROVIDER_OFFSET} = $config{MASK_BITS} if $config{PROVIDER_OFFSET} < $config{MASK_BITS}; $globals{ZONE_OFFSET} = $config{PROVIDER_OFFSET} + $config{PROVIDER_BITS}; } elsif ( $config{MASK_BITS} >= $config{PROVIDER_BITS} ) { $globals{ZONE_OFFSET} = $config{MASK_BITS}; } else { $globals{ZONE_OFFSET} = $config{PROVIDER_BITS}; } # # It is okay if the event mark is outside of the a 32-bit integer. We check that in IfEvent" # fatal_error 'Invalid Packet Mark layout' if $config{ZONE_BITS} + $globals{ZONE_OFFSET} > 30; $globals{EXCLUSION_MASK} = 1 << ( $globals{ZONE_OFFSET} + $config{ZONE_BITS} ); $globals{TPROXY_MARK} = $globals{EXCLUSION_MASK} << 1; $globals{EVENT_MARK} = $globals{TPROXY_MARK} << 1; $globals{PROVIDER_MIN} = 1 << $config{PROVIDER_OFFSET}; $globals{TC_MAX} = make_mask( $config{TC_BITS} ); $globals{TC_MASK} = make_mask( $config{MASK_BITS} ); $globals{PROVIDER_MASK} = make_mask( $config{PROVIDER_BITS} ) << $config{PROVIDER_OFFSET}; if ( $config{ZONE_BITS} ) { $globals{ZONE_MASK} = make_mask( $config{ZONE_BITS} ) << $globals{ZONE_OFFSET}; } else { $globals{ZONE_MASK} = 0; } if ( ( my $userbits = $config{PROVIDER_OFFSET} - $config{TC_BITS} ) > 0 ) { $globals{USER_MASK} = make_mask( $userbits ) << $config{TC_BITS}; $globals{USER_BITS} = $userbits; } else { $globals{USER_MASK} = $globals{USER_BITS} = 0; } $val = $config{PROVIDER_OFFSET}; $globals{SMALL_MAX} = $val ? make_mask( $val ) : $globals{TC_MASK}; if ( supplied ( $val = $config{ZONE2ZONE} ) ) { fatal_error "Invalid ZONE2ZONE value ( $val )" unless $val =~ /^[2-]$/; } else { $config{ZONE2ZONE} = '-'; } default 'BLACKLIST_DISPOSITION' , 'DROP'; unless ( ( $val = $config{BLACKLIST_DISPOSITION} ) =~ /^(?:A_)?DROP$/ || $config{BLACKLIST_DISPOSITION} =~ /^(?:A_)?REJECT/ ) { fatal_error q(BLACKLIST_DISPOSITION must be 'DROP', 'A_DROP', 'REJECT' or 'A_REJECT'); } require_capability 'AUDIT_TARGET', "BLACKLIST_DISPOSITION=$val", 's' if $val =~ /^A_/; default 'SMURF_DISPOSITION' , 'DROP'; unless ( ( $val = $config{SMURF_DISPOSITION} ) =~ /^(?:A_)?DROP$/ ) { fatal_error q(SMURF_DISPOSITION must be 'DROP' or 'A_DROP'); } require_capability 'AUDIT_TARGET', "SMURF_DISPOSITION=$val", 's' if $val =~ /^A_/; default_log_level 'BLACKLIST_LOG_LEVEL', ''; default_log_level 'MACLIST_LOG_LEVEL', ''; default_log_level 'TCP_FLAGS_LOG_LEVEL', ''; default_log_level 'RFC1918_LOG_LEVEL', ''; default_log_level 'RELATED_LOG_LEVEL', ''; default_log_level 'INVALID_LOG_LEVEL', ''; default_log_level 'UNTRACKED_LOG_LEVEL', ''; if ( supplied( $val = $config{LOG_BACKEND} ) ) { if ( $family == F_IPV4 && $val eq 'ULOG' ) { $val = 'ipt_ULOG'; } elsif ( $val eq 'netlink' ) { $val = 'nfnetlink_log'; } elsif ( $val eq 'LOG' ) { $val = $family == F_IPV4 ? 'ipt_LOG' : 'ip6t_LOG'; } else { fatal_error "Invalid LOG Backend ($val)"; } $config{LOG_BACKEND} = $val; } warning_message "RFC1918_LOG_LEVEL=$config{RFC1918_LOG_LEVEL} ignored. The 'norfc1918' interface/host option is no longer supported" if $config{RFC1918_LOG_LEVEL}; default_log_level 'SMURF_LOG_LEVEL', ''; default_log_level 'LOGALLNEW', ''; default_log_level 'SFILTER_LOG_LEVEL', 'info'; if ( $val = $config{SFILTER_DISPOSITION} ) { fatal_error "Invalid SFILTER_DISPOSITION setting ($val)" unless $val =~ /^(A_)?(DROP|REJECT)$/; require_capability 'AUDIT_TARGET' , "SFILTER_DISPOSITION=$val", 's' if $1; } else { $config{SFILTER_DISPOSITION} = 'DROP'; } default_log_level 'RPFILTER_LOG_LEVEL', 'info'; if ( $val = $config{RPFILTER_DISPOSITION} ) { fatal_error "Invalid RPFILTER_DISPOSITION setting ($val)" unless $val =~ /^(A_)?(DROP|REJECT)$/; require_capability 'AUDIT_TARGET' , "RPFILTER_DISPOSITION=$val", 's' if $1; } else { $config{RPFILTER_DISPOSITION} = 'DROP'; } if ( $val = $config{MACLIST_DISPOSITION} ) { if ( $val =~ /^(?:A_)?DROP$/ ) { $globals{MACLIST_TARGET} = $val; } elsif ( $val eq 'REJECT' ) { $globals{MACLIST_TARGET} = 'reject'; } elsif ( $val eq 'A_REJECT' ) { $globals{MACLIST_TARGET} = $val; } elsif ( $val eq 'ACCEPT' ) { $globals{MACLIST_TARGET} = 'RETURN'; } else { fatal_error "Invalid value ($config{MACLIST_DISPOSITION}) for MACLIST_DISPOSITION" } require_capability 'AUDIT_TARGET' , "MACLIST_DISPOSITION=$val", 's' if $val =~ /^A_/; } else { $config{MACLIST_DISPOSITION} = 'REJECT'; $globals{MACLIST_TARGET} = 'reject'; } if ( $val = $config{RELATED_DISPOSITION} ) { if ( $val =~ /^(?:A_)?(?:DROP|ACCEPT)$/ ) { $globals{RELATED_TARGET} = $val; } elsif ( $val eq 'REJECT' ) { $globals{RELATED_TARGET} = 'reject'; } elsif ( $val eq 'A_REJECT' ) { $globals{RELATED_TARGET} = $val; } elsif ( $val eq 'CONTINUE' ) { $globals{RELATED_TARGET} = ''; } else { fatal_error "Invalid value ($config{RELATED_DISPOSITION}) for RELATED_DISPOSITION" } require_capability 'AUDIT_TARGET' , "RELATED_DISPOSITION=$val", 's' if $val =~ /^A_/; } else { $config{RELATED_DISPOSITION} = $globals{RELATED_TARGET} = 'ACCEPT'; } if ( $val = $config{INVALID_DISPOSITION} ) { if ( $val =~ /^(?:A_)?DROP$/ ) { $globals{INVALID_TARGET} = $val; } elsif ( $val eq 'REJECT' ) { $globals{INVALID_TARGET} = 'reject'; } elsif ( $val eq 'A_REJECT' ) { $globals{INVALID_TARGET} = $val; } elsif ( $val eq 'CONTINUE' ) { $globals{INVALID_TARGET} = ''; } else { fatal_error "Invalid value ($config{INVALID_DISPOSITION}) for INVALID_DISPOSITION" } require_capability 'AUDIT_TARGET' , "INVALID_DISPOSITION=$val", 's' if $val =~ /^A_/; } else { $config{INVALID_DISPOSITION} = 'CONTINUE'; $globals{INVALID_TARGET} = ''; } if ( $val = $config{UNTRACKED_DISPOSITION} ) { if ( $val =~ /^(?:A_)?(?:DROP|ACCEPT)$/ ) { $globals{UNTRACKED_TARGET} = $val; } elsif ( $val eq 'REJECT' ) { $globals{UNTRACKED_TARGET} = 'reject'; } elsif ( $val eq 'A_REJECT' ) { $globals{UNTRACKED_TARGET} = $val; } elsif ( $val eq 'CONTINUE' ) { $globals{UNTRACKED_TARGET} = ''; } else { fatal_error "Invalid value ($config{UNTRACKED_DISPOSITION}) for UNTRACKED_DISPOSITION" } require_capability 'AUDIT_TARGET' , "UNTRACKED_DISPOSITION=$val", 's' if $val =~ /^A_/; } else { $config{UNTRACKED_DISPOSITION} = 'CONTINUE'; $globals{UNTRACKED_TARGET} = ''; } if ( $val = $config{MACLIST_TABLE} ) { if ( $val eq 'mangle' ) { fatal_error 'MACLIST_DISPOSITION=$1 is not allowed with MACLIST_TABLE=mangle' if $config{MACLIST_DISPOSITION} =~ /^((?:A)?REJECT)$/; } else { fatal_error "Invalid value ($val) for MACLIST_TABLE option" unless $val eq 'filter'; } } else { default 'MACLIST_TABLE' , 'filter'; } if ( $val = $config{TCP_FLAGS_DISPOSITION} ) { fatal_error "Invalid value ($config{TCP_FLAGS_DISPOSITION}) for TCP_FLAGS_DISPOSITION" unless $val =~ /^(?:(A_)?(?:REJECT|DROP))|ACCEPT$/; require_capability 'AUDIT_TARGET' , "TCP_FLAGS_DISPOSITION=$val", 's' if $1; } else { $val = $config{TCP_FLAGS_DISPOSITION} = 'DROP'; } default 'TC_ENABLED' , $family == F_IPV4 ? 'Internal' : 'no'; $val = "\L$config{TC_ENABLED}"; if ( $val eq 'yes' ) { my $file = find_file 'tcstart'; fatal_error "Unable to find tcstart file" unless -f $file; $globals{TC_SCRIPT} = $file; } elsif ( $val eq 'internal' ) { $config{TC_ENABLED} = 'Internal'; } elsif ( $val eq 'shared' ) { $config{TC_ENABLED} = 'Shared'; } elsif ( $val eq 'simple' ) { $config{TC_ENABLED} = 'Simple'; } else { fatal_error "Invalid value ($config{TC_ENABLED}) for TC_ENABLED" unless $val eq 'no'; $config{TC_ENABLED} = ''; } if ( $config{TC_ENABLED} ) { fatal_error "TC_ENABLED=$config{TC_ENABLED} is not allowed with MANGLE_ENABLED=No" unless $config{MANGLE_ENABLED}; require_capability 'MANGLE_ENABLED', "TC_ENABLED=$config{TC_ENABLED}", 's'; } if ( $val = $config{TC_PRIOMAP} ) { my @priomap = split ' ',$val; fatal_error "Invalid TC_PRIOMAP ($val)" unless @priomap == 16; for ( @priomap ) { fatal_error "Invalid TC_PRIOMAP entry ($_)" unless /^[1-3]$/; $_--; } $config{TC_PRIOMAP} = join ' ', @priomap; } else { $config{TC_PRIOMAP} = '1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1'; } default 'RESTOREFILE' , 'restore'; default 'DROP_DEFAULT' , 'Drop'; default 'REJECT_DEFAULT' , 'Reject'; default 'QUEUE_DEFAULT' , 'none'; default 'NFQUEUE_DEFAULT' , 'none'; default 'ACCEPT_DEFAULT' , 'none'; default 'OPTIMIZE' , 0; for my $default ( qw/DROP_DEFAULT REJECT_DEFAULT QUEUE_DEFAULT NFQUEUE_DEFAULT ACCEPT_DEFAULT/ ) { $config{$default} = 'none' if "\L$config{$default}" eq 'none'; } if ( ( $val = $config{OPTIMIZE} ) =~ /^all$/i ) { $config{OPTIMIZE} = $val = OPTIMIZE_ALL; } elsif ( $val =~ /^none$/i ) { $config{OPTIMIZE} = $val = 0; } else { $val = numeric_value $config{OPTIMIZE}; fatal_error "Invalid OPTIMIZE value ($config{OPTIMIZE})" unless supplied( $val ) && $val >= 0 && ( $val & ~OPTIMIZE_USE_FIRST ) <= OPTIMIZE_ALL; } require_capability 'XMULTIPORT', 'OPTIMIZE level 16', 's' if $val & 16; $globals{MARKING_CHAIN} = $config{MARK_IN_FORWARD_CHAIN} ? 'tcfor' : 'tcpre'; if ( $val = $config{LOGFORMAT} ) { my $result; eval { if ( $val =~ /%d/ ) { $globals{LOGRULENUMBERS} = 'Yes'; $result = sprintf "$val", 'fooxx2barxx', 1, 'ACCEPT'; } else { $result = sprintf "$val", 'fooxx2barxx', 'ACCEPT'; } }; fatal_error "Invalid LOGFORMAT ($val)" if $@; fatal_error "LOGFORMAT string is longer than 29 characters ($val)" if length $result > 29; $globals{MAXZONENAMELENGTH} = int ( 5 + ( ( 29 - (length $result ) ) / 2) ); } else { $config{LOGFORMAT}='Shorewall:%s:%s:'; $globals{MAXZONENAMELENGTH} = 5; } if ( $config{LOCKFILE} ) { my ( $file, $dir, $suffix ); eval { ( $file, $dir, $suffix ) = fileparse( $config{LOCKFILE} ); }; cleanup, die $@ if $@; fatal_error "LOCKFILE=$config{LOCKFILE}: Directory $dir does not exist" unless $export or -d $dir; } else { $config{LOCKFILE} = ''; } require_capability( 'MULTIPORT' , "Shorewall $globals{VERSION}" , 's' ); require_capability( 'RECENT_MATCH' , 'MACLIST_TTL' , 's' ) if $config{MACLIST_TTL}; require_capability( 'XCONNMARK' , 'HIGH_ROUTE_MARKS=Yes' , 's' ) if $config{PROVIDER_OFFSET} > 0; require_capability( 'MANGLE_ENABLED' , 'Traffic Shaping' , 's' ) if $config{TC_ENABLED}; if ( $config{WARNOLDCAPVERSION} ) { if ( $capabilities{CAPVERSION} ) { warning_message "Your capabilities file is out of date -- it does not contain all of the capabilities defined by $Product version $globals{VERSION}" unless $capabilities{CAPVERSION} >= $globals{CAPVERSION}; } else { warning_message "Your capabilities file may not contain all of the capabilities defined by $Product version $globals{VERSION}"; } } add_variables %config; while ( my ($var, $val ) = each %renamed ) { $variables{$var} = $config{$val}; } convert_to_directives if $update; cleanup_iptables if $sillyname && ! $config{LOAD_HELPERS_ONLY}; } # # The values of the options in @propagateconfig are copied to the script file in OPTION= format. # sub propagateconfig() { for my $option ( @propagateconfig ) { my $value = $config{$option}; $value = '' unless defined $value; emit "$option=\"$value\""; } } # # Add a shell script file to the output script -- Return true if the # file exists and is not in /usr/share/shorewall/ and is non-empty. # sub append_file( $;$$ ) { my ( $file, $nomsg, $unindented ) = @_; my $user_exit = find_file $file; my $result = 0; my $save_indent = $indent; $indent = '' if $unindented; unless ( $user_exit =~ m(^$shorewallrc{SHAREDIR}/shorewall6?/) ) { if ( -f $user_exit ) { if ( $nomsg ) { # # Suppress progress message # $result = copy1 $user_exit; } else { # # Include progress message -- Pretend progress_message call was in the file # my $name = $globals{EXPORT} ? "$file user exit" : $user_exit; $result = 1; save_progress_message "Processing $name ..."; copy1 $user_exit; } } } $indent = $save_indent; $result; } # # Run a Perl extension script # sub run_user_exit( $ ) { my $chainref = $_[0]; my $file = find_file $chainref->{name}; if ( $config{CHAIN_SCRIPTS} && -f $file ) { progress_message2 "Running $file..."; my $command = qq(package Shorewall::User;\nno strict;\n# line 1 "$file"\n) . `cat $file`; unless (my $return = eval $command ) { fatal_error "Couldn't parse $file: $@" if $@; unless ( defined $return ) { fatal_error "Couldn't do $file: $!" if $!; fatal_error "Couldn't do $file"; } fatal_error "$file returned a false value"; } } } sub run_user_exit1( $ ) { my $file = find_file $_[0]; if ( -f $file ) { progress_message2 "Running $file..."; # # File may be empty -- in which case eval would fail # push_open $file; if ( read_a_line( STRIP_COMMENTS | SUPPRESS_WHITESPACE | CHECK_GUNK ) ) { close_file; pop_open; my $command = qq(package Shorewall::User;\n# line 1 "$file"\n) . `cat $file`; unless (my $return = eval $command ) { fatal_error "Couldn't parse $file: $@" if $@; unless ( defined $return ) { fatal_error "Couldn't do $file: $!" if $!; fatal_error "Couldn't do $file"; } fatal_error "$file returned a false value"; } } else { pop_open; } } } sub run_user_exit2( $$ ) { my ($file, $chainref) = ( find_file $_[0], $_[1] ); if ( $config{CHAIN_SCRIPTS} && -f $file ) { progress_message2 "Running $file..."; # # File may be empty -- in which case eval would fail # push_open $file; if ( read_a_line( STRIP_COMMENTS | SUPPRESS_WHITESPACE | CHECK_GUNK ) ) { close_file; pop_open; unless (my $return = eval `cat $file` ) { fatal_error "Couldn't parse $file: $@" if $@; unless ( defined $return ) { fatal_error "Couldn't do $file: $!" if $!; fatal_error "Couldn't do $file"; } fatal_error "$file returned a false value"; } } pop_open; } } # # Generate the aux config file for Shorewall Lite # sub generate_aux_config() { sub conditionally_add_option( $ ) { my $option = $_[0]; my $value = $config{$option}; emit "[ -n \"\${$option:=$value}\" ]" if supplied $value; } sub conditionally_add_option1( $ ) { my $option = $_[0]; my $value = $config{$option}; emit "$option=\"$value\"" if $value; } create_temp_aux_config; my $date = localtime; emit "#\n# Shorewall auxiliary configuration file created by Shorewall version $globals{VERSION} - $date\n#"; for my $option ( qw(VERBOSITY LOGFILE LOGFORMAT ARPTABLES IPTABLES IP6TABLES IP TC IPSET PATH SHOREWALL_SHELL SUBSYSLOCK LOCKFILE RESTOREFILE WORKAROUNDS RESTART) ) { conditionally_add_option $option; } conditionally_add_option1 'TC_ENABLED'; my $fn = find_file 'scfilter'; if ( -f $fn ) { emit( '', 'show_connections_filter() {' ); push_indent; append_file( $fn,1 ) or emit 'cat -'; pop_indent; emit '}'; } $fn = find_file 'dumpfilter'; if ( -f $fn ) { emit( '', 'dump_filter() {' ); push_indent; append_file( $fn,1 ) or emit 'cat -'; pop_indent; emit '}'; } finalize_aux_config; } sub dump_mark_layout() { sub dumpout( $$$$$ ) { my ( $name, $bits, $min, $max, $mask ) = @_; if ( $bits ) { if ( $min == $max ) { emit_unindented "$name:" . $min . ' mask ' . in_hex( $mask ); } else { emit_unindented "$name:" . join('-', $min, $max ) . ' (' . join( '-', in_hex( $min ), in_hex( $max ) ) . ') mask ' . in_hex( $mask ); } } else { emit_unindented "$name: Not Enabled"; } } dumpout( "Traffic Shaping", $config{TC_BITS}, 0, $globals{TC_MAX}, $globals{TC_MASK} ); dumpout( "User", $globals{USER_BITS}, $globals{TC_MAX} + 1, $globals{USER_MASK}, $globals{USER_MASK} ); dumpout( "Provider", $config{PROVIDER_BITS}, $globals{PROVIDER_MIN}, $globals{PROVIDER_MASK}, $globals{PROVIDER_MASK} ); dumpout( "Zone", $config{ZONE_BITS}, 1 << $globals{ZONE_OFFSET}, $globals{ZONE_MASK}, $globals{ZONE_MASK} ); dumpout( "Exclusion", 1, $globals{EXCLUSION_MASK}, $globals{EXCLUSION_MASK}, $globals{EXCLUSION_MASK} ); dumpout( "TProxy", 1, $globals{TPROXY_MARK}, $globals{TPROXY_MARK}, $globals{TPROXY_MARK} ); } sub report_used_capabilities() { if ( $verbosity > 1 ) { progress_message2 "Configuration uses these capabilities ('*' denotes required):"; for ( sort grep $_ ne 'KERNELVERSION', keys %used ) { if ( ( $used{$_} || 0 ) & REQUIRED ) { progress_message2 " $_*"; } else { progress_message2 " $_"; } } } } END { cleanup; } 1; shorewall-5.0.4/Perl/Shorewall/Chains.pm0000644000000000000000000073461712650243750016651 0ustar rootroot# # Shorewall 4.5 -- /usr/share/shorewall/Shorewall/Chains.pm # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2007,2008,2009,2010,2011,2012,2013 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # 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 . # # This is the low-level iptables module. It provides the basic services # of chain and rule creation. It is used by the higher level modules such # as Rules to create iptables-restore input. # package Shorewall::Chains; require Exporter; use Scalar::Util 'reftype'; use Digest::SHA qw(sha1_hex); use File::Basename; use Shorewall::Config qw(:DEFAULT :internal); use Shorewall::Zones; use Shorewall::IPAddrs; use strict; our @ISA = qw(Exporter); our @EXPORT = ( qw( DONT_OPTIMIZE DONT_DELETE DONT_MOVE add_rule add_irule add_jump add_ijump insert_rule insert_irule clone_irule insert_ijump rule_target clear_rule_target set_rule_target set_rule_option add_trule add_commands incr_cmd_level decr_cmd_level new_chain new_manual_chain ensure_filter_chain ensure_manual_chain ensure_audit_chain ensure_blacklog_chain ensure_audit_blacklog_chain require_audit newlogchain log_rule_limit log_irule_limit allow_optimize allow_delete allow_move make_terminating set_optflags reset_optflags has_return dont_optimize dont_delete dont_move add_interface_options state_match state_imatch split_action get_target_param get_target_param1 get_inline_matches handle_inline STANDARD NATRULE BUILTIN NONAT NATONLY REDIRECT ACTION MACRO LOGRULE NFLOG NFQ CHAIN SET AUDIT HELPER INLINE STATEMATCH USERBUILTIN INLINERULE OPTIONS IPTABLES TARPIT FILTER_TABLE NAT_TABLE MANGLE_TABLE RAW_TABLE %chain_table %targets $raw_table $rawpost_table $nat_table $mangle_table $filter_table ) ); our %EXPORT_TAGS = ( internal => [ qw( NO_RESTRICT PREROUTE_RESTRICT DESTIFACE_DISALLOW INPUT_RESTRICT OUTPUT_RESTRICT POSTROUTE_RESTRICT ALL_RESTRICT ALL_COMMANDS NOT_RESTORE unreachable_warning state_match state_imatch initialize_chain_table copy_rules move_rules insert_rule1 delete_jumps add_tunnel_rule forward_chain forward_option_chain rules_chain blacklist_chain established_chain related_chain invalid_chain untracked_chain zone_forward_chain use_forward_chain input_chain input_option_chain zone_input_chain use_input_chain output_chain output_option_chain prerouting_chain postrouting_chain zone_output_chain use_output_chain masq_chain syn_flood_chain mac_chain macrecent_target dnat_chain snat_chain ecn_chain notrack_chain load_chain first_chains option_chains reserved_name find_chain ensure_chain ensure_accounting_chain accounting_chainrefs ensure_mangle_chain ensure_nat_chain ensure_raw_chain ensure_rawpost_chain new_standard_chain new_builtin_chain new_nat_chain optimize_chain check_optimization optimize_level0 optimize_ruleset setup_zone_mss newexclusionchain newnonatchain source_exclusion source_iexclusion dest_exclusion dest_iexclusion clearrule port_count do_proto do_iproto do_mac do_imac verify_mark verify_small_mark validate_mark do_test do_ratelimit do_connlimit do_time do_user do_length decode_tos do_tos do_connbytes do_helper validate_helper do_headers do_probability do_condition do_dscp do_nfacct have_ipset_rules record_runtime_address verify_address_variables conditional_rule conditional_rule_end match_source_dev imatch_source_dev match_dest_dev imatch_dest_dev iprange_match match_source_net imatch_source_net match_dest_net imatch_dest_net match_orig_dest match_ipsec_in match_ipsec_out do_ipsec_options do_ipsec log_rule log_irule handle_network_list expand_rule addnatjump split_host_list set_chain_variables mark_firewall_not_started mark_firewall6_not_started get_interface_address get_interface_addresses get_interface_bcasts get_interface_acasts get_interface_gateway get_interface_mac have_global_variables have_address_variables set_global_variables save_dynamic_chains load_ipsets create_save_ipsets validate_nfobject create_nfobjects create_netfilter_load preview_netfilter_load create_chainlist_reload create_stop_load initialize_switches %targets %builtin_target %dscpmap ) ], ); Exporter::export_ok_tags('internal'); our $VERSION = '5.0_4'; # # Chain Table # # %chain_table { => { => { name => # table =>
# is_policy => undef|1 -- if 1, this is a policy chain # provisional => undef|1 -- See below. # referenced => undef|1 -- If 1, will be written to the iptables-restore-input. # builtin => undef|1 -- If 1, one of Netfilter's built-in chains. # manual => undef|1 -- If 1, a manual chain. # accounting => undef|1 -- If 1, an accounting chain # optflags => # log => # policy => # policychain => -- self-reference if this is a policy chain # policypair => [ , ] -- Used for reporting duplicated policies # origin => # loglevel => # synparams => # synchain => # default => # cmdlevel => # new => undef| # rules => [ # # ... # ] # logchains => { = , ... } # references => { => , => , ... } # blacklistsection # => Chain was created by entries in the BLACKLIST section of the rules file # action => # restricted => Logical OR of restrictions of rules in this chain. # restriction => Restrictions on further rules in this chain. # audit => Audit the result. # filtered => Number of filter rules at the front of an interface forward chain # digest => SHA1 digest of the string representation of the chain's rules for use in optimization # level 8. # complete => The last rule in the chain is a -g or a simple -j to a terminating target # Suppresses adding additional rules to the chain end of the chain # sections => {
= 1, ... } - Records sections that have been completed. # } , # => ... # } # } # # 'provisional' only applies to policy chains; when true, indicates that this is a provisional policy chain which might be # replaced. Policy chains created under the IMPLICIT_CONTINUE=Yes option are marked with provisional == 1 as are intra-zone # ACCEPT policies. # # Only 'referenced' chains get written to the iptables-restore input. # # 'loglevel', 'synparams', 'synchain', 'audit', 'default' abd 'origin' only apply to policy chains. ########################################################################################################################################### # # For each ordered pair of zones, there may exist a 'canonical rules chain' in the filter table; the name of this chain is formed by # joining the names of the zones using the ZONE_SEPARATOR ('2' or '-'). This chain contains the rules that specifically deal with # connections from the first zone to the second. These chains will end with the policy rules when EXPAND_POLICIES=Yes and when there is an # explicit policy for the order pair. Otherwise, unless the applicable policy is CONTINUE, the chain will terminate with a jump to a # wildcard policy chain (all[2-]zone, zone[2-]all, or all[2-]all). # # Except in the most trivial one-interface configurations, each zone has a "forward chain" which is branched to from the filter table # FORWARD chain. # # For each network interface, there are up to 6 chains in the filter table: # # - Input, Output, Forward "Interface Chains" # These are present when there is more than one zone associated with the interface. They are jumped to from the INPUT, OUTPUT and # FORWARD chains respectively. # - Input Option, Output Option and Forward "Interface Option Chains" # Used when blacklisting is involved for enforcing interface options that require Netfilter rules. This allows blacklisting to # occur prior to interface option filtering. When these chains are not used, any rules that they contained are moved to the # corresponding interface chains. # ########################################################################################################################################### # # Constructed chain names # # Interface Chains for device # # OUTPUT - _out # PREROUTING - _pre # POSTROUTING - _post # MASQUERADE - _masq # MAC filtering - _mac # MAC Recent - _rec # SNAT - _snat # ECN - _ecn # FORWARD Options - _fop # OUTPUT Options - _oop # FORWARD Options - _fop # # Zone Chains for zone # # INPUT - _input # OUTPUT - _output # FORWARD - _frwd # DNAT - _dnat # Conntrack - _ctrk # # Provider Chains for provider

# Load Balance - ~

# # Zone-pair chains for rules chain # # Syn Flood - @ # Blacklist - ~ # Established - ^ # Related - + # Invalid - _ # Untracked - & # our %chain_table; our $raw_table; our $rawpost_table; our $nat_table; our $mangle_table; our $filter_table; our $export; our %renamed; our %nfobjects; # # Target Types # use constant { STANDARD => 0x1, #defined by Netfilter NATRULE => 0x2, #Involves NAT BUILTIN => 0x4, #A built-in action NONAT => 0x8, #'NONAT' or 'ACCEPT+' NATONLY => 0x10, #'DNAT-' or 'REDIRECT-' REDIRECT => 0x20, #'REDIRECT' ACTION => 0x40, #An action (may be built-in) MACRO => 0x80, #A Macro LOGRULE => 0x100, #'LOG','NFLOG' NFQ => 0x200, #'NFQUEUE' CHAIN => 0x400, #Manual Chain SET => 0x800, #SET AUDIT => 0x1000, #A_ACCEPT, etc HELPER => 0x2000, #CT:helper NFLOG => 0x4000, #NFLOG or ULOG INLINE => 0x8000, #Inline action STATEMATCH => 0x10000, #action.Invalid, action.Related, etc. USERBUILTIN => 0x20000, #Builtin action from user's actions file. INLINERULE => 0x40000, #INLINE OPTIONS => 0x80000, #Target Accepts Options IPTABLES => 0x100000, #IPTABLES or IP6TABLES TARPIT => 0x200000, #TARPIT FILTER_TABLE => 0x1000000, MANGLE_TABLE => 0x2000000, RAW_TABLE => 0x4000000, NAT_TABLE => 0x8000000, }; # # Valid Targets -- value is a combination of one or more of the above # our %targets; # # Terminating builtins # our %terminating; # # expand_rule() restrictions # use constant { NO_RESTRICT => 0, # FORWARD chain rule - Both -i and -o may be used in the rule PREROUTE_RESTRICT => 1, # PREROUTING chain rule - -o converted to -d

using main routing table INPUT_RESTRICT => 4, # INPUT chain rule - -o not allowed OUTPUT_RESTRICT => 8, # OUTPUT chain rule - -i not allowed POSTROUTE_RESTRICT => 16, # POSTROUTING chain rule - -i converted to -s
using main routing table ALL_RESTRICT => 12, # fw->fw rule - neither -i nor -o allowed DESTIFACE_DISALLOW => 32, # Don't allow dest interface. Similar to INPUT_RESTRICT but generates a more relevant error message }; # # Possible IPSET options # our %ipset_extensions = ( 'nomatch' => '--return-nomatch ', 'no-update-counters' => '! --update-counters ', 'no-update-subcounters' => '! --update-subcounters ', 'packets' => '', 'bytes' => '', ); # # See initialize() below for additional comments on these variables # our $iprangematch; our %chainseq; our $idiotcount; our $idiotcount1; our $hashlimitset; our $global_variables; our %address_variables; our $ipset_rules; # # Determines the commands for which a particular interface-oriented shell variable needs to be set # use constant { ALL_COMMANDS => 1, NOT_RESTORE => 2 }; use constant { DONT_OPTIMIZE => 1 , DONT_DELETE => 2, DONT_MOVE => 4, RETURNS => 8, RETURNS_DONT_MOVE => 12 }; our %dscpmap = ( CS0 => 0x00, CS1 => 0x08, CS2 => 0x10, CS3 => 0x18, CS4 => 0x20, CS5 => 0x28, CS6 => 0x30, CS7 => 0x38, BE => 0x00, AF11 => 0x0a, AF12 => 0x0c, AF13 => 0x0e, AF21 => 0x12, AF22 => 0x14, AF23 => 0x16, AF31 => 0x1a, AF32 => 0x1c, AF33 => 0x1e, AF41 => 0x22, AF42 => 0x24, AF43 => 0x26, EF => 0x2e, ); our %tosmap = ( 'Minimize-Delay' => 0x10, 'Maximize-Throughput' => 0x08, 'Maximize-Reliability' => 0x04, 'Minimize-Cost' => 0x02, 'Normal-Service' => 0x00 ); # # These hashes hold the shell code to set shell variables. The key is the name of the variable; the value is the code to generate the variable's contents # our %interfaceaddr; # First interface address our %interfaceaddrs; # All interface addresses our %interfacenets; # Networks routed out of the interface our %interfacemacs; # Interface MAC our %interfacebcasts; # Broadcast addresses associated with the interface (IPv4) our %interfaceacasts; # Anycast addresses associated with the interface (IPv6) our %interfacegateways; # Gateway of default route out of the interface # # Built-in Chains # our @builtins = qw(PREROUTING INPUT FORWARD OUTPUT POSTROUTING); # # Mode of the emitter (part of this module that converts rules in the chain table into iptables-restore input) # use constant { NULL_MODE => 0 , # Emitting neither shell commands nor iptables-restore input CAT_MODE => 1 , # Emitting iptables-restore input CMD_MODE => 2 }; # Emitting shell commands. our $mode; # # A reference to this rule is returned when we try to push a rule onto a 'complete' chain # our $dummyrule = { simple => 1, matches => [], mode => CAT_MODE }; # # Address Family # our $family; # # These are the current builtin targets # our %builtin_target = ( ACCEPT => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, ACCOUNT => STANDARD + MANGLE_TABLE, AUDIT => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, CHAOS => STANDARD + FILTER_TABLE, CHECKSUM => STANDARD + MANGLE_TABLE, CLASSIFY => STANDARD + MANGLE_TABLE, CLUSTERIP => STANDARD + MANGLE_TABLE + RAW_TABLE, CONNMARK => STANDARD + MANGLE_TABLE, CONNSECMARK => STANDARD + MANGLE_TABLE, COUNT => STANDARD + FILTER_TABLE, CT => STANDARD + RAW_TABLE, DELUDE => STANDARD + FILTER_TABLE, DHCPMAC => STANDARD + MANGLE_TABLE, DNAT => STANDARD + NAT_TABLE, DNETMAP => STANDARD + NAT_TABLE, DROP => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, DSCP => STANDARD + MANGLE_TABLE, ECHO => STANDARD + FILTER_TABLE, ECN => STANDARD + MANGLE_TABLE, HL => STANDARD + MANGLE_TABLE, IDLETIMER => STANDARD, IPMARK => STANDARD + MANGLE_TABLE, LOG => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, LOGMARK => STANDARD + MANGLE_TABLE, MARK => STANDARD + FILTER_TABLE + MANGLE_TABLE, MASQUERADE => STANDARD + NAT_TABLE, MIRROR => STANDARD + FILTER_TABLE, NETMAP => STANDARD + NAT_TABLE,, NFLOG => STANDARD + MANGLE_TABLE + RAW_TABLE, NFQUEUE => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, NOTRACK => STANDARD + RAW_TABLE, QUEUE => STANDARD + FILTER_TABLE, RATEEST => STANDARD + MANGLE_TABLE, RAWDNAT => STANDARD + RAW_TABLE, RAWSNAT => STANDARD + RAW_TABLE, REDIRECT => STANDARD + NAT_TABLE, REJECT => STANDARD + FILTER_TABLE, RETURN => STANDARD + MANGLE_TABLE + RAW_TABLE, SAME => STANDARD, SECMARK => STANDARD + MANGLE_TABLE, SET => STANDARD + MANGLE_TABLE + RAW_TABLE, SNAT => STANDARD + NAT_TABLE, STEAL => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, SYSRQ => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, TARPIT => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, TCPMSS => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, TCPOPTSTRIP => STANDARD + MANGLE_TABLE, TEE => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, TOS => STANDARD + MANGLE_TABLE, TPROXY => STANDARD + MANGLE_TABLE, TRACE => STANDARD + RAW_TABLE, TTL => STANDARD + MANGLE_TABLE, ULOG => STANDARD + FILTER_TABLE + NAT_TABLE + MANGLE_TABLE + RAW_TABLE, ); our %ipset_exists; # # Rules are stored in an internal form # # { mode => CAT_MODE if rule is not part of a conditional block or loop # => CMD_MODE if the rule contains a shell command or if it # part of a loop or conditional block. If it is a # shell command, the text of the command is in # the cmd # cmd => Shell command, if mode == CMD_MODE and cmdlevel == 0 # cmdlevel => nesting level within loops and conditional blocks. # determines indentation # simple => true|false. If true, there are no matches or options # jump => 'j' or 'g' (determines whether '-j' or '-g' is included) # Omitted, if target is ''. # target => Rule target, if jump is 'j' or 'g'. # targetopts => Target options. Only included if non-empty #