#!/bin/sh # # The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V3.0 # # This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] # # (c) 1999,2000,2001,2002,2003,2004,2005 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # This program is free software; you can redistribute it and/or modify # it under the terms of Version 2 of the GNU General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # # If an error occurs while starting or restarting the firewall, the # firewall is automatically stopped. # # Commands are: # # shorewall start Starts the firewall # shorewall restart Restarts the firewall # shorewall stop Stops the firewall # shorewall reset Resets iptables packet and # byte counts # shorewall clear Remove all Shorewall chains # and rules/policies. # shorewall refresh . Rebuild the common chain # shorewall check Verify the more heavily-used # configuration files. # # Mutual exclusion -- These functions are jackets for the mutual exclusion # routines in $FUNCTIONS. They invoke # the corresponding function in that file if the user did # not specify "nolock" on the runline. # my_mutex_on() { [ -n "$nolock" ] || { mutex_on; HAVE_MUTEX=Yes; } } my_mutex_off() { [ -n "$HAVE_MUTEX" ] && { mutex_off; HAVE_MUTEX=; } } # # Message to stderr # error_message() # $* = Error Message { echo " $@" >&2 } # # Fatal error -- stops the firewall after issuing the error message # fatal_error() # $* = Error Message { echo " ERROR: $@" >&2 if [ $COMMAND = check ]; then [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR else stop_firewall fi exit 2 } # # Fatal error during startup -- generate an error message and abend without # altering the state of the firewall # startup_error() # $* = Error Message { echo " ERROR: $@" >&2 my_mutex_off [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE kill $$ exit 2 } # # Send a message to STDOUT and the System Log # report () { # $* = message echo "$@" logger "$@" } # # Write the passed args to $RESTOREBASE # save_command() { echo "$@" >> $RESTOREBASE } # # Write a progress_message command to $RESTOREBASE # save_progress_message() { echo >> $RESTOREBASE echo "progress_message \"$@\"" >> $RESTOREBASE echo >> $RESTOREBASE } # # Save the passed command in the restore script then run it -- returns the status of the command # If the command involves file redirection then it must be enclosed in quotes as in: # # run_and_save_command "echo 1 > /proc/sys/net/ipv4/ip_forward" # run_and_save_command() { echo "$@" >> $RESTOREBASE eval $* } # # Run the passed command and if it succeeds, save it in the restore script. If it fails, stop the firewall and die # ensure_and_save_command() { if eval $* ; then echo "$@" >> $RESTOREBASE else [ -z "$STOPPING" ] && { stop_firewall; exit 2; } fi } # # Append a file in /var/lib/shorewall to $RESTOREBASE # append_file() # $1 = File Name { save_command "cat > /var/lib/shorewall/$1 << __EOF__" cat /var/lib/shorewall/$1 >> $RESTOREBASE save_command __EOF__ } # # Run iptables and if an error occurs, stop the firewall and quit # run_iptables() { # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$BRIDGING" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange if ! $IPTABLES $@ ; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"$IPTABLES $@\" Failed" stop_firewall exit 2 fi fi } # # Version of 'run_iptables' that inserts white space after "!" in the arg list # run_iptables2() { case "$@" in *!*) run_iptables $(fix_bang $@) ;; *) run_iptables $@ ;; esac } # # Quietly run iptables # qt_iptables() { # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$BRIDGING" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange qt $IPTABLES $@ } # # Run ip and if an error occurs, stop the firewall and quit # run_ip() { if ! ip $@ ; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"ip $@\" Failed" stop_firewall exit 2 fi fi } # # Run tc and if an error occurs, stop the firewall and quit # run_tc() { if ! tc $@ ; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"tc $@\" Failed" stop_firewall exit 2 fi fi } # # Run ipset and if an error occurs, stop the firewall and quit # run_ipset() { if ! ipset $@ ; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"ipset $@\" Failed" stop_firewall exit 2 fi fi } # # Add the implicit ACCEPT rules at the end of a rules file section # finish_chain_section() # $1 = canonical chain $2 = state list { local policy policychain [ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state $2 -j ACCEPT if list_search RELATED $(separate_list $2) ; then if is_policy_chain $1 ; then if eval test -n \"\$${1}_synparams\" ; then if [ $SECTION = DONE ]; then eval policy=\$${1}_policy case $policy in ACCEPT|CONTINUE|QUEUE) run_iptables -A $1 -p tcp --syn -j @$1 ;; *) esac else run_iptables -A $1 -p tcp --syn -j @$1 fi fi else eval policychain=\$${1}_policychain if eval test -n \"\$${policychain}_synparams\" ; then run_iptables -A $1 -p tcp --syn -j @$policychain fi fi fi } finish_section() # $1 = Section(s) { local zone zone1 chain if [ "$COMMAND" != check ]; then for zone in $ZONES $FW; do for zone1 in $ZONES $FW; do chain=${zone}2${zone1} if havechain $chain; then finish_chain_section $chain $1 fi done done fi } # # Create a filter chain # # If the chain isn't one of the common chains then add a rule to the chain # allowing packets that are part of an established connection. Create a # variable exists_${1} and set its value to Yes to indicate that the chain now # exists. # createchain() # $1 = chain name, $2 = If "yes", do section-end processing { local c=$(chain_base $1) run_iptables -N $1 if [ $2 = yes ]; then case $SECTION in NEW|DONE) finish_chain_section $1 ESTABLISHED,RELATED ;; RELATED) finish_chain_section $1 ESTABLISHED ;; esac fi eval exists_${c}=Yes } createchain2() # $1 = chain name, $2 = If "yes", create default rules { local c=$(chain_base $1) if $IPTABLES -N $1; then if [ $2 = yes ]; then case $SECTION in NEW|DONE) finish_chain_section $1 ESTABLISHED,RELATED ;; RELATED) finish_chain_section $1 ESTABLISHED ;; esac fi eval exists_${c}=Yes fi } # # Determine if a chain exists # # When we create a chain "x", we create a variable named exists_x and # set its value to Yes. This function tests for the "exists_" variable # corresponding to the passed chain having the value of "Yes". # havechain() # $1 = name of chain { local c=$(chain_base $1) eval test \"\$exists_${c}\" = Yes } # # Query NetFilter about the existence of a filter chain # chain_exists() # $1 = chain name { qt $IPTABLES -L $1 -n } # # Query NetFilter about the existence of a mangle chain # mangle_chain_exists() # $1 = chain name { qt $IPTABLES -t mangle -L $1 -n } # # Ensure that a chain exists (create it if it doesn't) # ensurechain() # $1 = chain name { havechain $1 || createchain $1 yes } ensurechain1() # $1 = chain name { havechain $1 || createchain $1 no } # # Add a rule to a chain creating the chain if necessary # addrule() # $1 = chain name, remainder of arguments specify the rule { ensurechain $1 run_iptables -A $@ } addrule2() # $1 = chain name, remainder of arguments specify the rule { ensurechain $1 run_iptables2 -A $@ } # # Create a nat chain # # Create a variable exists_nat_${1} and set its value to Yes to indicate that # the chain now exists. # createnatchain() # $1 = chain name { run_iptables -t nat -N $1 eval exists_nat_${1}=Yes } # # Determine if a nat chain exists # # When we create a chain "chain", we create a variable named exists_nat_chain # and set its value to Yes. This function tests for the "exists_" variable # corresponding to the passed chain having the value of "Yes". # havenatchain() # $1 = name of chain { eval test \"\$exists_nat_${1}\" = Yes } # # Ensure that a nat chain exists (create it if it doesn't) # ensurenatchain() # $1 = chain name { havenatchain $1 || createnatchain $1 } # # Add a rule to a nat chain creating the chain if necessary # addnatrule() # $1 = chain name, remainder of arguments specify the rule { ensurenatchain $1 run_iptables2 -t nat -A $@ } # # Delete a chain if it exists # deletechain() # $1 = name of chain { qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1 } # # Determine if a chain is a policy chain # is_policy_chain() # $1 = name of chain { eval test \"\$${1}_is_policy\" = Yes } # # Set a standard chain's policy # setpolicy() # $1 = name of chain, $2 = policy { run_iptables -P $1 $2 } # # Set a standard chain to enable established and related connections # setcontinue() # $1 = name of chain { run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT } # # Flush one of the NAT table chains # flushnat() # $1 = name of chain { run_iptables -t nat -F $1 } # # Flush one of the Mangle table chains # flushmangle() # $1 = name of chain { run_iptables -t mangle -F $1 } # # This function assumes that the TMP_DIR variable is set and that # its value named an existing directory. # determine_zones() { local zone parent parents rest new_zone_file= r merge_zone() { local z zones="$ZONES" merged= if [ -n "$parents" ]; then ZONES= for z in $zones; do if [ -z "$merged" ] && list_search $z $parents; then ZONES="$ZONES $zone" merged=Yes fi ZONES="$ZONES $z" done else ZONES="$ZONES $zone" fi } strip_file zones ZONES= [ "$IPSECFILE" = zones ] && new_zone_file=Yes while read zone type rest; do expandv zone type case $zone in *:*) parents=${zone#*:} zone=${zone%:*} [ -n "$zone" ] || startup_error "Invalid nested zone syntax: :$parents" parents=$(separate_list $parents) ;; *) parents= ;; esac for parent in $parents; do [ "$parent" = "$FW" ] && startup_error "Sub-zones of the firewall zone are not allowed" list_search $parent $ZONES || startup_error "Parent zone not defined: $parent" done [ ${#zone} -gt 5 ] && startup_error "Zone name longer than 5 characters: $zone" case "$zone" in [0-9*]) startup_error "Illegal zone name \"$zone\" in zones file" ;; all|none) startup_error "Reserved zone name \"$zone\" in zones file" ;; esac if [ -n "$new_zone_file" ]; then case ${type:=ipv4} in ipv4|IPv4|IPV4|plain|-) list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" merge_zone ;; ipsec|IPSEC|ipsec4|IPSEC4) list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" eval ${zone}_is_ipsec=Yes eval ${zone}_is_complex=Yes merge_zone ;; firewall) list_search $zone $ZONES && startup_error "Zone $zone is defined more than once" [ -n "$parents" ] && startup_error "The firewall zone may not be nested" for r in $rest; do [ "x$r" = x- ] || startup_error "OPTIONS not allowed on the firewall zone" done FW=$zone ;; *) startup_error "Invalid Zone Type: $type" ;; esac else list_search $zone $ZONES $FW && startup_error "Zone $zone is defined more than once" ZONES="$ZONES $zone" fi done < $TMP_DIR/zones [ -z "$ZONES" ] && startup_error "No ipv4 or ipsec Zones Defined" [ -z "$FW" ] && startup_error "No Firewall Zone Defined" } # # Find interfaces to a given zone # # Search the variables representing the contents of the interfaces file and # for each record matching the passed ZONE, echo the expanded contents of # the "INTERFACE" column # find_interfaces() # $1 = interface zone { local zne=$1 local z local interface for interface in $ALL_INTERFACES; do eval z=\$$(chain_base $interface)_zone [ "x${z}" = x${zne} ] && echo $interface done } # # Forward Chain for an interface # forward_chain() # $1 = interface { echo $(chain_base $1)_fwd } # # Input Chain for an interface # input_chain() # $1 = interface { echo $(chain_base $1)_in } # # Output Chain for an interface # output_chain() # $1 = interface { echo $(chain_base $1)_out } # # Masquerade Chain for an interface # masq_chain() # $1 = interface { echo $(chain_base $1)_masq } # # MAC Verification Chain for an interface # mac_chain() # $1 = interface { echo $(chain_base $1)_mac } macrecent_target() # $1 - interface { [ -n "$MACLIST_TTL" ] && echo $(chain_base $1)_rec || echo RETURN } # # Functions for creating dynamic zone rules # dynamic_fwd() # $1 = interface { echo $(chain_base $1)_dynf } dynamic_in() # $1 = interface { echo $(chain_base $1)_dyni } dynamic_out() # $1 = interface { echo $(chain_base $1)_dyno } dynamic_chains() #$1 = interface { local c=$(chain_base $1) echo ${c}_dyni ${c}_dynf ${c}_dyno } # # DNAT Chain from a zone # dnat_chain() # $1 = zone { echo ${1}_dnat } # # SNAT Chain to an interface # snat_chain() # $1 = interface { echo $(chain_base $1)_snat } # # ECN Chain to an interface # ecn_chain() # $1 = interface { echo $(chain_base $1)_ecn } # # First chains for an interface # first_chains() #$1 = interface { local c=$(chain_base $1) echo ${c}_fwd ${c}_in } # # Horrible hack to work around an iptables limitation # iprange_echo() { if [ -f $TMP_DIR/iprange ]; then echo $@ else echo "-m iprange $@" > $TMP_DIR/iprange fi } # # Get set flags (ipsets). # get_set_flags() # $1 = set name and optional [levels], $2 = src or dst { local temp setname=$1 options=$2 [ -n "$IPSET_MATCH" ] || fatal_error "Your kernel and/or iptables does not include ipset match: $1" case $1 in *\[[1-6]\]) temp=${1#*\[} temp=${temp%\]} setname=${1%\[*} while [ $temp -gt 1 ]; do options="$options,$2" temp=$(($temp - 1)) done ;; *\[*\]) options=${1#*\[} options=${options%\]} setname=${1%\[*} ;; *) ;; esac echo "--set ${setname#+} $options" } # # Source IP range # source_ip_range() # $1 = Address or Address Range { case $1 in *.*.*.*-*.*.*.*) case $1 in !*) iprange_echo "! --src-range ${1#!}" ;; *) iprange_echo "--src-range $1" ;; esac ;; !+*) echo "-m set ! $(get_set_flags ${1#!} src)" ;; +*) echo "-m set $(get_set_flags $1 src)" ;; *) echo "-s $1" ;; esac } # # Destination IP range # dest_ip_range() # $1 = Address or Address Range { case $1 in *.*.*.*-*.*.*.*) case $1 in !*) iprange_echo "! --dst-range ${1#!}" ;; *) iprange_echo "--dst-range $1" ;; esac ;; !+*) echo "-m set ! $(get_set_flags ${1#!} dst)" ;; +*) echo "-m set $(get_set_flags $1 dst)" ;; *) echo "-d $1" ;; esac } both_ip_ranges() # $1 = Source address or range, $2 = dest address or range { local rangeprefix= setprefix= rangematch= setmatch= case $1 in *.*.*.*-*.*.*.*) rangeprefix="-m iprange" rangematch="--src-range $1" ;; !+*) setprefix="-m set" setmatch="! $(get_set_flags ${1#!} src)" ;; +*) setprefix="-m set" setmatch="$(get_set_flags $1 src)" ;; *) rangematch="-s $1" ;; esac case $2 in *.*.*.*-*.*.*.*) rangeprefix="-m iprange" rangematch="$rangematch --dst-range $2" ;; !+*) setprefix="-m set" match="$setmatch ! $(get_set_flags ${2#!} dst)" ;; +*) setprefix="-m set" setmatch="$setmatch $(get_set_flags $2 dst)" ;; *) rangematch="$rangematch -d $2" ;; esac echo "$rangeprefix $rangematch $setprefix $setmatch" } # # Horrible hack to work around an iptables limitation # physdev_echo() { if [ -f $TMP_DIR/physdev ]; then echo $@ else echo -m physdev $@ > $TMP_DIR/physdev fi } # # We allow hosts to be specified by IP address or by physdev. These two functions # are used to produce the proper match in a netfilter rule. # match_source_hosts() { if [ -n "$BRIDGING" ]; then case $1 in *:*) physdev_echo "--physdev-in ${1%:*} $(source_ip_range ${1#*:})" ;; *.*.*.*|+*|!+*) echo $(source_ip_range $1) ;; *) physdev_echo "--physdev-in $1" ;; esac else echo $(source_ip_range $1) fi } match_dest_hosts() { if [ -n "$BRIDGING" ]; then case $1 in *:*) physdev_echo "--physdev-out ${1%:*} $(dest_ip_range ${1#*:})" ;; *.*.*.*|+*|!+*) echo $(dest_ip_range $1) ;; *) physdev_echo "--physdev-out $1" ;; esac else echo $(dest_ip_range $1) fi } # # Similarly, the source or destination in a rule can be qualified by a device name. If # the device is defined in /etc/shorewall/interfaces then a normal interface match is # generated (-i or -o); otherwise, a physdev match is generated. #------------------------------------------------------------------------------------- # # loosely match the passed interface with those in /etc/shorewall/interfaces. # known_interface() # $1 = interface name { local iface for iface in $ALL_INTERFACES ; do if if_match $iface $1 ; then return 0 fi done return 1 } match_source_dev() { if [ -n "$BRIDGING" ]; then list_search $1 $all_ports && physdev_echo "--physdev-in $1" || echo -i $1 else echo -i $1 fi } match_dest_dev() { if [ -n "$BRIDGING" ]; then list_search $1 $all_ports && physdev_echo "--physdev-out $1" || echo -o $1 else echo -o $1 fi } verify_interface() { known_interface $1 || { [ -n "$BRIDGING" ] && list_search $1 $all_ports ; } } # # Determine if communication to/from a host is encrypted using IPSEC # is_ipsec_host() # $1 = zone, $2 = host { eval local is_ipsec=\$${1}_is_ipsec eval local hosts=\"\$${1}_ipsec_hosts\" test -n "$is_ipsec" || list_search $2 $hosts } # # Generate a match for decrypted packets # match_ipsec_in() # $1 = zone, $2 = host { if is_ipsec_host $1 $2 ; then eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_in_options\" echo "-m policy --pol ipsec --dir in $options" elif [ -n "$POLICY_MATCH" ]; then echo "-m policy --pol none --dir in" fi } # # Generate a match for packets that will be encrypted # match_ipsec_out() # $1 = zone, $2 = host { if is_ipsec_host $1 $2 ; then eval local options=\"\$${1}_ipsec_options \$${1}_ipsec_out_options\" echo "-m policy --pol ipsec --dir out $options" elif [ -n "$POLICY_MATCH" ]; then echo "-m policy --pol none --dir out" fi } # # Jacket for ip_range() that takes care of iprange match # firewall_ip_range() # $1 = IP address or range { [ -n "$IPRANGE_MATCH" ] && echo $1 || ip_range $1 } # # # Find hosts in a given zone # # Read hosts file and for each record matching the passed ZONE, # echo the expanded contents of the "HOST(S)" column # find_hosts() # $1 = host zone { local hosts interface address addresses while read z hosts options; do if [ "x$(expand $z)" = "x$1" ]; then expandv hosts interface=${hosts%%:*} addresses=${hosts#*:} for address in $(separate_list $addresses); do echo $interface:$address done fi done < $TMP_DIR/hosts } # # Determine the interfaces on the firewall # # For each zone, create a variable called ${zone}_interfaces. This # variable contains a space-separated list of interfaces to the zone # determine_interfaces() { for zone in $ZONES; do interfaces=$(find_interfaces $zone) interfaces=$(echo $interfaces) # Remove extra trash eval ${zone}_interfaces=\"\$interfaces\" done } # # Determine if an interface has a given option # interface_has_option() # $1 = interface, #2 = option { local options eval options=\$$(chain_base $1)_options list_search $2 $options } # # Determine the defined hosts in each zone and generate report # determine_hosts() { for zone in $ZONES; do hosts=$(find_hosts $zone) hosts=$(echo $hosts) # Remove extra trash eval interfaces=\$${zone}_interfaces for interface in $interfaces; do if interface_has_option $interface detectnets; then networks=$(get_routed_networks $interface) else networks=0.0.0.0/0 fi for network in $networks; do if [ -z "$hosts" ]; then hosts=$interface:$network else hosts="$hosts $interface:$network" fi if interface_has_option $interface routeback; then eval ${zone}_routeback=\"$interface:$network \$${zone}_routeback\" fi done done interfaces= for host in $hosts; do interface=${host%:*} if list_search $interface $interfaces; then list_search $interface:0.0.0.0/0 $hosts && \ startup_error "Invalid zone definition for zone $zone" list_search $interface:0/0 $hosts && \ startup_error "Invalid zone definition for zone $zone" eval ${zone}_is_complex=Yes else if [ -z "$interfaces" ]; then interfaces=$interface else interfaces="$interfaces $interface" fi fi done eval ${zone}_interfaces="\$interfaces" eval ${zone}_hosts="\$hosts" if [ -n "$hosts" ]; then display_list "$zone Zone:" $hosts else error_message "WARNING: Zone $zone is empty" fi done } # # Ensure that the passed zone is defined in the zones file or is the firewall # validate_zone() # $1 = zone { list_search $1 $ZONES $FW } # # Ensure that the passed zone is defined in the zones file. # validate_zone1() # $1 = zone { list_search $1 $ZONES } # # Validate the zone names and options in the interfaces file # validate_interfaces_file() { local wildcard local found_obsolete_option= local z interface networks options r iface option while read z interface networks options; do expandv z interface networks options r="$z $interface $networks $options" [ "x$z" = "x-" ] && z= if [ -n "$z" ]; then validate_zone $z || startup_error "Invalid zone ($z) in record \"$r\"" fi list_search $interface $ALL_INTERFACES && \ startup_error "Duplicate Interface $interface" wildcard= case $interface in *:*|+) startup_error "Invalid Interface Name: $interface" ;; *+) wildcard=Yes ;; esac ALL_INTERFACES="$ALL_INTERFACES $interface" options=$(separate_list $options) iface=$(chain_base $interface) eval ${iface}_broadcast="$networks" eval ${iface}_zone="$z" eval ${iface}_options=\"$options\" for option in $options; do case $option in -) ;; dhcp|tcpflags|arp_filter|routefilter|logmartians|sourceroute|blacklist|proxyarp|maclist|nosmurfs|upnp|-) ;; norfc1918) addr=$(ip -f inet addr show $interface 2> /dev/null | grep inet | head -n1) if [ -n "$addr" ]; then addr=$(echo $addr | sed 's/inet //;s/\/.*//;s/ peer.*//') for network in 10.0.0.0/8 176.16.0.0/12 192.168.0.0/16; do if in_network $addr $network; then startup_error "The 'norfc1918' option may not be specified on an interface with an RFC 1918 address. Interface:$interface" fi done fi ;; arp_ignore=*) eval ${iface}_arp_ignore=${option#*=} ;; arp_ignore) eval ${iface}_arp_ignore=1 ;; detectnets) [ -n "$wildcard" ] && \ startup_error "The \"detectnets\" option may not be used with a wild-card interface" ;; routeback) [ -n "$z" ] || startup_error "The routeback option may not be specified on a multi-zone interface" ;; *) error_message "WARNING: Invalid option ($option) in record \"$r\"" ;; esac done done < $TMP_DIR/interfaces [ -z "$ALL_INTERFACES" ] && startup_error "No Interfaces Defined" } # # Check that a mark value or mask is less that 256 # verify_mark() # $1 = value to test { verify_mark1() { [ $1 -lt 256 ] } verify_mark2() { verify_mark1 $1 2> /dev/null } verify_mark2 $1 || fatal_error "Invalid Mark or Mask value: $1" } # # Process the providers file # setup_providers() { local table number mark duplicate interface gateway options provider address copy route loose addresses rulenum pref echobin=$(mywhich echo) copy_table() { run_ip route show table $duplicate | while read net route; do case $net in default|nexthop) ;; *) ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add table $number $net $route" ;; esac done } copy_and_edit_table() { run_ip route show table $duplicate | while read net route; do case $net in default|nexthop) ;; *) if list_search $(find_device $route) $copy; then ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add table $number $net $route" fi ;; esac done } add_a_provider() { local t n iface option for t in $PROVIDERS; do if [ "$t" = "$table" ]; then fatal_error "Duplicate Provider: $table, provider: \"$provider\"" fi eval n=\$${t}_number if [ $n -eq $number ]; then fatal_error "Duplicate Provider number: $number, provider: \"$provider\"" fi done eval ${table}_number=$number if [ $COMMAND != check ]; then run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip route flush table $number" if [ "x${duplicate:=-}" != x- ]; then if [ "x${copy:=-}" != "x-" ]; then copy="$interface $(separate_list $copy)" copy_and_edit_table else copy_table fi fi fi if [ "x$gateway" = xdetect ] ; then # # First assume that this is some sort of point-to-point interface # gateway=$( find_peer $(ip addr ls $interface ) ) # # Maybe there's a default route through this gateway already # [ -n "$gateway" ] || gateway=$(find_gateway $(ip route ls dev $interface)) # # Last hope -- is there a load-balancing route through the interface? # [ -n "$gateway" ] || gateway=$(find_nexthop $interface) # # Be sure we found one # [ -n "$gateway" ] || fatal_error "Unable to detect the gateway through interface $interface" fi if [ $COMMAND != check ]; then ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace $gateway dev $interface table $number" ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route add default via $gateway dev $interface table $number" fi if [ x${mark} != x- ]; then verify_mark $mark eval ${table}_mark=$mark if [ $COMMAND != check ]; then run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip rule del fwmark $mark" ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip rule add fwmark $mark pref $((10000 + $mark)) table $number" fi fi loose= for option in $(separate_list $options); do case $option in -) ;; track) list_search $interface $ROUTEMARK_INTERFACES && \ fatal_error "Interface $interface is tracked through an earlier provider" iface=$(chain_base $interface) [ x${mark} = x- ] && fatal_error "The 'track' option requires a numeric value in the MARK column - Provider \"$provider\"" eval ${iface}_routemark=$mark ROUTEMARK_INTERFACES="$ROUTEMARK_INTERFACES $interface" ;; balance=*) DEFAULT_ROUTE="$DEFAULT_ROUTE nexthop via $gateway dev $interface weight ${option#*=}" ;; balance) DEFAULT_ROUTE="$DEFAULT_ROUTE nexthop via $gateway dev $interface weight 1" ;; loose) loose=Yes ;; *) error_message " WARNING: Invalid option ($option) ignored in provider \"$provider\"" ;; esac done rulenum=0 if [ $COMMAND != check ]; then find_interface_addresses $interface | while read address; do run_and_save_command "[ -n \"\$NOROUTES\" ] || qt ip rule del from $address" if [ -z "$loose" ]; then pref=$((20000 + $rulenum * 1000 + $number )) rulenum=$(($rulenum + 1)) ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip rule add from $address pref $pref table $number" fi done fi } strip_file providers $1 if [ -s $TMP_DIR/providers ]; then if [ $COMMAND != check ]; then echo "Processing $1..." save_progress_message "Restoring Providers..." else echo "Validating $1..." fi while read table number mark duplicate interface gateway options copy; do expandv table number mark duplicate interface gateway options copy provider="$table $number $mark $duplicate $interface $gateway $options $copy" add_a_provider PROVIDERS="$PROVIDERS $table" progress_message " Provider $provider Added" done < $TMP_DIR/providers if [ $COMMAND != check ]; then if [ -n "$PROVIDERS" ]; then if [ -n "$DEFAULT_ROUTE" ]; then ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace default scope global $DEFAULT_ROUTE" progress_message " Default route $DEFAULT_ROUTE Added." fi cat > /etc/iproute2/rt_tables <> /etc/iproute2/rt_tables done save_command "cat > /etc/iproute2/rt_tables << __EOF__" cat /etc/iproute2/rt_tables >> $RESTOREBASE save_command __EOF__ fi ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route flush cache" fi fi } # # Validate the zone names and options in the hosts file # validate_hosts_file() { local z hosts options r interface host option port ports check_bridge_port() { list_search $1 $ports || ports="$ports $1" list_search ${interface}:${1} $zports || zports="$zports ${interface}:${1}" list_search $1 $all_ports || all_ports="$all_ports $1" } while read z hosts options; do expandv z hosts options r="$z $hosts $options" validate_zone1 $z || startup_error "Invalid zone ($z) in record \"$r\"" case $hosts in *:*) interface=${hosts%%:*} iface=$(chain_base $interface) list_search $interface $ALL_INTERFACES || \ startup_error "Unknown interface ($interface) in record \"$r\"" hosts=${hosts#*:} ;; *) fatal_error "Invalid HOST(S) column contents: $hosts" ;; esac eval ports=\$${iface}_ports eval zports=\$${z}_ports for host in $(separate_list $hosts); do if [ -n "$BRIDGING" ]; then case $host in *:*) known_interface ${host%:*} && \ startup_error "Bridged interfaces may not be defined in /etc/shorewall/interfaces: $host" check_bridge_port ${host%%:*} ;; *.*.*.*) ;; +*) eval ${z}_is_complex=Yes ;; *) known_interface $host && \ startup_error "Bridged interfaces may not be defined in /etc/shorewall/interfaces: $host" check_bridge_port $host ;; esac else case $host in +*) eval ${z}_is_complex=Yes ;; esac fi for option in $(separate_list $options) ; do case $option in maclist|norfc1918|blacklist|tcpflags|nosmurfs|-) ;; ipsec) [ -n "$POLICY_MATCH" ] || \ startup_error "Your kernel and/or iptables does not support policy match: ipsec" eval ${z}_ipsec_hosts=\"\$${z}_ipsec_hosts $interface:$host\" eval ${z}_is_complex=Yes ;; routeback) [ -z "$ports" ] && \ eval ${z}_routeback=\"$interface:$host \$${z}_routeback\" ;; *) error_message "WARNING: Invalid option ($option) in record \"$r\"" ;; esac done done if [ -n "$ports" ]; then eval ${iface}_ports=\"$ports\" eval ${z}_ports=\"$zports\" fi done < $TMP_DIR/hosts [ -n "$all_ports" ] && echo " Bridge ports are: $all_ports" } # # Format a match by the passed MAC address # The passed address begins with "~" and uses "-" as a separator between bytes # Example: ~01-02-03-04-05-06 # mac_match() # $1 = MAC address formated as described above { echo "--match mac --mac-source $(echo $1 | sed 's/~//;s/-/:/g')" } # # validate the policy file # validate_policy() { local clientwild local serverwild local zone local zone1 local pc local chain local policy local loglevel local synparams print_policy() # $1 = source zone, $2 = destination zone { [ $COMMAND != check ] || \ [ $1 = $2 ] || \ [ $1 = all ] || \ [ $2 = all ] || \ progress_message " Policy for $1 to $2 is $policy using chain $chain" } ALL_POLICY_CHAINS= for zone in $ZONES $FW; do chain=${zone}2${zone} eval ${chain}_is_policy=Yes eval ${chain}_is_optional=Yes eval ${chain}_policy=ACCEPT eval ${chain}_policychain=$chain ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain" done strip_file policy while read client server policy loglevel synparams; do expandv client server policy loglevel synparams clientwild= serverwild= case "$client" in all|ALL) clientwild=Yes ;; *) if ! validate_zone $client; then startup_error "Undefined zone $client" fi esac case "$server" in all|ALL) serverwild=Yes ;; *) if ! validate_zone $server; then startup_error "Undefined zone $server" fi esac case $policy in ACCEPT|REJECT|DROP|CONTINUE|QUEUE) ;; NONE) [ "$client" = "$FW" -o "$server" = "$FW" ] && \ startup_error " $client $server $policy $loglevel $synparams: NONE policy not allowed to/from the $FW zone" [ -n "$clientwild" -o -n "$serverwild" ] && \ startup_error " $client $server $policy $loglevel $synparams: NONE policy not allowed with \"all\"" ;; *) startup_error "Invalid policy $policy" ;; esac chain=${client}2${server} if is_policy_chain $chain ; then if eval test \$${chain}_is_optional = Yes ; then eval ${chain}_is_optional= else startup_error "Duplicate policy: $client $server $policy" fi fi [ "x$loglevel" = "x-" ] && loglevel= [ "x$synparams" = "x-" ] && synparams= [ $policy = NONE ] || ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain" eval ${chain}_is_policy=Yes eval ${chain}_policy=$policy eval ${chain}_loglevel=$loglevel eval ${chain}_synparams=$synparams if [ -n "${clientwild}" ]; then if [ -n "${serverwild}" ]; then for zone in $ZONES $FW all; do for zone1 in $ZONES $FW all; do eval pc=\$${zone}2${zone1}_policychain if [ -z "$pc" ]; then eval ${zone}2${zone1}_policychain=$chain eval ${zone}2${zone1}_policy=$policy print_policy $zone $zone1 fi done done else for zone in $ZONES $FW all; do eval pc=\$${zone}2${server}_policychain if [ -z "$pc" ]; then eval ${zone}2${server}_policychain=$chain eval ${zone}2${server}_policy=$policy print_policy $zone $server fi done fi elif [ -n "$serverwild" ]; then for zone in $ZONES $FW all; do eval pc=\$${client}2${zone}_policychain if [ -z "$pc" ]; then eval ${client}2${zone}_policychain=$chain eval ${client}2${zone}_policy=$policy print_policy $client $zone fi done else eval ${chain}_policychain=${chain} print_policy $client $server fi done < $TMP_DIR/policy } # # Find broadcast addresses # find_broadcasts() { for interface in $ALL_INTERFACES; do eval bcast=\$$(chain_base $interface)_broadcast if [ "x$bcast" = "xdetect" ]; then ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u elif [ "x${bcast}" != "x-" ]; then echo $(separate_list $bcast) fi done } # # Find interface address--returns the first IP address assigned to the passed # device # find_first_interface_address() # $1 = interface { # # get the line of output containing the first IP address # addr=$(ip -f inet addr show $1 2> /dev/null | grep inet | head -n1) # # If there wasn't one, bail out now # [ -n "$addr" ] || fatal_error "Can't determine the IP address of $1" # # Strip off the trailing VLSM mask (or the peer IP in case of a P-t-P link) # along with everything else on the line # echo $addr | sed 's/inet //;s/\/.*//;s/ peer.*//' } # # Find interfaces that have the passed option specified # find_interfaces_by_option() # $1 = option { for interface in $ALL_INTERFACES; do eval options=\$$(chain_base $interface)_options list_search $1 $options && echo $interface done } # # This slightly slower version is used to find both the option and option followed # by equal sign ("=") and a value # find_interfaces_by_option1() # $1 = option { local options option for interface in $ALL_INTERFACES; do eval options=\$$(chain_base $interface)_options for option in $options; do if [ "${option%=*}" = "$1" ]; then echo $interface break fi done done } # # Find hosts with the passed option # find_hosts_by_option() # $1 = option { local ignore hosts interface address addresses options ipsec= list while read ignore hosts options; do expandv options list=$(separate_list $options) if list_search $1 $list; then list_search ipsec $list && ipsec=ipsec || ipsec=none expandv hosts interface=${hosts%%:*} addresses=${hosts#*:} for address in $(separate_list $addresses); do echo ${ipsec}^$interface:$address done fi done < $TMP_DIR/hosts for interface in $ALL_INTERFACES; do interface_has_option $interface $1 && \ echo none^${interface}:0.0.0.0/0 done } # # Flush and delete all user-defined chains in the filter table # deleteallchains() { run_iptables -F run_iptables -X } ## # Source a user exit file if it exists # run_user_exit() # $1 = file name { local user_exit=$(find_file $1) if [ -f $user_exit ]; then progress_message "Processing $user_exit ..." . $user_exit fi } # # Add a logging rule. # log_rule_limit() # $1 = log level, $2 = chain, $3 = display Chain $4 = disposition , $5 = rate limit $6=log tag $7=command $... = predicates for the rule { local level=$1 local chain=$2 local displayChain=$3 local disposition=$4 local rulenum= local limit="${5:-$LOGLIMIT}" local tag=${6:+$6 } local command=${7:--A} local prefix local base=$(chain_base $displayChain) shift;shift;shift;shift;shift;shift;shift if [ -n "$tag" -a -n "$LOGTAGONLY" ]; then displayChain=$tag tag= fi if [ -n "$LOGRULENUMBERS" ]; then eval rulenum=\$${base}_logrules rulenum=${rulenum:-1} prefix="$(printf "$LOGFORMAT" $displayChain $rulenum $disposition)${tag}" rulenum=$(($rulenum + 1)) eval ${base}_logrules=$rulenum else prefix="$(printf "$LOGFORMAT" $displayChain $disposition)${tag}" fi if [ ${#prefix} -gt 29 ]; then prefix="$(echo $prefix | truncate 29)" error_message "WARNING: Log Prefix shortened to \"$prefix\"" fi case $level in ULOG) if ! $IPTABLES $command $chain $@ $limit -j ULOG $LOGPARMS --ulog-prefix "$prefix" ; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"$IPTABLES $command $chain $@ $limit -j ULOG $LOGPARMS --ulog-prefix \"$prefix\"\" Failed" stop_firewall exit 2 fi fi ;; *) if ! $IPTABLES $command $chain $@ $limit -j LOG $LOGPARMS --log-level $level --log-prefix "$prefix"; then if [ -z "$STOPPING" ]; then error_message "ERROR: Command \"$IPTABLES $command $chain $@ $limit -j LOG $LOGPARMS --log-level $level --log-prefix \"$prefix\"\" Failed" stop_firewall exit 2 fi fi ;; esac if [ $? -ne 0 ] ; then [ -z "$STOPPING" ] && { stop_firewall; exit 2; } fi } log_rule() # $1 = log level, $2 = chain, $3 = disposition , $... = predicates for the rule { local level=$1 local chain=$2 local disposition=$3 shift;shift;shift log_rule_limit $level $chain $chain $disposition "$LOGLIMIT" "" -A $@ } # # Set /proc/sys/net/ipv4/ip_forward based on $IP_FORWARDING # setup_forwarding() { save_progress_message "Restoring IP Forwarding..." case "$IP_FORWARDING" in [Oo][Nn]) run_and_save_command "echo 1 > /proc/sys/net/ipv4/ip_forward" echo "IP Forwarding Enabled" ;; [Oo][Ff][Ff]) run_and_save_command "echo 0 > /proc/sys/net/ipv4/ip_forward" echo "IP Forwarding Disabled!" ;; esac } # # Disable IPV6 # disable_ipv6() { local foo="$(ip -f inet6 addr ls 2> /dev/null)" if [ -n "$foo" ]; then if qt mywhich ip6tables; then save_progress_message "Disabling IPV6..." ip6tables -P FORWARD DROP && save_command ip6tables -P FORWARD DROP ip6tables -P INPUT DROP && save_command ip6tables -P INPUT DROP ip6tables -P OUTPUT DROP && save_command ip6tables -P OUTPUT DROP else error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables" fi fi } disable_ipv6_1() { local foo="$(ip -f inet6 addr ls 2> /dev/null)" if [ -n "$foo" ]; then if qt mywhich ip6tables; then progress_message "Disabling IPV6..." ip6tables -P FORWARD DROP ip6tables -P INPUT DROP ip6tables -P OUTPUT DROP else error_message "WARNING: DISABLE_IPV6=Yes in shorewall.conf but this system does not appear to have ip6tables" fi fi } # # Process the routestopped file either adding or deleting rules # process_routestopped() # $1 = command { local hosts= interface host host1 options networks source= dest= matched while read interface host options; do expandv interface host options [ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0 for h in $(separate_list $host); do hosts="$hosts $interface:$h" done routeback= if [ -n "$options" ]; then for option in $(separate_list $options); do case $option in routeback) if [ -n "$routeback" ]; then error_message "WARNING: Duplicate routestopped option ignored: routeback" else routeback=Yes for h in $(separate_list $host); do run_iptables $1 FORWARD -i $interface -o $interface $(both_ip_ranges $h $h) -j ACCEPT done fi ;; source) for h in $(separate_list $host); do source="$source $interface:$h" done ;; dest) for h in $(separate_list $host); do dest="$dest $interface:$h" done ;; critical) ;; *) error_message "WARNING: Unknown routestopped option ignored: $option" ;; esac done fi done < $TMP_DIR/routestopped for host in $hosts; do interface=${host%:*} networks=${host#*:} $IPTABLES $1 INPUT -i $interface $(source_ip_range $networks) -j ACCEPT [ -z "$ADMINISABSENTMINDED" -o $COMMAND != stop ] && \ run_iptables $1 OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT matched= if list_search $host $source ; then run_iptables $1 FORWARD -i $interface $(source_ip_range $networks) -j ACCEPT matched=Yes fi if list_search $host $dest ; then run_iptables $1 FORWARD -o $interface $(dest_ip_range $networks) -j ACCEPT matched=Yes fi if [ -z "$matched" ]; then for host1 in $hosts; do [ "$host" != "$host1" ] && run_iptables $1 FORWARD -i $interface -o ${host1%:*} $(both_ip_ranges $networks ${host1#*:}) -j ACCEPT done fi done } process_criticalhosts() { local hosts= interface host h options networks criticalhosts= [ -f $TMP_DIR/routestopped ] || strip_file routestopped while read interface host options; do expandv interface host options [ "x$host" = "x-" -o -z "$host" ] && host=0.0.0.0/0 || host=$(separate_list $host) if [ -n "$options" ]; then for option in $(separate_list $options); do case $option in routeback|source|dest) ;; critical) for h in $host; do criticalhosts="$criticalhosts $interface:$h" done ;; *) error_message "WARNING: Unknown routestopped option ignored: $option" ;; esac done fi done < $TMP_DIR/routestopped if [ -n "$criticalhosts" ]; then CRITICALHOSTS=$criticalhosts progress_message "Critical Hosts are:$CRITICALHOSTS" fi } # # For each entry in the CRITICALHOSTS global list, add INPUT and OUTPUT rules to # enable traffic to/from those hosts. # enable_critical_hosts() { for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} $IPTABLES -A INPUT -i $interface $(source_ip_range $networks) -j ACCEPT $IPTABLES -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done } # # For each entry in the CRITICALHOSTS global list, delete the INPUT and OUTPUT rules that # enable traffic to/from those hosts. # disable_critical_hosts() { for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} $IPTABLES -D INPUT -i $interface $(source_ip_range $networks) -j ACCEPT $IPTABLES -D OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done } # # Stop the Firewall # stop_firewall() { # # Turn off trace unless we were tracing "stop" or "clear" # [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE case $COMMAND in stop|clear) ;; check) kill $$ exit 2 ;; *) set +x [ -n "${RESTOREFILE:=restore}" ] RESTOREPATH=/var/lib/shorewall/$RESTOREFILE if [ -x $RESTOREPATH ]; then if [ -x ${RESTOREPATH}-ipsets ]; then echo Restoring Ipsets... # # We must purge iptables to be sure that there are no # references to ipsets # iptables -F iptables -X ${RESTOREPATH}-ipsets fi echo Restoring Shorewall... if $RESTOREPATH; then echo "Shorewall restored from $RESTOREPATH" set_state "Started" else set_state "Unknown" fi my_mutex_off kill $$ exit 2 fi ;; esac set_state "Stopping" STOPPING="Yes" TERMINATOR= deletechain shorewall run_user_exit stop [ -n "$MANGLE_ENABLED" ] && \ run_iptables -t mangle -F && \ run_iptables -t mangle -X [ -n "$RAW_TABLE" ] && \ run_iptables -t raw -F && \ run_iptables -t raw -X [ -n "$NAT_ENABLED" ] && delete_nat delete_proxy_arp [ -n "$CLEAR_TC" ] && delete_tc1 [ -n "$DISABLE_IPV6" ] && disable_ipv6_1 process_criticalhosts if [ -n "$CRITICALHOSTS" ]; then if [ -z "$ADMINISABSENTMINDED" ]; then for chain in INPUT OUTPUT; do setpolicy $chain ACCEPT done setpolicy FORWARD DROP deleteallchains enable_critical_hosts for chain in INPUT OUTPUT; do setpolicy $chain DROP done else for chain in INPUT OUTPUT; do setpolicy $chain ACCEPT done setpolicy FORWARD DROP deleteallchains enable_critical_hosts setpolicy INPUT DROP for chain in INPUT FORWARD; do setcontinue $chain done fi elif [ -z "$ADMINISABSENTMINDED" ]; then for chain in INPUT OUTPUT FORWARD; do setpolicy $chain DROP done deleteallchains else for chain in INPUT FORWARD; do setpolicy $chain DROP done setpolicy OUTPUT ACCEPT deleteallchains for chain in INPUT FORWARD; do setcontinue $chain done fi process_routestopped -A $IPTABLES -A INPUT -i lo -j ACCEPT [ -z "$ADMINISABSENTMINDED" ] && \ $IPTABLES -A OUTPUT -o lo -j ACCEPT for interface in $(find_interfaces_by_option dhcp); do $IPTABLES -A INPUT -p udp -i $interface --dport 67:68 -j ACCEPT [ -z "$ADMINISABSENTMINDED" ] && \ $IPTABLES -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT # # This might be a bridge # $IPTABLES -A FORWARD -p udp -i $interface -o $interface --dport 67:68 -j ACCEPT done case "$IP_FORWARDING" in [Oo][Nn]) echo 1 > /proc/sys/net/ipv4/ip_forward echo "IP Forwarding Enabled" ;; [Oo][Ff][Ff]) echo 0 > /proc/sys/net/ipv4/ip_forward echo "IP Forwarding Disabled!" ;; esac run_user_exit stopped set_state "Stopped" logger "Shorewall Stopped" rm -rf $TMP_DIR case $COMMAND in stop|clear) ;; *) # # The firewall is being stopped when we were trying to do something # else. Remove the lock file and Kill the shell in case we're in a # subshell # my_mutex_off kill $$ ;; esac } # # Remove all rules and remove all user-defined chains # clear_firewall() { stop_firewall setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT run_iptables -F echo 1 > /proc/sys/net/ipv4/ip_forward if qt mywhich ip6tables; then ip6tables -P INPUT ACCEPT 2> /dev/null ip6tables -P OUTPUT ACCEPT 2> /dev/null ip6tables -P FORWARD ACCEPT 2> /dev/null fi run_user_exit clear set_state "Cleared" logger "Shorewall Cleared" } # # Set up ipsec tunnels # setup_tunnels() # $1 = name of tunnels file { local inchain local outchain setup_one_ipsec() # $1 = gateway $2 = Tunnel Kind $3 = gateway zones { local kind=$2 noah= case $kind in *:*) noah=${kind#*:} [ $noah = noah -o $noah = NOAH ] || fatal_error "Invalid IPSEC modifier $noah in tunnel \"$tunnel\"" kind=${kind%:*} ;; esac [ $kind = IPSEC ] && kind=ipsec options="-m state --state NEW -j ACCEPT" addrule2 $inchain -p 50 $(source_ip_range $1) -j ACCEPT addrule2 $outchain -p 50 $(dest_ip_range $1) -j ACCEPT if [ -z "$noah" ]; then run_iptables -A $inchain -p 51 $(source_ip_range $1) -j ACCEPT run_iptables -A $outchain -p 51 $(dest_ip_range $1) -j ACCEPT fi run_iptables -A $outchain -p udp $(dest_ip_range $1) --dport 500 $options if [ $kind = ipsec ]; then run_iptables -A $inchain -p udp $(source_ip_range $1) --dport 500 $options else run_iptables -A $inchain -p udp $(source_ip_range $1) --dport 500 $options run_iptables -A $inchain -p udp $(source_ip_range $1) --dport 4500 $options fi for z in $(separate_list $3); do if validate_zone $z; then addrule ${FW}2${z} -p udp --dport 500 $options if [ $kind = ipsec ]; then addrule ${z}2${FW} -p udp --dport 500 $options else addrule ${z}2${FW} -p udp --dport 500 $options addrule ${z}2${FW} -p udp --dport 4500 $options fi else fatal_error "Invalid gateway zone ($z) -- Tunnel \"$tunnel\"" fi done progress_message " IPSEC tunnel to $gateway defined." } setup_one_other() # $1 = TYPE, $2 = gateway, $3 = protocol { addrule2 $inchain -p $3 $(source_ip_range $2) -j ACCEPT addrule2 $outchain -p $3 $(dest_ip_range $2) -j ACCEPT progress_message " $1 tunnel to $2 defined." } setup_pptp_client() # $1 = gateway { addrule2 $outchain -p 47 $(dest_ip_range $1) -j ACCEPT addrule2 $inchain -p 47 $(source_ip_range $1) -j ACCEPT addrule2 $outchain -p tcp --dport 1723 $(dest_ip_range $1) -j ACCEPT progress_message " PPTP tunnel to $1 defined." } setup_pptp_server() # $1 = gateway { addrule2 $inchain -p 47 $(source_ip_range $1) -j ACCEPT addrule2 $outchain -p 47 $(dest_ip_range $1) -j ACCEPT addrule2 $inchain -p tcp --dport 1723 $(source_ip_range $1) -j ACCEPT progress_message " PPTP server defined." } setup_one_openvpn() # $1 = gateway, $2 = kind[:port] { local protocol=udp local p=1194 case $2 in *:*:*) protocol=${2%:*} protocol=${protocol#*:} p=${2##*:} ;; *:*) p=${2#*:} ;; esac addrule2 $inchain -p $protocol $(source_ip_range $1) --dport $p -j ACCEPT addrule2 $outchain -p $protocol $(dest_ip_range $1) --dport $p -j ACCEPT progress_message " OPENVPN tunnel to $1:$protocol:$p defined." } setup_one_openvpn_server() # $1 = gateway, $2 = kind[:port] { local protocol=udp local p=1194 case $2 in *:*:*) protocol=${2%:*} protocol=${protocol#*:} p=${2##*:} ;; *:*) p=${2#*:} ;; esac addrule2 $inchain -p $protocol $(source_ip_range $1) --dport $p -j ACCEPT addrule2 $outchain -p $protocol $(dest_ip_range $1) --sport $p -j ACCEPT progress_message " OPENVPN server tunnel from $1:$protocol:$p defined." } setup_one_openvpn_client() # $1 = gateway, $2 = kind[:port] { local protocol=udp local p=1194 case $2 in *:*:*) protocol=${2%:*} protocol=${protocol#*:} p=${2##*:} ;; *:*) p=${2#*:} ;; esac addrule2 $inchain -p $protocol $(source_ip_range $1) --sport $p -j ACCEPT addrule2 $outchain -p $protocol $(dest_ip_range $1) --dport $p -j ACCEPT progress_message " OPENVPN client tunnel to $1:$protocol:$p defined." } setup_one_generic() # $1 = gateway, $2 = kind:protocol[:port], $3 = Gateway Zone { local protocol local p= case $2 in *:*:*) p=${2##*:} protocol=${2%:*} protocol=${protocol#*:} ;; *:*) protocol=${2#*:} ;; *) protocol=udp p=5000 ;; esac p=${p:+--dport $p} addrule2 $inchain -p $protocol $(source_ip_range $1) $p -j ACCEPT addrule2 $outchain -p $protocol $(dest_ip_range $1) $p -j ACCEPT for z in $(separate_list $3); do if validate_zone $z; then addrule ${FW}2${z} -p $protocol $p -j ACCEPT addrule ${z}2${FW} -p $protocol $p -j ACCEPT else error_message "Warning: Invalid gateway zone ($z)" \ " -- Tunnel \"$tunnel\" may encounter problems" fi done progress_message " GENERIC tunnel to $1:$p defined." } strip_file tunnels $1 while read kind z gateway z1; do expandv kind z gateway z1 tunnel="$(echo $kind $z $gateway $z1)" if validate_zone $z; then inchain=${z}2${FW} outchain=${FW}2${z} gateway=${gateway:-0.0.0.0/0} case $kind in ipsec|IPSEC|ipsec:*|IPSEC:*) setup_one_ipsec $gateway $kind $z1 ;; ipsecnat|IPSECNAT|ipsecnat:*|IPSECNAT:*) setup_one_ipsec $gateway $kind $z1 ;; ipip|IPIP) setup_one_other IPIP $gateway 4 ;; gre|GRE) setup_one_other GRE $gateway 47 ;; 6to4|6TO4) setup_one_other 6to4 $gateway 41 ;; pptpclient|PPTPCLIENT) setup_pptp_client $gateway ;; pptpserver|PPTPSERVER) setup_pptp_server $gateway ;; openvpn|OPENVPN|openvpn:*|OPENVPN:*) setup_one_openvpn $gateway $kind ;; openvpnclient|OPENVPNCLIENT|openvpnclient:*|OPENVPNCLIENT:*) setup_one_openvpn_client $gateway $kind ;; openvpnserver|OPENVPNSERVER|openvpnserver:*|OPENVPNSERVER:*) setup_one_openvpn_server $gateway $kind ;; generic:*|GENERIC:*) setup_one_generic $gateway $kind $z1 ;; *) error_message "Tunnels of type $kind are not supported:" \ "Tunnel \"$tunnel\" Ignored" ;; esac else error_message "Invalid gateway zone ($z)" \ " -- Tunnel \"$tunnel\" Ignored" fi done < $TMP_DIR/tunnels } # # Process the ipsec information in the zones file # setup_ipsec() { local zone using_ipsec= # # Add a --set-mss rule to the passed chain # set_mss1() # $1 = chain, $2 = MSS { eval local policy=\$${1}_policy if [ "$policy" != NONE ]; then case $COMMAND in start|restart) ensurechain $1 run_iptables -I $1 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss $2 ;; esac fi } # # Set up rules to set MSS to and/or from zone "$zone" # set_mss() # $1 = MSS value, $2 = _in, _out or "" { if [ $COMMAND != check ]; then for z in $ZONES; do case $2 in _in) set_mss1 ${zone}2${z} $1 ;; _out) set_mss1 ${z}2${zone} $1 ;; *) set_mss1 ${z}2${zone} $1 set_mss1 ${zone}2${z} $1 ;; esac done fi } do_options() # $1 = _in, _out or "" - $2 = option list { local option opts newoptions= val [ x${2} = x- ] && return opts=$(separate_list $2) for option in $opts; do val=${option#*=} case $option in mss=[0-9]*) set_mss $val $1 ;; strict) newoptions="$newoptions --strict" ;; next) newoptions="$newoptions --next" ;; reqid=*) newoptions="$newoptions --reqid $val" ;; spi=*) newoptions="$newoptions --spi $val" ;; proto=*) newoptions="$newoptions --proto $val" ;; mode=*) newoptions="$newoptions --mode $val" ;; tunnel-src=*) newoptions="$newoptions --tunnel-src $val" ;; tunnel-dst=*) newoptions="$newoptions --tunnel-dst $val" ;; reqid!=*) newoptions="$newoptions ! --reqid $val" ;; spi!=*) newoptions="$newoptions ! --spi $val" ;; proto!=*) newoptions="$newoptions ! --proto $val" ;; mode!=*) newoptions="$newoptions ! --mode $val" ;; tunnel-src!=*) newoptions="$newoptions ! --tunnel-src $val" ;; tunnel-dst!=*) newoptions="$newoptions ! --tunnel-dst $val" ;; *) fatal_error "Invalid option \"$option\" for zone $zone" ;; esac done if [ -n "$newoptions" ]; then [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" eval ${zone}_is_complex=Yes eval ${zone}_ipsec${1}_options=\"${newoptions# }\" fi } case $IPSECFILE in zones) f=zones progress_message "Setting up IPSEC..." ;; *) f=$IPSECFILE strip_file $f progress_message "Processing $f..." using_ipsec=Yes ;; esac while read zone type options in_options out_options mss; do expandv zone type options in_options out_options mss if [ -n "$using_ipsec" ]; then validate_zone1 $zone || fatal_error "Unknown zone: $zone" fi if [ -n "$type" ]; then if [ -n "$using_ipsec" ]; then case $type in No|no) ;; Yes|yes) [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" eval ${zone}_is_ipsec=Yes eval ${zone}_is_complex=Yes ;; *) fatal_error "Invalid IPSEC column contents" ;; esac fi do_options "" $options do_options "_in" $in_options do_options "_out" $out_options fi done < $TMP_DIR/$f } ## # Setup Proxy ARP # setup_proxy_arp() { local setlist= resetlist= print_error() { error_message "Invalid value for HAVEROUTE - ($haveroute)" error_message "Entry \"$address $interface $external $haveroute\" ignored" } print_error1() { error_message "Invalid value for PERSISTENT - ($persistent)" error_message "Entry \"$address $interface $external $haveroute $persistent\" ignored" } print_warning() { error_message "PERSISTENT setting ignored - ($persistent)" error_message "Entry \"$address $interface $external $haveroute $persistent\"" } setup_one_proxy_arp() { case $haveroute in [Nn][Oo]) haveroute= ;; [Yy][Ee][Ss]) ;; *) if [ -n "$haveroute" ]; then print_error return fi ;; esac case $persistent in [Nn][Oo]) persistent= ;; [Yy][Ee][Ss]) [ -z "$haveroute" ] || print_warning ;; *) if [ -n "$persistent" ]; then print_error1 return fi ;; esac if [ $COMMAND != check ]; then if [ -z "$haveroute" ]; then ensure_and_save_command "[ -n \"\$NOROUTES\" ] || ip route replace $address dev $interface" [ -n "$persistent" ] && haveroute=yes fi ensure_and_save_command arp -i $external -Ds $address $external pub echo $address $interface $external $haveroute >> /var/lib/shorewall/proxyarp fi progress_message " Host $address connected to $interface added to ARP on $external" } if [ $COMMAND != check ]; then > /var/lib/shorewall/proxyarp save_progress_message "Restoring Proxy ARP..." fi while read address interface external haveroute persistent; do expandv address interface external haveroute persistent list_search $interface $setlist || setlist="$setlist $interface" list_search $external $resetlist || list_search $external $setlist || resetlist="$resetlist $external" setup_one_proxy_arp done < $TMP_DIR/proxyarp if [ $COMMAND != check ]; then for interface in $resetlist; do list_search $interface $setlist || \ run_and_save_command "echo 0 > /proc/sys/net/ipv4/conf/$interface/proxy_arp" done for interface in $setlist; do run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp" done interfaces=$(find_interfaces_by_option proxyarp) for interface in $interfaces; do if echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp 2> /dev/null; then progress_message " Enabled proxy ARP on $interface" save_command "echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp" else error_message "WARNING: Unable to enable proxy ARP on $interface" fi done fi } # # Set up MAC Verification # setup_mac_lists() { local interface local mac local addresses local address local chain local chain1 local macpart local blob local hosts local ipsec local policy= # # Generate the list of interfaces having MAC verification # maclist_interfaces= for hosts in $maclist_hosts; do hosts=${hosts#*^} interface=${hosts%%:*} if ! list_search $interface $maclist_interfaces; then\ if [ -z "$maclist_interfaces" ]; then maclist_interfaces=$interface else maclist_interfaces="$maclist_interfaces $interface" fi fi done progress_message "Setting up MAC Verification on $maclist_interfaces..." # # Create chains. # for interface in $maclist_interfaces; do chain=$(mac_chain $interface) createchain $chain no if [ -n "$MACLIST_TTL" ]; then chain1=$(macrecent_target $interface) createchain $chain1 no run_iptables -A $chain -m recent --rcheck --seconds $MACLIST_TTL --name $chain -j RETURN run_iptables -A $chain -j $chain1 run_iptables -A $chain -m recent --update --name $chain -j RETURN run_iptables -A $chain -m recent --set --name $chain fi done # # Process the maclist file producing the verification rules # while read interface mac addresses; do expandv interface mac addresses physdev_part= if [ -n "$BRIDGING" ]; then case $interface in *:*) physdev_part="-m physdev --physdev-in ${interface#*:}" interface=${interface%:*} ;; esac fi [ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface) if ! havechain $chain ; then fatal_error "No hosts on $interface have the maclist option specified" fi macpart=$(mac_match $mac) if [ -z "$addresses" ]; then run_iptables -A $chain $macpart $physdev_part -j RETURN else for address in $(separate_list $addresses) ; do run_iptables2 -A $chain $macpart -s $address $physdev_part -j RETURN done fi done < $TMP_DIR/maclist # # Must take care of our own broadcasts and multicasts then terminate the verification # chains # for interface in $maclist_interfaces; do [ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface) blob=$(ip link show $interface 2> /dev/null) [ -z "$blob" ] && \ fatal_error "Interface $interface must be up before Shorewall can start" ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet //; s/brd //; s/scope.*//;' | while read address broadcast; do address=${address%/*} if [ -n "$broadcast" ]; then run_iptables -A $chain -s $address -d $broadcast -j RETURN fi run_iptables -A $chain -s $address -d 255.255.255.255 -j RETURN run_iptables -A $chain -s $address -d 224.0.0.0/4 -j RETURN done if [ -n "$MACLIST_LOG_LEVEL" ]; then log_rule_limit $MACLIST_LOG_LEVEL $chain $(mac_chain $interface) $MACLIST_DISPOSITION "$LOGLIMIT" "" -A fi run_iptables -A $chain -j $maclist_target done # # Generate jumps from the input and forward chains # for hosts in $maclist_hosts; do ipsec=${hosts%^*} hosts=${hosts#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${hosts%%:*} hosts=${hosts#*:} for chain in $(first_chains $interface) ; do run_iptables -A $chain $(match_source_hosts $hosts) -m state --state NEW \ $policy -j $(mac_chain $interface) done done } # # Set up SYN flood protection # setup_syn_flood_chain () # $1 = policy chain # $2 = synparams # $3 = loglevel { local chain=@$1 local limit=$2 local limit_burst= case $limit in *:*) limit_burst="--limit-burst ${limit#*:}" limit=${limit%:*} ;; esac run_iptables -N $chain run_iptables -A $chain -m limit --limit $limit $limit_burst -j RETURN [ -n "$3" ] && \ log_rule_limit $3 $chain $chain DROP "-m limit --limit 5/min --limit-burst 5" "" "" run_iptables -A $chain -j DROP } setup_syn_flood_chains() { for chain in $ALL_POLICY_CHAINS; do eval loglevel=\$${chain}_loglevel eval synparams=\$${chain}_synparams [ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams $loglevel done } # # Delete existing Proxy ARP # delete_proxy_arp() { if [ -f /var/lib/shorewall/proxyarp ]; then while read address interface external haveroute; do qt arp -i $external -d $address pub [ -z "${haveroute}${NOROUTES}" ] && qt ip route del $address dev $interface done < /var/lib/shorewall/proxyarp rm -f /var/lib/shorewall/proxyarp fi [ -d /var/lib/shorewall ] && touch /var/lib/shorewall/proxyarp for f in /proc/sys/net/ipv4/conf/*; do [ -f $f/proxy_arp ] && echo 0 > $f/proxy_arp done } # # Setup Static Network Address Translation (NAT) # setup_nat() { local external= interface= internal= allints= localnat= policyin= policyout= validate_one() #1 = Variable Name, $2 = Column name, $3 = value { case $3 in Yes|yes) ;; No|no) eval ${1}= ;; *) [ -n "$3" ] && \ fatal_error "Invalid value ($3) for $2 in entry \"$external $interface $internal $allints $localnat\"" ;; esac } do_one_nat() { local add_ip_aliases=$ADD_IP_ALIASES iface=${interface%:*} if [ -n "$add_ip_aliases" ]; then case $interface in *:) interface=${interface%:} add_ip_aliases= ;; *) [ -n "$RETAIN_ALIASES" ] || run_and_save_command qt ip addr del $external dev $iface ;; esac else interface=${interface%:} fi validate_one allints "ALL INTERFACES" $allints validate_one localnat "LOCAL" $localnat if [ $COMMAND != check ]; then if [ -n "$allints" ]; then addnatrule nat_in -d $external $policyin -j DNAT --to-destination $internal addnatrule nat_out -s $internal $policyout -j SNAT --to-source $external else addnatrule $(input_chain $iface) -d $external $policyin -j DNAT --to-destination $internal addnatrule $(output_chain $iface) -s $internal $policyout -j SNAT --to-source $external fi [ -n "$localnat" ] && \ run_iptables2 -t nat -A OUTPUT -d $external $policyout -j DNAT --to-destination $internal fi if [ -n "$add_ip_aliases" ]; then list_search $external $ALIASES_TO_ADD || \ ALIASES_TO_ADD="$ALIASES_TO_ADD $external $interface" fi } # # At this point, we're just interested in the network translation # [ $COMMAND = check ] || > /var/lib/shorewall/nat if [ -n "$POLICY_MATCH" ]; then policyin="-m policy --pol none --dir in" policyout="-m policy --pol none --dir out" fi [ -n "$RETAIN_ALIASES" -o $COMMAND = check ] || save_progress_message "Restoring one-to-one NAT..." while read external interface internal allints localnat; do expandv external interface internal allints localnat do_one_nat progress_message " Host $internal NAT $external on $interface" done < $TMP_DIR/nat } # # Delete existing Static NAT # delete_nat() { run_iptables -t nat -F run_iptables -t nat -X if [ -f /var/lib/shorewall/nat ]; then while read external interface; do qt ip addr del $external dev $interface done < /var/lib/shorewall/nat rm -f {/var/lib/shorewall}/nat fi [ -d /var/lib/shorewall ] && touch /var/lib/shorewall/nat } # # Setup Network Mapping (NETMAP) # setup_netmap() { while read type net1 interface net2 ; do expandv type net1 interface net2 list_search $interface $ALL_INTERFACES || \ fatal_error "Unknown interface $interface in entry \"$type $net1 $interface $net2\"" case $type in DNAT) addnatrule $(input_chain $interface) -d $net1 -j NETMAP --to $net2 ;; SNAT) addnatrule $(output_chain $interface) -s $net1 -j NETMAP --to $net2 ;; *) fatal_error "Invalid type $type in entry \"$type $net1 $interface $net2\"" ;; esac progress_message " Network $net1 on $interface mapped to $net2 ($type)" done < $TMP_DIR/netmap } # # Setup ECN disabling rules # setup_ecn() # $1 = file name { local interfaces="" local hosts= local h strip_file ecn $1 echo "Processing $1..." while read interface host; do expandv interface host list_search $interface $ALL_INTERFACES || \ startup_error "Unknown interface $interface" list_search $interface $interfaces || \ interfaces="$interfaces $interface" [ "x$host" = "x-" ] && host= for h in $(separate_list ${host:-0.0.0.0/0}); do hosts="$hosts $interface:$h" done done < $TMP_DIR/ecn if [ -n "$interfaces" ]; then progress_message "Setting up ECN control on${interfaces}..." for interface in $interfaces; do chain=$(ecn_chain $interface) if mangle_chain_exists $chain; then flushmangle $chain else run_iptables -t mangle -N $chain run_iptables -t mangle -A POSTROUTING -p tcp -o $interface -j $chain run_iptables -t mangle -A OUTPUT -p tcp -o $interface -j $chain fi done for host in $hosts; do interface=${host%:*} h=${host#*:} run_iptables -t mangle -A $(ecn_chain $interface) -p tcp $(dest_ip_range $h) -j ECN --ecn-tcp-remove progress_message " ECN Disabled to $h through $interface" done fi } # # Set up an exclusion chain # build_exclusion_chain() # $1 = variable to store chain name into $2 = table, $3 = SOURCE exclusion list, $4 = DESTINATION exclusion list { local c=excl_${EXCLUSION_SEQ} net EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 )) run_iptables -t $2 -N $c for net in $(separate_list $3); do run_iptables -t $2 -A $c $(source_ip_range $net) -j RETURN done for net in $(separate_list $4); do run_iptables -t $2 -A $c $(dest_ip_range $net) -j RETURN done case $2 in filter) eval exists_${c}=Yes ;; nat) eval exists_nat_${c}=Yes ;; esac eval $1=$c } # # Process a TC Rule - $MARKING_CHAIN is assumed to contain the name of the # default marking chain # process_tc_rule() { chain=$MARKING_CHAIN target="MARK --set-mark" marktest= verify_designator() { [ "$chain" = tcout ] && \ fatal_error "Chain designator not allowed when source is \$FW; rule \"$rule\"" chain=$1 mark="${mark%:*}" } add_a_tc_rule() { r= if [ "x$source" != "x-" ]; then case $source in $FW:*) [ $chain = tcpost ] || chain=tcout r="$(source_ip_range ${source#*:}) " ;; *.*.*|+*|!+*) r="$(source_ip_range $source) " ;; ~*) r="$(mac_match $source) " ;; $FW) [ $chain = tcpost ] || chain=tcout ;; *) verify_interface $source || fatal_error "Unknown interface $source in rule \"$rule\"" r="$(match_source_dev) $source " ;; esac fi if [ "x${user:--}" != "x-" ]; then [ "$chain" != tcout ] && \ fatal_error "Invalid use of a user/group: rule \"$rule\"" r="$r-m owner" case "$user" in *+*) r="$r --cmd-owner ${user#*+} " user=${user%+*} ;; esac case "$user" in *:*) temp="${user%:*}" [ -n "$temp" ] && r="$r --uid-owner $temp " temp="${user#*:}" [ -n "$temp" ] && r="$r --gid-owner $temp " ;; *) [ -n "$user" ] && r="$r --uid-owner $user " ;; esac fi [ -n "$marktest" ] && r="${r}-m ${marktest}--mark $testval " if [ "x$dest" != "x-" ]; then case $dest in *.*.*|+*|!+*) r="${r}$(dest_ip_range $dest) " ;; *) [ "$chain" = tcpre ] && fatal_error "Destination interface is not allowed in the PREROUTING chain" verify_interface $dest || fatal_error "Unknown interface $dest in rule \"$rule\"" r="${r}$(match_dest_dev $dest) " ;; esac fi multiport= case $proto in ipp2p|IPP2P) [ "x$port" = "x-" ] && port="ipp2p" r="${r}-p tcp -m ipp2p --${port} " ;; icmp|ICMP|1) r="${r}-p icmp " [ "x$port" = "x-" ] || r="${r}--icmp-type $port" ;; *) [ "x$proto" = "x-" ] && proto=all [ "x$proto" = "x" ] && proto=all [ "$proto" = "all" ] || r="${r}-p $proto " [ "x$port" = "x-" ] || r="${r}--dport $port " ;; esac [ "x$sport" = "x-" ] || r="${r}--sport $sport " if [ -n "${excludesources}${excludedests}" ]; then build_exclusion_chain chain1 mangle "$excludesources" "$excludedests" run_iptables2 -t mangle -A $chain $r -j $chain1 run_iptables -t mangle -A $chain1 -j $target $mark else run_iptables2 -t mangle -A $chain $r -j $target $mark fi } if [ "$mark" != "${mark%:*}" ]; then case "${mark#*:}" in p|P) verify_designator tcpre ;; cp|CP) verify_designator tcpre target="CONNMARK --set-mark" ;; f|F) verify_designator tcfor ;; cf|CF) verify_designator tcfor target="CONNMARK --set-mark" ;; c|C) target="CONNMARK --set-mark" mark=${mark%:*} ;; *) chain=tcpost target="CLASSIFY --set-class" ;; esac fi case $mark in SAVE) target="CONNMARK --save-mark --mask 255" mark= ;; SAVE/*) target="CONNMARK --save-mark --mask" mark=${mark#*/} verify_mark $mark ;; RESTORE) target="CONNMARK --restore-mark --mask 255" mark= ;; RESTORE/*) target="CONNMARK --restore-mark --mask" mark=${mark#*/} verify_mark $mark ;; CONTINUE) target=RETURN mark= ;; *) if [ "$chain" != tcpost ]; then verify_mark $mark fi ;; esac case $testval in -) ;; !*:C) marktest="connmark ! " testval=${testval%:*} testval=${testval#!} ;; *:C) marktest="connmark " testval=${testval%:*} ;; !*) marktest="mark ! " testval=${testval#!} ;; *) [ -n "$testval" ] && marktest="mark " ;; esac if [ -n "$marktest" ] ; then case $testval in */*) verify_mark ${testval%/*} verify_mark ${testval#*/} ;; *) verify_mark $testval testval=$testval/255 ;; esac fi excludesources= case ${sources:=-} in *!*!*) fatal_error "Invalid SOURCE in rule \"$rule\"" ;; !*) if [ $(list_count $sourcess) -gt 1 ]; then excludesources=${sources#!} sources=- fi ;; *!*) excludesources=${sources#*!} sources=${sources%!*} ;; esac excludedests= case ${dests:=-} in *!*!*) fatal_error "Invalid DEST in rule \"$rule\"" ;; !*) if [ $(list_count $dests) -gt 1 ]; then excludedests=${dests#*!} dests=- fi ;; *!*) excludedests=${dests#*!} dests=${dests%!*} ;; esac for source in $(separate_list $sources); do for dest in $(separate_list $dests); do for port in $(separate_list ${ports:=-}); do for sport in $(separate_list ${sports:=-}); do add_a_tc_rule done done done done progress_message " TC Rule \"$rule\" added" } # # Setup queuing and classes # setup_tc1() { # # Create the TC mangle chains # run_iptables -t mangle -N tcpre run_iptables -t mangle -N tcfor run_iptables -t mangle -N tcout run_iptables -t mangle -N tcpost # # Process the TC Rules File # strip_file tcrules while read mark sources dests proto ports sports user testval; do expandv mark sources dests proto ports sports user testval rule=$(echo "$mark $sources $dests $proto $ports $sports $user $testval") process_tc_rule done < $TMP_DIR/tcrules # # Link to the TC mangle chains from the main chains # if [ -n "$ROUTEMARK_INTERFACES" ]; then # # Route marks are restored in PREROUTING/OUTPUT prior to these rules. We only send # packets that are not part of a marked connection to the 'tcpre/tcout' chains # run_iptables -t mangle -A PREROUTING -m mark --mark 0 -j tcpre run_iptables -t mangle -A OUTPUT -m mark --mark 0 -j tcout else run_iptables -t mangle -A PREROUTING -j tcpre run_iptables -t mangle -A OUTPUT -j tcout fi run_iptables -t mangle -A FORWARD -j tcfor run_iptables -t mangle -A POSTROUTING -j tcpost f=$(find_file tcstart) if [ -f $f ]; then run_user_exit tcstart f=$(find_file tcstart) # In case the script used this variable if [ $f != /usr/share/shorewall/tcstart ]; then save_progress_message "Restoring Traffic Control..." save_command . $f fi fi } setup_tc() { echo "Setting up Traffic Control Rules..." setup_tc1 } # # Clear Traffic Shaping # delete_tc() { clear_one_tc() { run_and_save_command "tc qdisc del dev $1 root 2> /dev/null" run_and_save_command "tc qdisc del dev $1 ingress 2> /dev/null" } save_progress_message "Clearing Traffic Control/QOS" run_user_exit tcclear run_ip link list | \ while read inx interface details; do case $inx in [0-9]*) clear_one_tc ${interface%:} ;; *) ;; esac done } delete_tc1() { clear_one_tc() { tc qdisc del dev $1 root 2> /dev/null tc qdisc del dev $1 ingress 2> /dev/null } run_user_exit tcclear run_ip link list | \ while read inx interface details; do case $inx in [0-9]*) clear_one_tc ${interface%:} ;; *) ;; esac done } # # Process a record from the accounting file # process_accounting_rule() { rule= rule2= jumpchain= user1= accounting_error() { error_message "WARNING: Invalid Accounting rule" $action $chain $source $dest $proto $port $sport $user } accounting_interface_error() { error_message "WARNING: Unknown interface $1 in " $action $chain $source $dest $proto $port $sport $user } accounting_interface_verify() { verify_interface $1 || accounting_interface_error $1 } jump_to_chain() { if ! havechain $jumpchain; then if ! createchain2 $jumpchain No; then accounting_error return 2 fi fi rule="$rule -j $jumpchain" } case $source in *:*) accounting_interface_verify ${source%:*} rule="$(source_ip_range ${source#*:}) $(match_source_dev ${source%:*})" ;; *.*.*.*|+*|!+*) rule="$(source_ip_range $source)" ;; -|all|any) ;; *) if [ -n "$source" ]; then accounting_interface_verify $source rule="$(match_source_dev $source)" fi ;; esac [ -n "$dest" ] && case $dest in *:*) accounting_interface_verify ${dest%:*} rule="$rule $(dest_ip_range ${dest#*:}) $(match_dest_dev ${dest%:*})" ;; *.*.*.*|+*|!*) rule="$rule $(dest_ip_range $dest)" ;; -|all|any) ;; *) accounting_interface_verify $dest rule="$rule $(match_dest_dev $dest)" ;; esac [ -n "$proto" ] && case $proto in -|any|all) ;; ipp2p) rule="$rule -p tcp -m ipp2p --${port:-ipp2p}" port= ;; *) rule="$rule -p $proto" ;; esac multiport= [ -n "$port" ] && case $port in -|any|all) ;; *) if [ -n "$MULTIPORT" ]; then rule="$rule -m multiport --dports $port" multiport=Yes else rule="$rule --dport $port" fi ;; esac [ -n "$sport" ] && case $sport in -|any|all) ;; *) if [ -n "$MULTIPORT" ]; then [ -n "$multiport" ] && rule="$rule --sports $sport" || rule="$rule -m multiport --sports $sport" else rule="$rule --sport $sport" fi ;; esac [ -n "$user" ] && case $user in -|any|all) ;; *) [ "$chain" != OUTPUT ] && \ fatal_error "Invalid use of a user/group: chain is not OUTPUT but $chain" rule="$rule -m owner" user1="$user" case "$user" in !*+*) if [ -n "${user#*+}" ]; then rule="$rule ! --cmd-owner ${user#*+} " fi user1=${user%+*} ;; *+*) if [ -n "${user#*+}" ]; then rule="$rule --cmd-owner ${user#*+} " fi user1=${user%+*} ;; esac case "$user1" in !*:*) if [ "$user1" != "!:" ]; then temp="${user1#!}" temp="${temp%:*}" [ -n "$temp" ] && rule="$rule ! --uid-owner $temp " temp="${user1#*:}" [ -n "$temp" ] && rule="$rule ! --gid-owner $temp " fi ;; *:*) if [ "$user1" != ":" ]; then temp="${user1%:*}" [ -n "$temp" ] && rule="$rule --uid-owner $temp " temp="${user1#*:}" [ -n "$temp" ] && rule="$rule --gid-owner $temp " fi ;; !*) [ "$user1" != "!" ] && rule="$rule ! --uid-owner ${user1#!} " ;; *) [ -n "$user1" ] && rule="$rule --uid-owner $user1 " ;; esac ;; esac case $action in COUNT) ;; DONE) rule="$rule -j RETURN" ;; *:COUNT) rule2="$rule" jumpchain=${action%:*} jump_to_chain || return ;; JUMP:*) jumpchain=${action#*:} jump_to_chain || return ;; *) jumpchain=$action jump_to_chain || return ;; esac [ "x${chain:=accounting}" = "x-" ] && chain=accounting ensurechain1 $chain if $IPTABLES -A $chain $(fix_bang $rule) ; then [ -n "$rule2" ] && run_iptables2 -A $jumpchain $rule2 progress_message " Accounting rule" $action $chain $source $dest $proto $port $sport $user Added else accounting_error fi } # # Set up Accounting # setup_accounting() # $1 = Name of accounting file { echo "Setting up Accounting..." strip_file accounting $1 while read action chain source dest proto port sport user ; do expandv action chain source dest proto port sport user process_accounting_rule done < $TMP_DIR/accounting if havechain accounting; then for chain in INPUT FORWARD OUTPUT; do run_iptables -I $chain -j accounting done fi } # # Check the configuration # check_config() { disclaimer() { echo echo "Notice: The 'check' command is provided to catch" echo " obvious errors in a Shorewall configuration." echo " It is not designed to catch all possible errors" echo " so please don't submit problem reports about" echo " error conditions that 'check' doesn't find" echo } report_capabilities echo "Verifying Configuration..." verify_os_version if [ -n "$BRIDGING" ]; then [ -n "$PHYSDEV_MATCH" ] || startup_error "BRIDGING=Yes requires Physdev Match support in your Kernel and iptables" fi [ "$MACLIST_TTL" = "0" ] && MACLIST_TTL= if [ -n "$MACLIST_TTL" -a -z "$RECENT_MATCH" ]; then startup_error "MACLIST_TTL requires the Recent Match capability which is not present in your Kernel and/or iptables" fi echo "Determining Zones..." determine_zones display_list "Zones:" $ZONES setup_ipsec echo "Validating interfaces file..." validate_interfaces_file echo "Validating hosts file..." validate_hosts_file echo "Determining Hosts in Zones..." determine_interfaces determine_hosts echo "Validating policy file..." validate_policy setup_providers $(find_file providers) validate_blacklist echo "Validating Proxy ARP" strip_file proxyarp setup_proxy_arp echo "Validating NAT..." strip_file nat setup_nat echo "Pre-validating Actions..." process_actions1 echo "Validating rules file..." rules=$(find_file rules) strip_file rules $rules process_rules echo "Validating Actions..." process_actions2 process_actions3 masq=$(find_file masq) [ -f $masq ] && setup_masq $masq rm -rf $TMP_DIR [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE echo "Configuration Validated" disclaimer } # # Refresh queuing and classes # refresh_tc() { echo "Refreshing Traffic Control Rules..." [ -n "$CLEAR_TC" ] && delete_tc1 [ -n "$MARK_IN_FORWARD_CHAIN" ] && chain=tcfor || chain=tcpre if mangle_chain_exists $chain; then # # Flush the TC mangle chains # run_iptables -t mangle -F $chain run_iptables -t mangle -F tcout # # Process the TC Rules File # strip_file tcrules while read mark sources dests proto ports sports; do expandv mark sources dests proto ports sports rule=$(echo "$mark $sources $dests $proto $ports $sports") process_tc_rule done < $TMP_DIR/tcrules else setup_tc1 fi f=$(find_file tcstart) if [ -x $f ]; then export CONFIG_PATH SHOREWALL_DIR eval $f fi } # # Add one Filter Rule from an action -- Helper function for the action file processor # # The caller has established the following variables: # COMMAND = current command. If 'check', we're executing a 'check' # which only goes through the motions. # client = SOURCE IP or MAC # server = DESTINATION IP or interface # protocol = Protocol # address = Original Destination Address # port = Destination Port # cport = Source Port # multioption = String to invoke multiport match if appropriate # action = The chain for this rule # ratelimit = Optional rate limiting clause # userandgroup = owner match clause # logtag = Log tag # add_an_action() { local chain1 do_ports() { if [ -n "$port" ]; then dports="--dport" if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then multiport="$multioption" dports="--dports" fi dports="$dports $port" fi if [ -n "$cport" ]; then sports="--sport" if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then multiport="$multioption" sports="--sports" fi sports="$sports $cport" fi } interface_error() { fatal_error "Unknown interface $1 in rule: \"$rule\"" } action_interface_verify() { verify_interface $1 || interface_error $1 } handle_exclusion() { build_exclusion_chain chain1 filter "$excludesource" "$excludedest" run_iptables -A $chain $(fix_bang $cli $proto $sports $multiport $dports) $user -j $chain1 cli= proto= sports= multiport= dports= user= } # Set source variables. The 'cli' variable will hold the client match predicate(s). cli= case "$client" in -) ;; *:*) action_interface_verify ${client%:*} cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})" ;; *.*.*|+*|!+*) cli="$(source_ip_range $client)" ;; ~*) cli=$(mac_match $client) ;; *) if [ -n "$client" ]; then action_interface_verify $client cli="$(match_source_dev $client)" fi ;; esac # Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s). dest_interface= serv= case "$server" in -) ;; *.*.*|+*|!+*) serv=$server ;; ~*) fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address" ;; *) if [ -n "$server" ]; then action_interface_verify $server dest_interface="$(match_dest_dev $server)" fi ;; esac # Setup protocol and port variables sports= dports= proto=$protocol servport=$serverport multiport= chain1=$chain user="$userandgroup" [ x$port = x- ] && port= [ x$cport = x- ] && cport= case $proto in tcp|TCP|6) do_ports [ "$target" = QUEUE ] && proto="$proto --syn" ;; udp|UDP|17) do_ports ;; icmp|ICMP|1) [ -n "$port" ] && dports="--icmp-type $port" ;; ipp2p) dports="-m ipp2p --${port:-ipp2p}" port= proto=tcp do_ports ;; *) [ -n "$port" ] && \ fatal_error "Port number not allowed with protocol \"$proto\"; rule: \"$rule\"" ;; esac proto="${proto:+-p $proto}" # Some misc. setup case "$logtarget" in LOG) [ -z "$loglevel" ] && fatal_error "LOG requires log level" ;; esac if [ $COMMAND != check ]; then if [ -n "${excludesource}${excludedest}" ]; then handle_exclusion fi if [ -n "${serv}" ]; then for serv1 in $(separate_list $serv); do for srv in $(firewall_ip_range $serv1); do if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \ $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) fi run_iptables2 -A $chain1 $proto $multiport $cli $sports \ $(dest_ip_range $srv) $dports $ratelimit $user -j $target done done else if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \ $(fix_bang $proto $sports $multiport $cli $dest_interface $dports) fi run_iptables2 -A $chain1 $proto $multiport $cli $dest_interface $sports \ $dports $ratelimit $user -j $target fi fi } # # Process a record from an action file for the 'start', 'restart' or 'check' commands # process_action() # $1 = chain (Chain to add the rules to) # $2 = action (The action name for logging purposes) # $3 = target (The (possibly modified) contents of the TARGET column) # $4 = clients # $5 = servers # $6 = protocol # $7 = ports # $8 = cports # $9 = ratelimit # $10 = userspec { local chain="$1" local action="$2" local target="$3" local clients="$4" local servers="$5" local protocol="$6" local ports="$7" local cports="$8" local ratelimit="$9" local userspec="${10}" local userandgroup= local logtag= if [ -n "$ratelimit" ]; then case $ratelimit in -) ratelimit= ;; *:*) ratelimit="-m limit --limit ${ratelimit%:*} --limit-burst ${ratelimit#*:}" ;; *) ratelimit="-m limit --limit $ratelimit" ;; esac fi [ "x$userspec" = "x-" ] && userspec= if [ -n "$userspec" ]; then userandgroup="-m owner" case "$userspec" in !*+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; *+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; esac case "$userspec" in !*:*) if [ "$userspec" != "!:" ]; then temp="${userspec#!}" temp="${temp%:*}" [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" fi ;; *:*) if [ "$userspec" != ":" ]; then temp="${userspec%:*}" [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" fi ;; !*) [ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}" ;; *) [ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec" ;; esac [ "$userandgroup" = "-m owner" ] && userandgroup= fi # Isolate log level if [ "$target" = "${target%:*}" ]; then loglevel= else loglevel="${target#*:}" target="${target%%:*}" expandv loglevel if [ "$loglevel" != "${loglevel%:*}" ]; then logtag="${loglevel#*:}" loglevel="${loglevel%:*}" expandv logtag fi case $loglevel in none*) loglevel= [ $target = LOG ] && return ;; esac loglevel=${loglevel%\!} fi logtarget="$target" case $target in REJECT) target=reject ;; CONTINUE) target=RETURN ;; *) ;; esac excludesource= case ${clients:=-} in *!*!*) fatal_error "Invalid SOURCE in rule \"$rule\"" ;; !*) if [ $(list_count $clients) -gt 1 ]; then excludesource=${clients#!} clients= fi ;; *!*) excludesource=${clients#*!} clients=${clients%!*} ;; esac excludedest= case ${servers:=-} in *!*!*) fatal_error "Invalid DEST in rule \"$rule\"" ;; !*) if [ $(list_count $servers) -gt 1 ]; then excludedest=${servers#*!} servers= fi ;; *!*) excludedest=${servers#*!} servers=${servers%!*} ;; esac # Generate Netfilter rule(s) [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all} if [ -n "$XMULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] then # # Extended MULTIPORT is enabled, and less than # 16 ports are listed (port ranges count as two ports) - use multiport match. # multioption="-m multiport" for client in $(separate_list $clients); do for server in $(separate_list $servers); do # # add_an_action() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_an_action done done elif [ -n "$MULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ "$ports" = "${ports%:*}" -a \ "$cports" = "${cports%:*}" -a \ $(list_count $ports) -le 15 -a \ $(list_count $cports) -le 15 ] then # # MULTIPORT is enabled, there are no port ranges in the rule and less than # 16 ports are listed - use multiport match. # multioption="-m multiport" for client in $(separate_list $clients); do for server in $(separate_list $servers); do # # add_an_action() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_an_action done done else # # MULTIPORT is disabled or the rule isn't compatible with multiport match # multioption= for client in $(separate_list $clients); do for server in $(separate_list $servers); do for port in $(separate_list ${ports:=-}); do for cport in $(separate_list ${cports:=-}); do add_an_action done done done done fi # # Report Result # if [ $COMMAND = check ]; then progress_message " Rule \"$rule\" checked." else progress_message " Rule \"$rule\" added." fi } # # Create and record a log action chain -- Log action chains have names # that are formed from the action name by prepending a "%" and appending # a 1- or 2-digit sequence number. In the functions that follow, # the CHAIN, LEVEL and TAG variable serves as arguments to the user's # exit. We call the exit corresponding to the name of the action but we # set CHAIN to the name of the iptables chain where rules are to be added. # Similarly, LEVEL and TAG contain the log level and log tag respectively. # # For each , we maintain two variables: # # _actchain - The action chain number. # _chains - List of ( level[:tag] , chainname ) pairs # # The maximum length of a chain name is 30 characters -- since the log # action chain name is 2-3 characters longer than the base chain name, # this function truncates the original chain name where necessary before # it adds the leading "%" and trailing sequence number. createlogactionchain() # $1 = Action Name, $2 = Log Level [: Log Tag ] { local actchain= action=$1 level=$2 eval actchain=\${${action}_actchain} case ${#action} in 29|30) CHAIN=$(echo $action | truncate 28) # %...n makes 30 ;; *) CHAIN=${action} ;; esac [ "$COMMAND" != check ] && \ while havechain %${CHAIN}${actchain}; do actchain=$(($actchain + 1)) [ $actchain -eq 10 -a ${#CHAIN} -eq 28 ] && CHAIN=$(echo $CHAIN | truncate 27) # %...nn makes 30 done CHAIN=%${CHAIN}${actchain} eval ${action}_actchain=$(($actchain + 1)) if [ $COMMAND != check ]; then createchain $CHAIN No LEVEL=${level%:*} if [ "$LEVEL" != "$level" ]; then TAG=${level#*:} else TAG= fi [ none = "${LEVEL%\!}" ] && LEVEL= run_user_exit $1 fi eval ${action}_chains=\"\$${action}_chains $level $CHAIN\" } # # Create an action chain and run it's associated user exit # createactionchain() # $1 = Action, including log level and tag if any { case $1 in *::*) fatal_error "Invalid ACTION $1" ;; *:*:*) set -- $(split $1) createlogactionchain $1 $2:$3 ;; *:*) set -- $(split $1) createlogactionchain $1 $2 ;; *) CHAIN=$1 if [ $COMMAND != check ]; then LEVEL= TAG= createchain $CHAIN no run_user_exit $CHAIN fi ;; esac } # # Find the chain that handles the passed action. If the chain cannot be found, # a fatal error is generated and the function does not return. # find_logactionchain() # $1 = Action, including log level and tag if any { local fullaction=$1 action=${1%%:*} level= chains= case $fullaction in *:*) level=${fullaction#*:} ;; *) if [ $COMMAND != check ]; then havechain $action || fatal_error "Fatal error in find_logactionchain" fi echo $action return ;; esac eval chains="\$${action}_chains" set -- $chains while [ $# -gt 0 ]; do [ "$1" = "$level" ] && { echo $2 ; return ; } shift;shift done fatal_error "Fatal error in find_logactionchain" } # # This function determines the logging for a subordinate action or a rule within a subordinate action # merge_levels() # $1=level at which superior action is called, $2=level at which the subordinate rule is called { local superior=$1 subordinate=$2 set -- $(split $1) case $superior in *:*:*) case $2 in 'none!') echo ${subordinate%%:*}:'none!':$3 return ;; *'!') echo ${subordinate%%:*}:$2:$3 return ;; *) case $subordinate in *:*:*) echo $subordinate return ;; *:*) echo $subordinate:$3 return ;; *) echo ${subordinate%%:*}:$2:$3 return ;; esac ;; esac ;; *:*) case $2 in 'none!') echo ${subordinate%%:*}:'none!' return ;; *'!') echo ${subordinate%%:*}:$2 return ;; *) case $subordinate in *:*) echo $subordinate return ;; *) echo ${subordinate%%:*}:$2 return ;; esac ;; esac ;; *) echo $subordinate ;; esac } # This function substitutes the second argument for the first part of the first argument up to the first colon (":") # # Example: # # substitute_action DNAT PARAM:info:FTP # # produces "DNAT:info:FTP" # substitute_action() # $1 = parameter, $2 = action { local logpart=${2#*:} case $2 in *:*) echo $1:${logpart%/} ;; *) echo $1 ;; esac } # # This function maps old action names into their new macro equivalents # map_old_action() # $1 = Potential Old Action { local macro= aktion if [ -n "$MAPOLDACTIONS" ]; then case $1 in */*) echo $1 return ;; *) if [ -f $(find_file $1) ]; then echo $1 return fi case $1 in Allow*) macro=${1#*w} aktion=ACCEPT ;; Drop*) macro=${1#*p} aktion=DROP ;; Reject*) macro=${1#*t} aktion=REJECT ;; *) echo $1 return ;; esac esac if [ -f $(find_file macro.$macro) ]; then echo $macro/$aktion fi fi echo $1 } # # The next three functions implement the three phases of action processing. # # The first phase (process_actions1) occurs before the rules file is processed. /usr/share/shorewall/actions.std # and /etc/shorewall/actions are scanned (in that order) and for each action: # # a) The related action definition file is located and scanned. # b) Forward and unresolved action references are trapped as errors. # c) A dependency graph is created. For each , the variable 'requiredby_' lists the # action[:level[:tag]] of each action invoked by . # d) All actions are listed in the global variable ACTIONS. # e) Common actions are recorded (in variables of the name _common) and are added to the global # USEDACTIONS # # As the rules file is scanned, each action[:level[:tag]] is merged onto the USEDACTIONS list. When an # is merged onto this list, its action chain is created. Where logging is specified, a chain with the name # %n is used where the name is truncated on the right where necessary to ensure that the total # length of the chain name does not exceed 30 characters. # # The second phase (process_actions2) occurs after the rules file is scanned. The transitive closure of # USEDACTIONS is generated; again, as new actions are merged onto this list, their action chains are created. # # The final phase (process_actions3) is to traverse the USEDACTIONS list populating each chain appropriately # by reading the action definition files and creating rules. Note that a given action definition file is # processed once for each unique [:level[:tag]] applied to an invocation of the action. # process_actions1() { ACTIONS="dropBcast allowBcast dropNotSyn rejNotSyn dropInvalid allowInvalid allowinUPnP allowoutUPnP forwardUPnP" USEDACTIONS= strip_file actions strip_file actions.std /usr/share/shorewall/actions.std for inputfile in actions.std actions; do while read xaction rest; do [ "x$rest" = x ] || fatal_error "Invalid Action: $xaction $rest" case $xaction in *:*) temp=${xaction#*:} [ ${#temp} -le 30 ] || fatal_error "Action Name Longer than 30 Characters: $temp" xaction=${xaction%:*} case $temp in ACCEPT|REJECT|DROP|QUEUE) eval ${temp}_common=$xaction if [ -n "$xaction" ] && ! list_search $xaction $USEDACTIONS; then USEDACTIONS="$USEDACTIONS $xaction" fi ;; *) startup_error "Common Actions are only allowed for ACCEPT, DROP, REJECT and QUEUE" ;; esac esac [ -z "$xaction" ] && continue [ "$xaction" = "$(chain_base $xaction)" ] || startup_error "Invalid Action Name: $xaction" if ! list_search $xaction $ACTIONS; then f=action.$xaction fn=$(find_file $f) eval requiredby_${action}= if [ -f $fn ]; then echo " Pre-processing $fn..." strip_file $f $fn while read xtarget xclients xservers xprotocol xports xcports xratelimit $xuserspec; do expandv xtarget temp="${xtarget%%:*}" case "$temp" in ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) ;; *) if list_search $temp $ACTIONS; then eval requiredby=\"\$requiredby_${xaction}\" list_search $xtarget $requiredby || eval requiredby_${xaction}=\"$requiredby $xtarget\" else temp=$(map_old_action $temp) case $temp in */*) param=${temp#*/} case $param in ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) ;; *) rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" startup_error "Invalid Macro Parameter in rule \"$rule\"" ;; esac temp=${temp%%/*} ;; esac f1=macro.${temp} fn=$(find_file $f1) if [ ! -f $TMP_DIR/$f1 ]; then # # We must only verify macros once to ensure that they don't invoke any non-standard actions # if [ -f $fn ]; then strip_file $f1 $fn progress_message " ..Expanding Macro $fn..." while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do expandv mtarget temp="${mtarget%%:*}" case "$temp" in ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE|PARAM) ;; *) rule="$mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec" startup_error "Invalid TARGET in rule \"$rule\"" esac done < $TMP_DIR/$f1 progress_message " ..End Macro" else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" startup_error "Invalid TARGET in rule \"$rule\"" fi fi fi ;; esac done < $TMP_DIR/$f else startup_error "Missing Action File: $f" fi ACTIONS="$ACTIONS $xaction" fi done < $TMP_DIR/$inputfile done } process_actions2() { local interfaces="$(find_interfaces_by_option upnp)" if [ -n "$interfaces" ]; then if ! list_search forwardUPnP $USEDACTIONS; then error_message "WARNING:Missing forwardUPnP rule (required by 'upnp' interface option on $interfaces)" USEDACTIONS="$USEDACTIONS forwardUPnP" fi fi progress_message " Generating Transitive Closure of Used-action List..." changed=Yes while [ -n "$changed" ]; do changed= for xaction in $USEDACTIONS; do eval required=\"\$requiredby_${xaction%%:*}\" for xaction1 in $required; do # # Generate the action that will be passed to process_action by merging the # logging specified when the action was invoked with the logging in the # invocation of the subordinate action (usually no logging) # xaction2=$(merge_levels $xaction $xaction1) if ! list_search $xaction2 $USEDACTIONS; then # # We haven't seen this one before -- create and record a chain to handle it # USEDACTIONS="$USEDACTIONS $xaction2" createactionchain $xaction2 changed=Yes fi done done done } process_actions3() { for xaction in $USEDACTIONS; do # # Find the chain associated with this action:level:tag # xchain=$(find_logactionchain $xaction) # # Split the action:level:tag # set -- $(split $xaction) xaction1=$1 xlevel=$2 xtag=$3 # # Handle Builtin actions # case $xaction1 in dropBcast) if [ "$COMMAND" != check ]; then if [ -n "$USEPKTTYPE" ]; then case $xlevel in none'!') ;; *) if [ -n "$xlevel" ]; then log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type broadcast log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type multicast fi ;; esac run_iptables -A dropBcast -m pkttype --pkt-type broadcast -j DROP run_iptables -A dropBcast -m pkttype --pkt-type multicast -j DROP else for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do case $xlevel in none*) ;; *) [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d $address ;; esac run_iptables -A $xchain -d $address -j DROP done fi fi ;; allowBcast) if [ "$COMMAND" != check ]; then if [ -n "$USEPKTTYPE" ]; then case $xlevel in none'!') ;; *) if [ -n "$xlevel" ]; then log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type broadcast log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type multicast fi ;; esac run_iptables -A allowBcast -m pkttype --pkt-type broadcast -j ACCEPT run_iptables -A allowBcast -m pkttype --pkt-type multicast -j ACCEPT else for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do case $xlevel in none*) ;; *) [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -d $address ;; esac run_iptables -A $xchain -d $address -j ACCEPT done fi fi ;; dropNotSyn) if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain dropNotSyn DROP "" "$xtag" -A -p tcp ! --syn run_iptables -A $xchain -p tcp ! --syn -j DROP fi ;; rejNotSyn) if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain rejNotSyn REJECT "" "$xtag" -A -p tcp ! --syn run_iptables -A $xchain -p tcp ! --syn -j REJECT --reject-with tcp-reset fi ;; dropInvalid) if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain dropInvalid DROP "" "$xtag" -A -m state --state INVALID run_iptables -A $xchain -m state --state INVALID -j DROP fi ;; allowInvalid) if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain allowInvalid ACCEPT "" "$xtag" -A -m state --state INVALID run_iptables -A $xchain -m state --state INVALID -j ACCEPT fi ;; forwardUPnP) ;; allowinUPnP) if [ "$COMMAND" != check ]; then if [ -n "$xlevel" ]; then log_rule_limit ${xlevel%\!} $xchain allowinUPnP ACCEPT "" "$xtag" -A -p udp --dport 1900 log_rule_limit ${xlevel%\!} $xchain allowinUPnP ACCEPT "" "$xtag" -A -p tcp --dport 49152 fi run_iptables -A $xchain -p udp --dport 1900 -j ACCEPT run_iptables -A $xchain -p tcp --dport 49152 -j ACCEPT fi ;; allowoutUPnP) if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain allowoutUPnP ACCEPT "" "$xtag" -A -m owner --owner-cmd upnpd run_iptables -A $xchain -m owner --cmd-owner upnpd -j ACCEPT fi ;; *) # # Not a builtin # f=action.$xaction1 echo "Processing $(find_file $f) for Chain $xchain..." while read xtarget xclients xservers xprotocol xports xcports xratelimit xuserspec; do expandv xtarget # # Generate the target:level:tag to pass to process_action() # xaction2=$(merge_levels $xaction $xtarget) is_macro= param= xtarget1=${xaction2%%:*} case $xtarget1 in ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) # # Builtin target -- Nothing to do # ;; *) if list_search $xtarget1 $ACTIONS ; then # # An Action -- Replace the target from the file # -- with the one generated above xtarget=$xaction2 # # And locate the chain for that action:level:tag # xaction2=$(find_logactionchain $xtarget) else is_macro=yes fi ;; esac expandv xclients xservers xprotocol xports xcports xratelimit xuserspec if [ -n "$is_macro" ]; then xtarget1=$(map_old_action $xtarget1) case $xtarget1 in */*) param=${xtarget1#*/} xtarget1=${xtarget1%%/*} ;; esac progress_message "..Expanding Macro $(find_file macro.$xtarget1)..." while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do expandv mtarget mclients mservers mprotocol mports mcports mratelimit muserspec mtarget=$(merge_levels $xaction2 $mtarget) case $mtarget in PARAM|PARAM:*) [ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation" ;; esac if [ -n "$mclients" ]; then case $mclients in -) mclients=${xclients} ;; *) mclients=${mclients}:${xclients} ;; esac else mclients=${xclients} fi if [ -n "$mservers" ]; then case $mservers in -) mservers=${xservers} ;; *) mservers=${mservers}:${xservers} ;; esac else mservers=${xserverss} fi [ -n "$xprotocol" ] && [ "x${xprotocol}" != x- ] && mprotocol=$xprotocol [ -n "$xports" ] && [ "x${xports}" != x- ] && mports=$xports [ -n "$xcports" ] && [ "x${xcports}" != x- ] && mcports=$xcports [ -n "$xratelimit" ] && [ "x${xratelimit}" != x- ] && mratelimit=$xratelimit [ -n "$xuserspec" ] && [ "x${xuserspec}" != x- ] && muserspec=$xuserspec rule="$mtarget ${mclients:=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${mratelimit:-} ${muserspec:=-}" process_action $xchain $xaction1 $mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec done < $TMP_DIR/macro.$xtarget1 progress_message "..End Macro" else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" process_action $xchain $xaction1 $xaction2 $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec fi done < $TMP_DIR/$f ;; esac done } # # Add a NAT rule - Helper function for the rules file processor # # The caller has established the following variables: # COMMAND = The current command -- if 'check', we just go through # the motions. # cli = Source IP, interface or MAC Specification # serv = Destination IP Specification # servport = Port the server is listening on # dest_interface = Destination Interface Specification # proto = Protocol Specification # addr = Original Destination Address # dports = Destination Port Specification. 'dports' may be changed # by this function # cport = Source Port Specification # multiport = String to invoke multiport match if appropriate # ratelimit = Optional rate limiting clause # userandgroup = -m owner match to limit the rule to a particular user and/or group # logtag = Log tag # excludesource = Source Exclusion List # add_nat_rule() { local chain local excludedests= # Be sure we can NAT if [ -z "$NAT_ENABLED" ]; then fatal_error "Rule \"$rule\" requires NAT which is disabled" fi # Parse SNAT address if any if [ "$addr" != "${addr%:*}" ]; then fatal_error "SNAT may no longer be specified in a DNAT rule; use /etc/shorewall/masq instead" fi # Set original destination address case $addr in all) addr= ;; detect) addr= if [ -n "$DETECT_DNAT_IPADDRS" -a "$source" != "$FW" ]; then eval interfaces=\$${source}_interfaces for interface in $interfaces; do addr=${addr:+$addr,}$(find_first_interface_address $interface) done fi ;; !*) if [ $(list_count $addr) -gt 1 ]; then excludedests="${addr#\!}" addr= fi ;; esac addr=${addr:-0.0.0.0/0} # Select target if [ "$logtarget" = SAME ]; then [ -n "$servport" ] && fatal_error "Port mapping not allowed in SAME rules" serv1= for srv in $(separate_list $serv); do serv1="$serv1 --to ${srv}" done target1="SAME $serv1" elif [ -n "$serv" ]; then servport="${servport:+:$servport}" serv1= for srv in $(separate_list $serv); do serv1="$serv1 --to-destination ${srv}${servport}" done target1="DNAT $serv1" else target1="REDIRECT --to-port $servport" fi if [ $source = $FW ]; then [ -n "$excludezones" ] && fatal_error "Invalid Source in rule \"$rule\"" fi # Generate nat table rules if [ $COMMAND != check ]; then if [ "$source" = "$FW" ]; then if [ -n "${excludesource}${excludedests}" ]; then build_exclusion_chain chain nat "$excludesource" $excludedests for adr in $(separate_list $addr); do run_iptables2 -t nat -A OUTPUT $cli $proto $userandgroup $multiport $sports $dports $(dest_ip_range $adr) -j $chain done if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat fi addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection else for adr in $(separate_list $addr); do if [ -n "$loglevel" ]; then log_rule_limit $loglevel OUTPUT OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat \ $(fix_bang $proto $cli $sports $userandgroup $(dest_ip_range $adr) $multiport $dports) fi run_iptables2 -t nat -A OUTPUT $ratelimit $proto $sports $userandgroup $(dest_ip_range $adr) $multiport $dports -j $target1 done fi else if [ -n "${excludesource}${excludedests}${excludezones}" ]; then build_exclusion_chain chain nat "$excludesource" $excludedests for adr in $(separate_list $addr); do addnatrule $(dnat_chain $source) $cli $proto $multiport $sports $dports $(dest_ip_range $adr) -j $chain done for z in $(separate_list $excludezones); do eval hosts=\$${z}_hosts for host in $hosts; do addnatrule $chain $(match_source_hosts ${host#*:}) -j RETURN done done if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $(dnat_chain $source) $logtarget "$ratelimit" "$logtag" -A -t nat fi addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection else chain=$(dnat_chain $source) for adr in $(separate_list $addr); do if [ -n "$loglevel" ]; then ensurenatchain $chain log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A -t nat \ $(fix_bang $proto $cli $sports $(dest_ip_range $adr) $multiport $dports) fi addnatrule $chain $proto $ratelimit $cli $sports \ -d $adr $multiport $dports -j $target1 done fi fi fi # Replace destination port by the new destination port if [ -n "$servport" ]; then if [ -z "$multiport" ]; then dports="--dport ${servport#*:}" else dports="--dports ${servport#*:}" fi fi [ "x$addr" = "x0.0.0.0/0" ] && addr= ratelimit= } # # Process a record from the rules file for the 'start', 'restart' or 'check' commands # process_rule() # $1 = target # $2 = clients # $3 = servers # $4 = protocol # $5 = ports # $6 = cports # $7 = address # $8 = ratelimit # $9 = userspec { local target="$1" local clients="$2" local servers="$3" local protocol="$4" local ports="$5" local cports="$6" local address="$7" local ratelimit="$8" local userspec="$9" local userandgroup= local logtag= local nonat= # # Add one Filter Rule # # The caller has established the following variables: # COMMAND = current command. If 'check', we're executing a 'check' # which only goes through the motions. # client = SOURCE IP or MAC # server = DESTINATION IP or interface # protocol = Protocol # address = Original Destination Address # port = Destination Port # cport = Source Port # multioption = String to invoke multiport match if appropriate # servport = Port the server listens on # chain = The canonical chain for this rule # logchain = The chain that should be mentioned in log messages # ratelimit = Optional rate limiting clause # userandgroup = -m owner clause # userspec = User name # logtag = Log tag # policy = Applicable Policy # add_a_rule() { local natrule= do_ports() { if [ -n "$port" ]; then dports="--dport" if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then multiport="$multioption" dports="--dports" fi dports="$dports $port" fi if [ -n "$cport" ]; then sports="--sport" if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then multiport="$multioption" sports="--sports" fi sports="$sports $cport" fi } interface_error() { fatal_error "Unknown interface $1 in rule: \"$rule\"" } rule_interface_verify() { verify_interface $1 || interface_error $1 } handle_exclusion() { build_exclusion_chain chain filter "$excludesource" "$excludedest" if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then for adr in $(separate_list $addr); do run_iptables -A $logchain $state $(fix_bang $proto $sports $multiport $dports) $user -m conntrack --ctorigdst $adr -j $chain done addr= else run_iptables -A $state $logchain $(fix_bang $cli $proto $sports $multiport $dports) $user -j $chain fi cli= proto= sports= multiport= dports= user= state= } # Set source variables. The 'cli' variable will hold the client match predicate(s). cli= case "$client" in -) ;; *:*) rule_interface_verify ${client%:*} cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})" ;; *.*.*|+*) cli="$(source_ip_range $client)" ;; ~*) cli=$(mac_match $client) ;; *) if [ -n "$client" ]; then rule_interface_verify $client cli="$(match_source_dev $client)" fi ;; esac # Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s). dest_interface= serv= case "$server" in -) ;; *.*.*|+*) serv=$server ;; ~*) fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address" ;; *) if [ -n "$server" ]; then [ -n "$nonat" ] && fatal_error "Destination interface not allowed with $logtarget" rule_interface_verify $server dest_interface="$(match_dest_dev $server)" fi ;; esac # Setup protocol and port variables sports= dports= proto=$protocol addr=$address servport=$serverport multiport= user="$userandgroup" # Restore $chain to the canonical chain. chain=$logchain [ x$port = x- ] && port= [ x$cport = x- ] && cport= case $proto in tcp|TCP|6) do_ports [ "$target" = QUEUE ] && proto="$proto --syn" ;; udp|UDP|17) do_ports ;; icmp|ICMP|1) [ -n "$port" ] && dports="--icmp-type $port" ;; all|ALL) [ -n "$port" ] && \ fatal_error "Port number not allowed with protocol \"all\"; rule: \"$rule\"" proto= ;; ipp2p) dports="-m ipp2p --${port:-ipp2p}" port= proto=tcp do_ports ;; *) [ -n "$port" ] && \ fatal_error "Port number not allowed with protocol \"$proto\"; rule: \"$rule\"" ;; esac proto="${proto:+-p $proto}" # Some misc. setup case "$logtarget" in ACCEPT|DROP|REJECT|CONTINUE) if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" -a -z "$user" -a -z "$excludesource" -a -z "$excludedest" ] ; then error_message "Warning -- Rule \"$rule\" is a POLICY" error_message " -- and should be moved to the policy file" fi ;; REDIRECT) [ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\"" [ -n "$serv" ] && \ fatal_error "REDIRECT rules cannot specify a server IP; rule: \"$rule\"" servport=${servport:=$port} natrule=Yes ;; DNAT|SAME) [ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\"" [ -n "$serv" ] || \ fatal_error "$logtarget rules require a server address; rule: \"$rule\"" natrule=Yes ;; LOG) [ -z "$loglevel" ] && \ fatal_error "LOG requires log level" ;; esac case $SECTION in ESTABLISHED|RELATED) state="-m state --state $SECTION" ;; *) state= ;; esac if [ -n "${serv}${servport}" ]; then if [ $COMMAND != check ]; then # A specific server or server port given if [ -n "$natrule" ]; then add_nat_rule [ $policy = ACCEPT ] && return elif [ -n "$servport" -a "$servport" != "$port" ]; then fatal_error "Only DNAT, SAME and REDIRECT rules may specify destination port mapping; rule \"$rule\"" fi if [ -n "${excludesource}${excludedest}" ]; then handle_exclusion fi if [ -z "$dnat_only" ]; then if [ -n "$serv" ]; then for serv1 in $(separate_list $serv); do for srv in $(firewall_ip_range $serv1); do if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then for adr in $(separate_list $addr); do if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A -m conntrack --ctorigdst $adr \ $user $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) $state fi run_iptables2 -A $chain $state $proto $ratelimit $multiport $cli $sports \ $(dest_ip_range $srv) $dports -m conntrack --ctorigdst $adr $user -j $target done else if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \ $state $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) fi if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $(dest_ip_range $srv) $dports $ratelimit $user -j RETURN fi if [ "$logtarget" != NONAT ]; then run_iptables2 -A $chain $state $proto $multiport $cli $sports \ $(dest_ip_range $srv) $dports $ratelimit $user -j $target fi fi done done else if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \ $state $(fix_bang $proto $sports $multiport $cli $dports) fi [ -n "$nonat" ] && \ addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user -j RETURN [ "$logtarget" != NONAT ] && \ run_iptables2 -A $chain $state $proto $multiport $cli $sports \ $dports $ratelimit $user -j $target fi fi fi else # Destination is a simple zone if [ $COMMAND != check ]; then if [ -n "${excludesource}${excludedest}" ]; then handle_exclusion fi if [ -n "$addr" ]; then for adr in $(separate_list $addr); do if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \ $state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports -m conntrack --ctorigdst $adr) fi if [ "$logtarget" != LOG ]; then if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user -m conntrack --ctorigdst $adr -j RETURN fi if [ "$logtarget" != NONAT ]; then run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \ $sports $dports $ratelimit $user -m conntrack --ctorigdst $adr -j $target fi fi done else if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \ $state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports) fi if [ "$logtarget" != LOG ]; then if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user -j RETURN fi if [ "$logtarget" != NONAT ]; then run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \ $sports $dports $ratelimit $user -j $target fi fi fi fi fi } # # # # # F u n c t i o n B o d y # # # # # [ "x$ratelimit" = "x-" ] && ratelimit= if [ -n "$ratelimit" ]; then case $ratelimit in *:*) ratelimit="-m limit --limit ${ratelimit%:*} --limit-burst ${ratelimit#*:}" ;; *) ratelimit="-m limit --limit $ratelimit" ;; esac fi # Isolate log level if [ "$target" = "${target%:*}" ]; then loglevel= else loglevel="${target#*:}" target="${target%%:*}" expandv loglevel if [ "$loglevel" != "${loglevel%:*}" ]; then logtag="${loglevel#*:}" loglevel="${loglevel%:*}" expandv logtag fi case $loglevel in none*) loglevel= [ $target = LOG ] && return ;; esac loglevel=${loglevel%\!} fi # # Save the original target in 'logtarget' for logging rules # logtarget=${target%-} # # Targets ending in "-" only apply to the nat table # [ $target = $logtarget ] && dnat_only= || dnat_only=Yes # Tranform the rule: # # - parse the user specification # - set 'target' to the filter table target. # - make $FW the destination for REDIRECT # - remove '-' suffix from logtargets while setting 'dnat_only' # - clear 'address' if it has been set to '-' [ "x$userspec" = x- ] && userspec= [ "x$address" = "x-" ] && address= if [ -n "$userspec" ]; then userandgroup="-m owner" case "$userspec" in !*+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; *+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; esac case "$userspec" in !*:*) if [ "$userspec" != "!:" ]; then temp="${userspec#!}" temp="${temp%:*}" [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" fi ;; *:*) if [ "$userspec" != ":" ]; then temp="${userspec%:*}" [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" fi ;; !*) [ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}" ;; *) [ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec" ;; esac [ "$userandgroup" = "-m owner" ] && userandgroup= fi case $target in ACCEPT+|NONAT) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" nonat=Yes target=ACCEPT ;; ACCEPT|LOG) ;; DROP) [ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with DROP" ;; REJECT) target=reject ;; CONTINUE) target=RETURN ;; DNAT*|SAME*) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" target=ACCEPT address=${address:=detect} ;; REDIRECT*) [ $SECTION = NEW ] || fatal_error "REDIRECT rules are not allowed in the $SECTION SECTION" target=ACCEPT address=${address:=all} if [ "x-" = "x$servers" ]; then servers=$FW else servers="$FW::$servers" fi ;; *-) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" ;; esac # Parse and validate source if [ "$clients" = "${clients%:*}" ]; then clientzone="$clients" clients= else clientzone="${clients%%:*}" clients="${clients#*:}" [ -z "$clientzone" -o -z "$clients" ] && \ fatal_error "Empty source zone or qualifier: rule \"$rule\"" fi excludesource= case $clients in *!*!*) fatal_error "Invalid SOURCE in rule \"$rule\"" ;; !*) if [ $(list_count $clients) -gt 1 ]; then excludesource=${clients#!} clients= fi ;; *!*) excludesource=${clients#*!} clients=${clients%!*} ;; esac if [ "$clientzone" = "${clientzone%!*}" ]; then excludezones= else excludezones="${clientzone#*!}" clientzone="${clientzone%!*}" case $logtarget in DNAT|REDIRECT|SAME) ;; *) fatal_error "Exclude zone only allowed with DNAT, SAME or REDIRECT" ;; esac fi validate_zone $clientzone || fatal_error "Undefined Client Zone in rule \"$rule\"" # Parse and validate destination source=$clientzone if [ $source = $FW ]; then source_hosts= elif [ -n "$userspec" ]; then fatal_error "Invalid use of a user-qualification: rule \"$rule\"" else eval source_hosts=\"\$${source}_hosts\" fi if [ "$servers" = "${servers%:*}" ] ; then serverzone="$servers" servers= serverport= else serverzone="${servers%%:*}" servers="${servers#*:}" if [ "$servers" != "${servers%:*}" ] ; then serverport="${servers#*:}" servers="${servers%:*}" [ -z "$serverzone" -o -z "$serverport" ] && \ fatal_error "Empty destination zone or server port: rule \"$rule\"" if [ $(list_count $servers) -gt 1 ]; then case $servers in !*) fatal_error "Exclude lists not supported in the DEST column" ;; esac fi else serverport= [ -z "$serverzone" -o -z "$servers" ] && \ fatal_error "Empty destination zone or qualifier: rule \"$rule\"" fi fi excludedest= case $servers in *!*!*) fatal_error "Invalid DEST in rule \"$rule\"" ;; !*) if [ $(list_count $servers) -gt 1 ]; then excludedest=${servers#*!} servers= fi ;; *!*) excludedest=${servers#*!} servers=${servers%!*} ;; esac if ! validate_zone $serverzone; then fatal_error "Undefined Server Zone in rule \"$rule\"" fi dest=$serverzone # Ensure that this rule doesn't apply to a NONE policy pair of zones chain=${source}2${dest} # If we have one or more exclusion lists, we will create a new chain and # store it's name in 'chain'. We still want log rules to reflect the # canonical chain so we store it's name in $logchain. logchain=$chain eval policy=\$${chain}_policy [ -z "$policy" ] && \ fatal_error "No policy defined from zone $source to zone $dest" [ $policy = NONE ] && \ fatal_error "Rules may not override a NONE policy: rule \"$rule\"" [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all} [ $COMMAND = check ] || ensurechain $chain # Generate Netfilter rule(s) case $logtarget in DNAT*|SAME) if [ -n "$XMULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] then # # Extended MULTIPORT is enabled, and less than # 16 ports are listed (port ranges count as two ports) - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do # # add_a_rule() modifies these so we must set their values each time # server=${servers:=-} port=${ports:=-} cport=${cports:=-} add_a_rule done elif [ -n "$MULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ "$ports" = "${ports%:*}" -a \ "$cports" = "${cports%:*}" -a \ $(list_count $ports) -le 15 -a \ $(list_count $cports) -le 15 ] then # # MULTIPORT is enabled, there are no port ranges in the rule and less than # 16 ports are listed - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do # # add_a_rule() modifies these so we must set their values each time # server=${servers:=-} port=${ports:=-} cport=${cports:=-} add_a_rule done else # # MULTIPORT is disabled or the rule isn't compatible with multiport match # multioption= for client in $(separate_list ${clients:=-}); do for port in $(separate_list ${ports:=-}); do for cport in $(separate_list ${cports:=-}); do server=${servers:=-} add_a_rule done done done fi ;; *) if [ -n "$XMULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] then # # Extended MULTIPORT is enabled, and less than # 16 ports are listed (port ranges count as two ports) - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do # # add_a_rule() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_a_rule done done elif [ -n "$MULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ "$ports" = "${ports%:*}" -a \ "$cports" = "${cports%:*}" -a \ $(list_count $ports) -le 15 -a \ $(list_count $cports) -le 15 ] then # # MULTIPORT is enabled, there are no port ranges in the rule and less than # 16 ports are listed - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do # # add_a_rule() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_a_rule done done else # # MULTIPORT is disabled or the rule isn't compatible with multiport match # multioption= for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do for port in $(separate_list ${ports:=-}); do for cport in $(separate_list ${cports:=-}); do add_a_rule done done done done fi ;; esac # # Report Result # if [ $COMMAND = check ]; then progress_message " Rule \"$rule\" checked." else progress_message " Rule \"$rule\" added." fi } # # Process a macro invocation in the rules file # process_macro() # $1 = target # $2 = param # $2 = clients # $3 = servers # $4 = protocol # $5 = ports # $6 = cports # $7 = address # $8 = ratelimit # $9 = userspec { local itarget="$1" local param="$2" local iclients="$3" local iservers="$4" local iprotocol="$5" local iports="$6" local icports="$7" local iaddress="$8" local iratelimit="$9" local iuserspec="${10}" progress_message "..Expanding Macro $(find_file macro.${itarget%%:*})..." while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do expandv mtarget mclients mservers mprotocol mports mcports mratelimit muserspec mtarget=$(merge_levels $itarget $mtarget) case $mtarget in PARAM|PARAM:*) [ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation" ;; esac case ${mtarget%%:*} in ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-) ;; *) if list_search ${mtarget%%:*} $ACTIONS; then if ! list_search $mtarget $USEDACTIONS; then createactionchain $mtarget USEDACTIONS="$USEDACTIONS $mtarget" fi mtarget=$(find_logactionchain $mtarget) else fatal_error "Invalid Action in rule \"$mtarget ${mclients:--} ${mservers:--} ${mprotocol:--} ${mports:--} ${mcports:--} ${xaddress:--} ${mratelimit:--} ${muserspec:--}\"" fi ;; esac if [ -n "$mclients" ]; then case $mclients in -) mclients=${iclients} ;; *) mclients=${mclients}:${iclients} ;; esac else mclients=${iclients} fi if [ -n "$mservers" ]; then case $mservers in -) mservers=${iservers} ;; *) mservers=${mservers}:${iservers} ;; esac else mservers=${iservers} fi [ -n "$iprotocol" ] && [ "x${iprotocol}" != x- ] && mprotocol=$iprotocol [ -n "$iports" ] && [ "x${iports}" != x- ] && mports=$iports [ -n "$icports" ] && [ "x${icports}" != x- ] && mcports=$icports [ -n "$iratelimit" ] && [ "x${iratelimit}" != x- ] && mratelimit=$iratelimit [ -n "$iuserspec" ] && [ "x${iuserspec}" != x- ] && muserspec=$iuserspec rule="$mtarget ${mclients=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${xaddress:=-} ${mratelimit:=-} ${muserspec:=-}" process_rule $mtarget $mclients $mservers $mprotocol $mports $mcports ${iaddress:=-} $mratelimit $muserspec done < $TMP_DIR/macro.${itarget%%:*} progress_message "..End Macro" } # # Process the rules file for the 'start', 'restart' or 'check' command. # process_rules() { # # Process a rule where the source or destination is "all" # process_wildcard_rule() # $1 = Yes, if this is a macro, $2 = Yes if we want intrazone traffic { local yclients yservers ysourcezone ydestzone ypolicy for yclients in $xclients; do for yservers in $xservers; do ysourcezone=${yclients%%:*} ydestzone=${yservers%%:*} if [ "${ysourcezone}" != "${ydestzone}" -o "$2" = Yes ] ; then eval ypolicy=\$${ysourcezone}2${ydestzone}_policy if [ "$ypolicy" != NONE ] ; then if [ "$1" = Yes ]; then process_macro $xtarget "$xparam" $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec else rule="$xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec" process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec fi fi fi done done } do_it() # $1 = "Yes" if the target is a macro. { expandv xprotocol xports xcports xaddress xratelimit xuserspec intrazone= if [ -z "$SECTIONS" ]; then finish_section ESTABLISHED,RELATED SECTIONS="ESTABLISHED RELATED NEW" SECTION=NEW fi case $xclients in all+) xclients=all intrazone=Yes ;; esac case $xservers in all+) xservers=all intrazone=Yes ;; esac if [ "x$xclients" = xall ]; then xclients="$ZONES $FW" if [ "x$xservers" = xall ]; then xservers="$ZONES $FW" fi process_wildcard_rule "$1" $intrazone return fi if [ "x$xservers" = xall ]; then xservers="$ZONES $FW" process_wildcard_rule "$1" $intrazone return fi if [ "$1" = Yes ]; then process_macro $xtarget "$xparam" $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec" process_rule $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec fi } while read xtarget xclients xservers xprotocol xports xcports xaddress xratelimit xuserspec; do expandv xtarget xclients xservers if [ "x$xclients" = xnone -o "x$servers" = xnone ]; then rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec" progress_message " Rule \"$rule\" ignored." continue fi case "${xtarget%%:*}" in ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-) do_it No ;; SECTION) list_search $xclients $SECTIONS && fatal_error "Duplicate or out of order SECTION $xclients" case $xclients in ESTABLISHED) SECTIONS=ESTABLISHED ;; RELATED) finish_section ESTABLISHED SECTIONS="ESTABLISHED RELATED" ;; NEW) [ $SECTION = RELATED ] && finish_section RELATED || finish_section ESTABLISHED,RELATED SECTIONS="ESTABLISHED RELATED NEW" ;; *) fatal_error "Invalid SECTION $xclients" ;; esac [ -n "$xservers" ] && fatal_error "Invalid SECTION $xclients $xservers" SECTION=$xclients ;; *) if list_search ${xtarget%%:*} $ACTIONS; then if ! list_search $xtarget $USEDACTIONS; then createactionchain $xtarget USEDACTIONS="$USEDACTIONS $xtarget" fi xtarget=$(find_logactionchain $xtarget) do_it No else xtarget1=$(map_old_action ${xtarget%%:*}) case $xtarget1 in */*) xparam=${xtarget1#*/} xtarget1=${xtarget1%%/*} xtarget=$(substitute_action $xtarget1 $xtarget) ;; *) xparam= ;; esac f=macro.$xtarget1 if [ -f $TMP_DIR/$f ]; then do_it Yes else fn=$(find_file $f) if [ -f $fn ]; then strip_file $f $fn do_it Yes else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec" fatal_error "Invalid Action in rule \"$rule\"" fi fi fi ;; esac done < $TMP_DIR/rules case $SECTION in ESTABLISHED) finish_section ESTABLISHED,RELATED ;; RELATED) finish_section RELATED ;; esac SECTION=DONE } # # Process a record from the tos file # # The caller has loaded the column contents from the record into the following # variables: # # src dst protocol sport dport tos # # and has loaded a space-separated list of their values in "rule". # process_tos_rule() { # # Parse the contents of the 'src' variable # if [ "$src" = "${src%:*}" ]; then srczone="$src" src= else srczone="${src%:*}" src="${src#*:}" fi source= # # Validate the source zone # if validate_zone $srczone; then source=$srczone elif [ "$srczone" = "all" ]; then source="all" else error_message "WARNING: Undefined Source Zone - rule \"$rule\" ignored" return fi [ -n "$src" ] && case "$src" in *.*.*|+*|!+*) # # IP Address or networks # src="$(source_ip_range $src)" ;; ~*) src=$(mac_match $src) ;; *) # # Assume that this is a device name # if ! verify_interface $src ; then error_message "WARNING: Unknown Interface in rule \"$rule\" ignored" return fi src="$(match_source_dev $src)" ;; esac # # Parse the contents of the 'dst' variable # if [ "$dst" = "${dst%:*}" ]; then dstzone="$dst" dst= else dstzone="${dst%:*}" dst="${dst#*:}" fi dest= # # Validate the destination zone # if validate_zone $dstzone; then dest=$dstzone elif [ "$dstzone" = "all" ]; then dest="all" else error_message \ "WARNING: Undefined Destination Zone - rule \"$rule\" ignored" return fi [ -n "$dst" ] && case "$dst" in *.*.*|+*|!+*) # # IP Address or networks # ;; *) # # Assume that this is a device name # error_message \ "WARNING: Invalid Destination - rule \"$rule\" ignored" return ;; esac # # Setup PROTOCOL and PORT variables # sports="" dports="" case $protocol in tcp|udp|TCP|UDP|6|17) [ -n "$sport" ] && [ "x${sport}" != "x-" ] && \ sports="--sport $sport" [ -n "$dport" ] && [ "x${dport}" != "x-" ] && \ dports="--dport $dport" ;; icmp|ICMP|0) [ -n "$dport" ] && [ "x${dport}" != "x-" ] && \ dports="--icmp-type $dport" ;; all|ALL) protocol= ;; *) ;; esac protocol="${protocol:+-p $protocol}" tos="-j TOS --set-tos $tos" case "$dstzone" in all|ALL) dst=0.0.0.0/0 ;; *) [ -z "$dst" ] && eval dst=\$${dstzone}_hosts ;; esac for dest in $dst; do dest="$(dest_ip_range $dest)" case $srczone in $FW) run_iptables2 -t mangle -A outtos \ $protocol $dest $dports $sports $tos ;; all|ALL) run_iptables2 -t mangle -A outtos \ $protocol $dest $dports $sports $tos run_iptables2 -t mangle -A pretos \ $protocol $dest $dports $sports $tos ;; *) if [ -n "$src" ]; then run_iptables2 -t mangle -A pretos $src \ $protocol $dest $dports $sports $tos else eval interfaces=\$${srczone}_interfaces for interface in $interfaces; do run_iptables2 -t mangle -A pretos -i $interface \ $protocol $dest $dports $sports $tos done fi ;; esac done progress_message " Rule \"$rule\" added." } # # Process the tos file # process_tos() # $1 = name of tos file { echo "Processing $1..." strip_file tos $1 if [ -s $TMP_DIR/tos ] ; then run_iptables -t mangle -N pretos run_iptables -t mangle -N outtos while read src dst protocol sport dport tos; do expandv src dst protocol sport dport tos rule="$(echo $src $dst $protocol $sport $dport $tos)" process_tos_rule done < $TMP_DIR/tos run_iptables -t mangle -A PREROUTING -j pretos run_iptables -t mangle -A OUTPUT -j outtos fi } # # Display elements of a list with leading white space # display_list() # $1 = List Title, rest of $* = list to display { [ $# -gt 1 ] && echo " $*" } policy_rules() # $1 = chain to add rules to # $2 = policy # $3 = loglevel { local target="$2" case "$target" in ACCEPT) [ -n "$ACCEPT_common" ] && run_iptables -A $1 -j $ACCEPT_common ;; DROP) [ -n "$DROP_common" ] && run_iptables -A $1 -j $DROP_common ;; REJECT) [ -n "$REJECT_common" ] && run_iptables -A $1 -j $REJECT_common target=reject ;; QUEUE) [ -n "$QUEUE_common" ] && run_iptables -A $1 -j $QUEUE_common ;; CONTINUE) target= ;; *) fatal_error "Invalid policy ($policy) for $1" ;; esac if [ $# -eq 3 -a "x${3}" != "x-" ]; then log_rule $3 $1 $2 fi [ -n "$target" ] && run_iptables -A $1 -j $target } # # Generate default policy & log level rules for the passed client & server # zones # # This function is only called when the canonical chain for this client/server # pair is known to exist. If the default policy for this pair specifies the # same chain then we add the policy (and logging) rule to the canonical chain; # otherwise add a rule to the canonical chain to jump to the appropriate # policy chain. # default_policy() # $1 = client $2 = server { local chain="${1}2${2}" local policy= local loglevel= local chain1 jump_to_policy_chain() { # # Add a jump to from the canonical chain to the policy chain. On return, # $chain is set to the name of the policy chain # run_iptables -A $chain -j $chain1 chain=$chain1 } report_syn_flood_protection() { progress_message " Enabled SYN flood protection" } apply_default() { # # Generate policy file column values from the policy chain # eval policy=\$${chain1}_policy eval loglevel=\$${chain1}_loglevel eval synparams=\$${chain1}_synparams # # Add the appropriate rules to the canonical chain ($chain) to enforce # the specified policy if [ "$chain" = "$chain1" ]; then # # The policy chain is the canonical chain; add policy rule to it # The syn flood jump has already been added if required. # policy_rules $chain $policy $loglevel else # # The policy chain is different from the canonical chain -- approach # depends on the policy # case $policy in ACCEPT|QUEUE) if [ -n "$synparams" ]; then # # To avoid double-counting SYN packets, enforce the policy # in this chain. # report_syn_flood_protection policy_rules $chain $policy $loglevel else # # No problem with double-counting so just jump to the # policy chain. # jump_to_policy_chain fi ;; CONTINUE) # # Silly to jump to the policy chain -- add any logging # rules and enable SYN flood protection if requested # [ -n "$synparams" ] && \ report_syn_flood_protection policy_rules $chain $policy $loglevel ;; *) # # DROP or REJECT policy -- enforce in the policy chain and # enable SYN flood protection if requested. # [ -n "$synparams" ] && \ report_syn_flood_protection jump_to_policy_chain ;; esac fi progress_message " Policy $policy for $1 to $2 using chain $chain" } eval chain1=\$${1}2${2}_policychain if [ -n "$chain1" ]; then apply_default $1 $2 else fatal_error "No default policy for zone $1 to zone $2" fi } # # Complete a standard chain # # - run any supplied user exit # - search the policy file for an applicable policy and add rules as # appropriate # - If no applicable policy is found, add rules for an assummed # policy of DROP INFO # complete_standard_chain() # $1 = chain, $2 = source zone, $3 = destination zone { local policy= local loglevel= local policychain= run_user_exit $1 eval policychain=\$${2}2${3}_policychain if [ -n "$policychain" ]; then eval policy=\$${policychain}_policy eval loglevel=\$${policychain}_loglevel eval policy_rules $1 $policy $loglevel else policy_rules $1 DROP info fi } # # Find the appropriate chain to pass packets from a source zone to a # destination zone # # If the canonical chain for this zone pair exists, echo it's name; otherwise # locate and echo the name of the appropriate policy chain # rules_chain() # $1 = source zone, $2 = destination zone { local chain=${1}2${2} local policy havechain $chain && { echo $chain; return; } [ "$1" = "$2" ] && { echo ACCEPT; return; } eval chain=\$${chain}_policychain eval policy=\$${chain}_policy if [ "$policy" != CONTINUE ] ; then [ -n "$chain" ] && { echo $chain; return; } fatal_error "No policy defined for zone $1 to zone $2" fi } # # echo the list of networks routed out of a given interface # get_routed_networks() # $1 = interface name { local address local rest ip route show dev $1 2> /dev/null | while read address rest; do if [ "x$address" = xdefault ]; then error_message "WARNING: default route ignored on interface $1" else [ "$address" = "${address%/*}" ] && address="${address}/32" echo $address fi done } # # Set up Routing # setup_routes() { run_iptables -t mangle -A PREROUTING -m connmark ! --mark 0 -j CONNMARK --restore-mark run_iptables -t mangle -A OUTPUT -m connmark ! --mark 0 -j CONNMARK --restore-mark run_iptables -t mangle -N routemark for interface in $ROUTEMARK_INTERFACES ; do iface=$(chain_base $interface) eval mark_value=\$${iface}_routemark run_iptables -t mangle -A PREROUTING -i $interface -m mark --mark 0 -j routemark run_iptables -t mangle -A routemark -i $interface -j MARK --set-mark $mark_value done run_iptables -t mangle -A routemark -m mark ! --mark 0 -j CONNMARK --save-mark --mask 255 } # # Set up Source NAT (including masquerading) # setup_masq() { do_ipsec_options() { local options="$(separate_list $ipsec)" option policy="-m policy --pol ipsec --dir out" for option in $options; do case $option in [Yy]es) ;; strict) policy="$policy --strict" ;; next) policy="$policy --next" ;; reqid=*) policy="$policy --reqid ${option#*=}" ;; spi=*) policy="$policy --spi ${option#*=}" ;; proto=*) policy="$policy --proto ${option#*=}" ;; mode=*) policy="$policy --mode ${option#*=}" ;; tunnel-src=*) policy="$policy --tunnel-src ${option#*=}" ;; tunnel-dst=*) policy="$policy --tunnel-dst ${option#*=}" ;; reqid!=*) policy="$policy ! --reqid ${option#*=}" ;; spi!=*) policy="$policy ! --spi ${option#*=}" ;; proto!=*) policy="$policy ! --proto ${option#*=}" ;; mode!=*) policy="$policy ! --mode ${option#*=}" ;; tunnel-src!=*) policy="$policy ! --tunnel-src ${option#*=}" ;; tunnel-dst!=*) policy="$policy ! --tunnel-dst ${option#*=}" ;; *) fatal_error "Invalid IPSEC option \"$option\"" ;; esac done } setup_one() { local add_snat_aliases=$ADD_SNAT_ALIASES pre_nat= policy= destnets= [ "x$ipsec" = x- ] && ipsec= case $ipsec in Yes|yes) [ -n "$POLICY_MATCH" ] || \ fatal_error "IPSEC=Yes requires policy match support in your kernel and iptables" policy="-m policy --pol ipsec --dir out" ;; No|no) [ -n "$POLICY_MATCH" ] || \ fatal_error "IPSEC=No requires policy match support in your kernel and iptables" policy="-m policy --pol none --dir out" ;; *) if [ -n "$ipsec" ]; then do_ipsec_options elif [ -n "$POLICY_MATCH" ]; then policy="-m policy --pol none --dir out" fi ;; esac case $fullinterface in +*) pre_nat=Yes fullinterface=${fullinterface#+} ;; esac case $fullinterface in *::*) add_snat_aliases= destnets="${fullinterface##*:}" fullinterface="${fullinterface%:*}" ;; *:*:*) # Both alias name and networks destnets="${fullinterface##*:}" fullinterface="${fullinterface%:*}" ;; *:) add_snat_aliases= fullinterface=${fullinterface%:} ;; *:*) # Alias name OR networks case ${fullinterface#*:} in *.*) # It's a networks destnets="${fullinterface#*:}" fullinterface="${fullinterface%:*}" ;; *) #it's an alias name ;; esac ;; *) ;; esac interface=${fullinterface%:*} if ! list_search $interface $ALL_INTERFACES; then fatal_error "Unknown interface $interface" fi if [ "$networks" = "${networks%!*}" ]; then nomasq= else nomasq="${networks#*!}" networks="${networks%!*}" fi source="${networks:=0.0.0.0/0}" case $source in *.*.*|+*|!+*) ;; *) networks=$(get_routed_networks $networks) [ -z "$networks" ] && fatal_error "Unable to determine the routes through interface \"$source\"" networks="$networks" ;; esac [ "x$addresses" = x- ] && addresses= if [ -n "$addresses" -a -n "$add_snat_aliases" ]; then for address in $(separate_list $addresses); do address=${address%:)} if [ -n "$address" ]; then for addr in $(ip_range_explicit ${address%:*}) ; do if ! list_search $addr $ALIASES_TO_ADD; then [ -n "$RETAIN_ALIASES" ] || save_command qt ip addr del $addr dev $interface ALIASES_TO_ADD="$ALIASES_TO_ADD $addr $fullinterface" case $fullinterface in *:*) fullinterface=${fullinterface%:*}:$((${fullinterface#*:} + 1 )) ;; esac fi done fi done fi [ "x$proto" = x- ] && proto= [ "x$ports" = x- ] && ports= if [ -n "$proto" ]; then displayproto="($proto)" case $proto in tcp|TCP|udp|UDP|6|17) if [ -n "$ports" ]; then displayproto="($proto $ports)" listcount=$(list_count $ports) if [ $listcount -gt 1 ]; then case $ports in *:*) if [ -n "$XMULTIPORT" ]; then if [ $(($listcount + $(list_count1 $(split $ports) ) )) -le 16 ]; then ports="-m multiport --dports $ports" else fatal_error "More than 15 entries in port list ($ports)" fi else fatal_error "Port Range not allowed in list ($ports)" fi ;; *) if [ -n "$MULTIPORT" ]; then [ $listcount -le 15 ] || fatal_error "More than 15 entries in port list ($ports)" ports="-m multiport --dports $ports" else fatal_error "Port Ranges require multiport match support in your kernel ($ports)" fi ;; esac else ports="--dport $ports" fi fi ;; *) [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)" ;; esac proto="-p $proto" else displayproto="(all)" [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)" fi destination=${destnets:=0.0.0.0/0} [ -z "$pre_nat" ] && chain=$(masq_chain $interface) || chain=$(snat_chain $interface) case $destnets in !*) destnets=${destnets#!} if [ $COMMAND != check ]; then build_exclusion_chain newchain nat "$nomasq" "$destnets" if [ -n "$networks" ]; then for s in $networks; do addnatrule $chain $(source_ip_range $s) $proto $ports $policy -j $newchain done networks= else addnatrule $chain -j $newchain fi else networks= fi chain=$newchain destnets=0.0.0.0/0 proto= ports= policy= [ -n "$nomasq" ] && source="$source except $nomasq" ;; *) if [ -n "$nomasq" ]; then if [ $COMMAND != check ]; then build_exclusion_chain newchain nat $nomasq if [ -n "$networks" ]; then for s in $networks; do for destnet in $(separate_list $destnets); do addnatrule $chain $(both_ip_ranges $s $destnet) $proto $ports $policy -j $newchain done done else for destnet in $(separate_list $destnets); do addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $newchain done fi fi chain=$newchain networks= destnets=0.0.0.0/0 proto= ports= policy= source="$source except $nomasq" fi ;; esac addrlist= target=MASQUERADE if [ -n "$addresses" ]; then case "$addresses" in SAME:nodst:*) target="SAME --nodst" addresses=${addresses#SAME:nodst:} for address in $(separate_list $addresses); do addrlist="$addrlist --to $address"; done ;; SAME:*) target="SAME" addresses=${addresses#SAME:} for address in $(separate_list $addresses); do addrlist="$addrlist --to $address"; done ;; *) for address in $(separate_list $addresses); do case $address in *.*.*.*) target=SNAT addrlist="$addrlist --to-source $address" ;; *) addrlist="$addrlist --to-ports ${address#:}" ;; esac done ;; esac fi if [ -n "$networks" ]; then for network in $networks; do if [ $COMMAND != check ]; then for destnet in $(separate_list $destnets); do addnatrule $chain $(both_ip_ranges $network $destnet) $proto $ports $policy -j $target $addrlist done fi if [ -n "$addresses" ]; then progress_message " To $destination $displayproto from $network through ${interface} using $addresses" else progress_message " To $destination $displayproto from $network through ${interface}" fi done else if [ $COMMAND != check ]; then for destnet in $(separate_list $destnets); do addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $target $addrlist done fi if [ -n "$addresses" ]; then progress_message " To $destination $displayproto from $source through ${interface} using $addresses" else progress_message " To $destination $displayproto from $source through ${interface}" fi fi } strip_file masq $1 if [ -n "$NAT_ENABLED" ]; then echo "Masqueraded Networks and Hosts:" [ -n "$RETAIN_ALIASES" -o $COMMAND = check ] || save_progress_message "Restoring Masquerading/SNAT..." fi while read fullinterface networks addresses proto ports ipsec; do expandv fullinterface networks addresses proto ports ipsec [ -n "$NAT_ENABLED" ] && setup_one || \ error_message "WARNING: NAT disabled; masq rule ignored" done < $TMP_DIR/masq } # # Add a record to the blacklst chain # # $source = address match # $proto = protocol selector # $dport = destination port selector # add_blacklist_rule() { if [ "$COMMAND" != check ]; then if [ -n "$BLACKLIST_LOGLEVEL" ]; then log_rule $BLACKLIST_LOGLEVEL blacklst $BLACKLIST_DISPOSITION $(fix_bang $source $proto $dport) fi run_iptables2 -A blacklst $source $proto $dport -j $disposition fi } # # Process a record from the blacklist file # # $networks = address/networks # $protocol = Protocol Number/Name # $port = Port Number/Name # process_blacklist_rec() { local source local addr local proto local dport local temp local setname for addr in $(separate_list $networks); do case $addr in ~*) addr=$(echo $addr | sed 's/~//;s/-/:/g') source="--match mac --mac-source $addr" ;; *) source="$(source_ip_range $addr)" ;; esac if [ -n "$protocol" ]; then proto=" -p $protocol " case $protocol in tcp|TCP|6|udp|UDP|17) if [ -n "$ports" ]; then if [ -n "$MULTIPORT" -a \ "$ports" != "${ports%,*}" -a \ "$ports" = "${ports%:*}" -a \ $(list_count $ports) -le 15 ] then dport="-m multiport --dports $ports" add_blacklist_rule else for dport in $(separate_list $ports); do dport="--dport $dport" add_blacklist_rule done fi else add_blacklist_rule fi ;; icmp|ICMP|0) if [ -n "$ports" ]; then for dport in $(separate_list $ports); do dport="--icmp-type $dport" add_blacklist_rule done else add_blacklist_rule fi ;; *) add_blacklist_rule ;; esac else add_blacklist_rule fi if [ -n "$ports" ]; then addr="$addr $protocol $ports" elif [ -n "$protocol" ]; then addr="$addr $protocol" fi if [ "$COMMAND" = check ]; then progress_message " $addr" Verified else progress_message " $addr added to Black List" fi done } # # Setup the Black List # setup_blacklist() { local hosts="$(find_hosts_by_option blacklist)" local f=$(find_file blacklist) local disposition=$BLACKLIST_DISPOSITION local ipsec policy if [ -n "$hosts" -a -f $f ]; then echo "Setting up Blacklisting..." strip_file blacklist $f createchain blacklst no [ -n "$BLACKLISTNEWONLY" ] && state="-m state --state NEW,INVALID" || state= for host in $hosts; do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} network=${host#*:} for chain in $(first_chains $interface); do run_iptables -A $chain $state $(match_source_hosts $network) $policy -j blacklst done [ $network = 0/0.0.0.0 ] && network= || network=":$network" progress_message " Blacklisting enabled on ${interface}${network}" done [ "$disposition" = REJECT ] && disposition=reject if [ -z "$DELAYBLACKLISTLOAD" ]; then while read networks protocol ports; do expandv networks protocol ports process_blacklist_rec done < $TMP_DIR/blacklist fi fi } # # Refresh the Black List # refresh_blacklist() { local f=$(find_file blacklist) local disposition=$BLACKLIST_DISPOSITION if qt $IPTABLES -L blacklst -n ; then echo "Loading Black List..." strip_file blacklist $f [ "$disposition" = REJECT ] && disposition=reject run_iptables -F blacklst while read networks protocol ports; do expandv networks protocol ports process_blacklist_rec done < $TMP_DIR/blacklist fi } # # Verify the Black List # validate_blacklist() { local f=$(find_file blacklist) local disposition=$BLACKLIST_DISPOSITION echo "Checking Black List..." strip_file blacklist $f [ "$disposition" = REJECT ] && disposition=reject while read networks protocol ports; do expandv networks protocol ports process_blacklist_rec done < $TMP_DIR/blacklist } # # Verify that kernel has netfilter support # verify_os_version() { osversion=$(uname -r) case $osversion in 2.4.*|2.5.*|2.6.*) ;; *) startup_error "Shorewall version $version does not work with kernel version $osversion" ;; esac [ $COMMAND = start -a -n "$(lsmod 2> /dev/null | grep '^ipchains')" ] && \ startup_error "Shorewall can't start with the ipchains kernel module loaded - see FAQ #8" } # # Add IP Aliases # add_ip_aliases() { local addresses external interface inet cidr rest val arping=$(mywhich arping) address_details() { # # Folks feel uneasy if they don't see all of the same # decoration on these IP addresses that they see when their # distro's net config tool adds them. In an attempt to reduce # the anxiety level, we have the following code which sets # the VLSM and BRD from an existing address in the same networks # # Get all of the lines that contain inet addresses with broadcast # ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | while read inet cidr rest ; do case $cidr in */*) if in_network $external $cidr; then echo "/${cidr#*/} brd $(broadcastaddress $cidr)" break fi ;; esac done } do_one() { val=$(address_details) if [ -n "$RETAIN_ALIASES" ]; then run_ip addr add ${external}${val} dev $interface $label save_command qt ip addr add ${external}${val} dev $interface $label else ensure_and_save_command ip addr add ${external}${val} dev $interface $label fi [ -n "$arping" ] && run_and_save_command qt $arping -U -c 2 -I $interface $external echo "$external $interface" >> /var/lib/shorewall/nat [ -n "$label" ] && label="with $label" progress_message " IP Address $external added to interface $interface $label" } set -- $ALIASES_TO_ADD save_progress_message "Restoring 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;shift list_search $external $(find_interface_addresses $interface) || do_one done } # # Load kernel modules required for Shorewall # load_kernel_modules() { save_modules_dir=$MODULESDIR [ -z "$MODULESDIR" ] && \ MODULESDIR=/lib/modules/$(uname -r)/kernel/net/ipv4/netfilter modules=$(find_file modules) if [ -f $modules -a -d $MODULESDIR ]; then progress_message "Loading Modules..." . $modules fi MODULESDIR=$save_modules_dir } save_load_kernel_modules() { modules=$(find_file modules) save_progress_message "Loading kernel modules..." save_command "reload_kernel_modules <<__EOF__" while read command; do case "$command" in loadmodule*) save_command $command ;; esac done < $modules save_command __EOF__ save_command "" } # Verify that the 'ip' program is installed verify_ip() { qt ip link ls ||\ startup_error "Shorewall $version requires the iproute package ('ip' utility)" } # # Perform Initialization # - Delete all old rules # - Delete all user chains # - Set the POLICY on all standard chains and add a rule to allow packets # that are part of established connections # - Determine the zones # initialize_netfilter () { report_capabilities if [ -n "$BRIDGING" ]; then [ -n "$PHYSDEV_MATCH" ] || startup_error "BRIDGING=Yes requires Physdev Match support in your Kernel and iptables" fi [ "$MACLIST_TTL" = "0" ] && MACLIST_TTL= if [ -n "$MACLIST_TTL" -a -z "$RECENT_MATCH" ]; then startup_error "MACLIST_TTL requires the Recent Match capability which is not present in your Kernel and/or iptables" fi [ -n "$RFC1918_STRICT" -a -z "$CONNTRACK_MATCH" ] && \ startup_error "RFC1918_STRICT=Yes requires Connection Tracking match" echo "Determining Zones..." determine_zones display_list "Zones:" $ZONES echo "Validating interfaces file..." validate_interfaces_file echo "Validating hosts file..." validate_hosts_file echo "Validating Policy file..." validate_policy echo "Determining Hosts in Zones..." determine_interfaces determine_hosts run_user_exit init # # Some files might be large so strip them while the firewall is still running # (restart command). This reduces the length of time that the firewall isn't # accepting new connections. # strip_file rules strip_file proxyarp strip_file maclist strip_file nat strip_file netmap echo "Pre-processing Actions..." process_actions1 TERMINATOR=fatal_error deletechain shorewall [ -n "$NAT_ENABLED" ] && delete_nat delete_proxy_arp [ -n "$MANGLE_ENABLED" ] && \ run_iptables -t mangle -F && \ run_iptables -t mangle -X [ -n "$RAW_TABLE" ] && \ run_iptables -t raw -F && \ run_iptables -t raw -X [ -n "$CLEAR_TC" ] && delete_tc echo "Deleting user chains..." exists_INPUT=Yes exists_OUTPUT=Yes exists_FORWARD=Yes process_criticalhosts if [ -n "$CRITICALHOSTS" ]; then setpolicy INPUT ACCEPT setpolicy OUTPUT ACCEPT setpolicy FORWARD DROP deleteallchains enable_critical_hosts setpolicy INPUT DROP setpolicy OUTPUT DROP setcontinue FORWARD setcontinue INPUT setcontinue OUTPUT else setpolicy INPUT DROP setpolicy OUTPUT DROP setpolicy FORWARD DROP deleteallchains setcontinue FORWARD setcontinue INPUT setcontinue OUTPUT fi f=$(find_file ipsets) if [ -f $f ]; then echo "Processing $f ..." ipset -U :all: :all: run_ipset -F run_ipset -X run_ipset -R < $f fi run_user_exit continue f=$(find_file routestopped) echo "Processing $f ..." strip_file routestopped $f process_routestopped -A [ -n "$DISABLE_IPV6" ] && disable_ipv6 # # Enable the Loopback interface for now # run_iptables -A INPUT -i lo -j ACCEPT run_iptables -A OUTPUT -o lo -j ACCEPT # # Allow DNS lookups during startup for FQDNs # for chain in INPUT OUTPUT FORWARD; do run_iptables -A $chain -p udp --dport 53 -j ACCEPT done if [ -n "$CLAMPMSS" ]; then case $CLAMPMSS in Yes) option="--clamp-mss-to-pmtu" ;; *) option="--set-mss $CLAMPMSS" ;; esac run_iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS $option fi accounting_file=$(find_file accounting) [ -f $accounting_file ] && setup_accounting $accounting_file createchain reject no createchain dynamic no createchain smurfs no if [ -f /var/lib/shorewall/save ]; then echo "Restoring dynamic rules..." if [ -f /var/lib/shorewall/save ]; then while read target ignore1 ignore2 address rest; do case $target in DROP|reject) run_iptables -A dynamic -s $address -j $target ;; *) ;; esac done < /var/lib/shorewall/save fi fi [ -n "$BLACKLISTNEWONLY" ] && state="-m state --state NEW,INVALID" || state= echo "Creating Interface Chains..." for interface in $ALL_INTERFACES; do createchain $(forward_chain $interface) no run_iptables -A $(forward_chain $interface) $state -j dynamic createchain $(input_chain $interface) no run_iptables -A $(input_chain $interface) $state -j dynamic done } # # Construct zone-independent rules # add_common_rules() { local savelogparms="$LOGPARMS" local broadcasts="$(find_broadcasts) 255.255.255.255 224.0.0.0/4" drop_broadcasts() { for address in $broadcasts ; do run_iptables -A reject -d $address -j DROP done } # # Populate the smurf chain # for address in $broadcasts ; do [ -n "$SMURF_LOG_LEVEL" ] && log_rule $SMURF_LOG_LEVEL smurfs DROP -s $address run_iptables -A smurfs $(source_ip_range $address) -j DROP done # # Reject Rules -- Don't respond to broadcasts with an ICMP # if [ -n "$USEPKTTYPE" ]; then qt $IPTABLES -A reject -m pkttype --pkt-type broadcast -j DROP if ! qt $IPTABLES -A reject -m pkttype --pkt-type multicast -j DROP; then # # No pkttype support -- do it the hard way # drop_broadcasts fi else drop_broadcasts fi # # Don't feed the smurfs # for address in $broadcasts ; do run_iptables -A reject -s $address -j DROP done run_iptables -A reject -p tcp -j REJECT --reject-with tcp-reset run_iptables -A reject -p udp -j REJECT # # Not all versions of iptables support these so don't complain if they don't work # qt $IPTABLES -A reject -p icmp -j REJECT --reject-with icmp-host-unreachable if ! qt $IPTABLES -A reject -j REJECT --reject-with icmp-host-prohibited; then # # In case the above doesn't work # run_iptables -A reject -j REJECT fi # # Create common action chains # for action in $USEDACTIONS; do createactionchain $action done run_user_exit initdone # # Process Black List # setup_blacklist # # SMURFS # hosts=$(find_hosts_by_option nosmurfs) if [ -n "$hosts" ]; then echo "Adding Anti-smurf Rules" for host in $hosts; do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} network=${host#*:} for chain in $(first_chains $interface); do run_iptables -A $chain -m state --state NEW,INVALID $(match_source_hosts $network) $policy -j smurfs done done fi # # DHCP # interfaces=$(find_interfaces_by_option dhcp) if [ -n "$interfaces" ]; then echo "Adding rules for DHCP" for interface in $interfaces; do if [ -n "$BRIDGING" ]; then is_bridge=$( brctl show $interface 2> /dev/null | grep ^$interface[[:space:]] ) [ -n "$is_bridge" ] && \ $IPTABLES -A $(forward_chain $interface) -p udp -o $interface --dport 67:68 -j ACCEPT fi run_iptables -A $(input_chain $interface) -p udp --dport 67:68 -j ACCEPT run_iptables -A OUTPUT -o $interface -p udp --dport 67:68 -j ACCEPT done fi # # RFC 1918 # hosts="$(find_hosts_by_option norfc1918)" if [ -n "$hosts" ]; then echo "Enabling RFC1918 Filtering" strip_file rfc1918 createchain norfc1918 no createchain rfc1918 no log_rule $RFC1918_LOG_LEVEL rfc1918 DROP run_iptables -A rfc1918 -j DROP chain=norfc1918 if [ -n "$RFC1918_STRICT" ]; then # # We'll generate two chains - one for source and one for destination # chain=rfc1918d createchain $chain no elif [ -n "$MANGLE_ENABLED" -a -z "$CONNTRACK_MATCH" ]; then # # Mangling is enabled but conntrack match isn't available -- # create a chain in the mangle table to filter RFC1918 destination # addresses. This must be done in the mangle table before we apply # any DNAT rules in the nat table # # Also add a chain to log and drop any RFC1918 packets that we find # run_iptables -t mangle -N man1918 run_iptables -t mangle -N rfc1918 log_rule $RFC1918_LOG_LEVEL rfc1918 DROP -t mangle run_iptables -t mangle -A rfc1918 -j DROP fi while read networks target; do case $target in logdrop) target=rfc1918 s_target=rfc1918 ;; DROP) s_target=DROP ;; RETURN) [ -n "$RFC1918_STRICT" ] && s_target=rfc1918d || s_target=RETURN ;; *) fatal_error "Invalid target ($target) for $networks" ;; esac for network in $(separate_list $networks); do run_iptables2 -A norfc1918 $(source_ip_range $network) -j $s_target if [ -n "$CONNTRACK_MATCH" ]; then # # We have connection tracking match -- match on the original destination # run_iptables2 -A $chain -m conntrack --ctorigdst $network -j $target elif [ -n "$MANGLE_ENABLED" ]; then # # No connection tracking match but we have mangling -- add a rule to # the mangle table # run_iptables2 -t mangle -A man1918 $(dest_ip_range $network) -j $target fi done done < $TMP_DIR/rfc1918 [ -n "$RFC1918_STRICT" ] && run_iptables -A norfc1918 -j rfc1918d for host in $hosts; do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} networks=${host#*:} for chain in $(first_chains $interface); do run_iptables -A $chain -m state --state NEW $(match_source_hosts $networks) $policy -j norfc1918 done [ -n "$MANGLE_ENABLED" -a -z "$CONNTRACK_MATCH" ] && \ run_iptables -t mangle -A PREROUTING -m state --state NEW -i $interface $(match_source_hosts $networks) -j man1918 done fi hosts=$(find_hosts_by_option tcpflags) if [ -n "$hosts" ]; then echo "Setting up TCP Flags checking..." createchain tcpflags no if [ -n "$TCP_FLAGS_LOG_LEVEL" ]; then createchain logflags no savelogparms="$LOGPARMS" [ "$TCP_FLAGS_LOG_LEVEL" = ULOG ] || LOGPARMS="$LOGPARMS --log-ip-options" log_rule $TCP_FLAGS_LOG_LEVEL logflags $TCP_FLAGS_DISPOSITION LOGPARMS="$savelogparms" case $TCP_FLAGS_DISPOSITION in REJECT) run_iptables -A logflags -j REJECT --reject-with tcp-reset ;; *) run_iptables -A logflags -j $TCP_FLAGS_DISPOSITION ;; esac disposition="-j logflags" else disposition="-j $TCP_FLAGS_DISPOSITION" fi run_iptables -A tcpflags -p tcp --tcp-flags ALL FIN,URG,PSH $disposition run_iptables -A tcpflags -p tcp --tcp-flags ALL NONE $disposition run_iptables -A tcpflags -p tcp --tcp-flags SYN,RST SYN,RST $disposition run_iptables -A tcpflags -p tcp --tcp-flags SYN,FIN SYN,FIN $disposition # # There are a lot of probes to ports 80, 3128 and 8080 that use a source # port of 0. This catches them even if they are directed at an IP that # hosts a web server. # run_iptables -A tcpflags -p tcp --syn --sport 0 $disposition for host in $hosts; do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} network=${host#*:} for chain in $(first_chains $interface); do run_iptables -A $chain -p tcp $(match_source_hosts $network) $policy -j tcpflags done done fi # # ARP Filtering # save_progress_message "Restoring ARP filtering..." for f in /proc/sys/net/ipv4/conf/*; do run_and_save_command "[ -f $f/arp_filter ] && echo 0 > $f/arp_filter" run_and_save_command "[ -f $f/arp_filter ] && echo 0 > $f/arp_ignore" done interfaces=$(find_interfaces_by_option arp_filter) interfaces1=$(find_interfaces_by_option1 arp_ignore) if [ -n "${interfaces}${interfaces1}" ]; then echo "Setting up ARP Filtering..." for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/arp_filter if [ -f $file ]; then run_and_save_command "echo 1 > $file" else error_message \ "WARNING: Cannot set ARP filtering on $interface" fi done for interface in $interfaces1; do file=/proc/sys/net/ipv4/conf/$interface/arp_ignore if [ -f $file ]; then eval command="\"echo \$$(chain_base $interface)_arp_ignore > $file\"" run_and_save_command "$command" else error_message \ "WARNING: Cannot set ARP filtering on $interface" fi done fi # # Route Filtering # interfaces="$(find_interfaces_by_option routefilter)" if [ -n "$interfaces" -o -n "$ROUTE_FILTER" ]; then echo "Setting up Kernel Route Filtering..." save_progress_message "Restoring Route Filtering..." for f in /proc/sys/net/ipv4/conf/*; do run_and_save_command "[ -f $f/rp_filter ] && echo 0 > $f/rp_filter" done for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/rp_filter if [ -f $file ]; then run_and_save_command "echo 1 > $file" else error_message \ "WARNING: Cannot set route filtering on $interface" fi done run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter" if [ -n "$ROUTE_FILTER" ]; then run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter" run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter" fi run_and_save_command "[ -n \"\$NOROUTES\" ] || ip route flush cache" fi # # Martian Logging # interfaces="$(find_interfaces_by_option logmartians)" if [ -n "$interfaces" -o -n "$LOG_MARTIANS" ]; then echo "Setting up Martian Logging..." save_progress_message "Restoring Martian Logging..." for f in /proc/sys/net/ipv4/conf/*; do run_and_save_command "[ -f $f/log_martians ] && echo 0 > $f/log_martians" done for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/log_martians if [ -f $file ]; then run_and_save_command "echo 1 > $file" else error_message \ "WARNING: Cannot set Martian logging on $interface" fi done if [ -n "$LOG_MARTIANS" ]; then run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/default/log_martians" run_and_save_command "echo 1 > /proc/sys/net/ipv4/conf/all/log_martians" fi fi # # Source Routing # save_progress_message "Restoring Accept Source Routing..." for f in /proc/sys/net/ipv4/conf/*; do run_and_save_command "[ -f $f/accept_source_route ] && echo 0 > $f/accept_source_route" done interfaces=$(find_interfaces_by_option sourceroute) if [ -n "$interfaces" ]; then echo "Setting up Accept Source Routing..." for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/accept_source_route if [ -f $file ]; then run_and_save_command "echo 1 > $file" else error_message \ "WARNING: Cannot set Accept Source Routing on $interface" fi done fi if [ -n "$DYNAMIC_ZONES" ]; then echo "Setting up Dynamic Zone Chains..." for interface in $ALL_INTERFACES; do for chain in $(dynamic_chains $interface); do createchain $chain no done chain=$(dynamic_in $interface) createnatchain $chain run_iptables -A $(input_chain $interface) -j $chain run_iptables -A $(forward_chain $interface) -j $(dynamic_fwd $interface) run_iptables -A OUTPUT -o $interface -j $(dynamic_out $interface) done fi # # UPnP # interfaces=$(find_interfaces_by_option upnp) if [ -n "$interfaces" ]; then echo "Setting up UPnP..." createnatchain UPnP for interface in $interfaces; do run_iptables -t nat -A PREROUTING -i $interface -j UPnP done fi setup_forwarding } # # Scan the policy file defining the necessary chains # Add the appropriate policy rule(s) to the end of each canonical chain # apply_policy_rules() { # # Create policy chains # for chain in $ALL_POLICY_CHAINS; do eval policy=\$${chain}_policy eval loglevel=\$${chain}_loglevel eval optional=\$${chain}_is_optional if ! havechain $chain && [ -z "$optional" -a "$policy" != CONTINUE ]; then # # The chain doesn't exist. Create the chain and add policy # rules # createchain $chain yes # # If either client or server is 'all' then this MUST be # a policy chain and we must apply the appropriate policy rules # # Otherwise, this is a canonical chain which will be handled in # the for loop below # case $chain in all2*|*2all) policy_rules $chain $policy $loglevel ;; esac fi done # # Add policy rules to canonical chains # for zone in $FW $ZONES; do for zone1 in $FW $ZONES; do chain=${zone}2${zone1} if havechain $chain; then run_user_exit $chain default_policy $zone $zone1 fi done done } # # Activate the rules # activate_rules() { local PREROUTING_rule=1 local POSTROUTING_rule=1 # # Jump to a NAT chain from one of the builtin nat chains # addnatjump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments { local sourcechain=$1 destchain=$2 shift shift if havenatchain $destchain ; then run_iptables2 -t nat -A $sourcechain $@ -j $destchain else [ -n "$BRIDGING" -a -f $TMP_DIR/physdev ] && -rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" -a -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi } # # Jump to a RULES chain from one of the builtin nat chains. These jumps are # are inserted before jumps to one-to-one NAT chains. # addrulejump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments { local sourcechain=$1 destchain=$2 shift shift if havenatchain $destchain; then eval run_iptables2 -t nat -I $sourcechain \ \$${sourcechain}_rule $@ -j $destchain eval ${sourcechain}_rule=\$\(\(\$${sourcechain}_rule + 1\)\) else [ -n "$BRIDGING" -a -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" -a -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi } # # Create a dynamic chain for a zone and jump to it from a second chain # create_zone_dyn_chain() # $1 = zone, $2 = second chain { createchain ${1}_dyn No run_iptables -A $2 -j ${1}_dyn } # # Add jumps to early SNAT chains # for interface in $ALL_INTERFACES; do addnatjump POSTROUTING $(snat_chain $interface) -o $interface done # # Add jumps for dynamic nat chains # [ -n "$DYNAMIC_ZONES" ] && for interface in $ALL_INTERFACES ; do addrulejump PREROUTING $(dynamic_in $interface) -i $interface done # # Add jumps from the builtin chains to the nat chains # addnatjump PREROUTING nat_in addnatjump POSTROUTING nat_out for interface in $ALL_INTERFACES; do addnatjump PREROUTING $(input_chain $interface) -i $interface addnatjump POSTROUTING $(output_chain $interface) -o $interface done > /var/lib/shorewall/chains > /var/lib/shorewall/zones # # Create forwarding chains for complex zones and generate jumps for IPSEC source hosts to that chain. # for zone in $ZONES; do if eval test -n \"\$${zone}_is_complex\" ; then frwd_chain=${zone}_frwd createchain $frwd_chain No if [ -n "$POLICY_MATCH" ]; then # # Because policy match only matches an 'in' or an 'out' policy (but not both), we have to place the # '--pol ipsec --dir in' rules at the front of the interface forwarding chains. Otherwise, decrypted packets # can match '--pol none --dir out' rules and send the packets down the wrong rules chain. # eval is_ipsec=\$${zone}_is_ipsec if [ -n "$is_ipsec" ]; then eval source_hosts=\$${zone}_hosts [ -n "$DYNAMIC_ZONES" ] && create_zone_dyn_chain $zone $frwd_chain else eval source_hosts=\$${zone}_ipsec_hosts [ -n "$DYNAMIC_ZONES" -a -n "$source_hosts" ] && create_zone_dyn_chain $zone $frwd_chain fi for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} run_iptables2 -A $(forward_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $frwd_chain done fi fi done for zone in $ZONES; do eval source_hosts=\$${zone}_hosts chain1=$(rules_chain $FW $zone) chain2=$(rules_chain $zone $FW) eval complex=\$${zone}_is_complex [ -n "$complex" ] && frwd_chain=${zone}_frwd echo $zone $source_hosts >> /var/lib/shorewall/zones if [ -n "$DYNAMIC_ZONES" ]; then echo "$FW $zone $chain1" >> /var/lib/shorewall/chains echo "$zone $FW $chain2" >> /var/lib/shorewall/chains fi need_broadcast= for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} [ -n "$chain1" ] && run_iptables2 -A OUTPUT -o $interface $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j $chain1 # # Add jumps from the builtin chain for DNAT rules # addrulejump PREROUTING $(dnat_chain $zone) -i $interface $(match_source_hosts $networks) $(match_ipsec_in $zone $host) [ -n "$chain2" ] && run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $chain2 if [ -n "$complex" ] && ! is_ipsec_host $zone $host ; then run_iptables2 -A $(forward_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $frwd_chain fi case $networks in *.*.*.*|+*) if [ "$networks" != 0.0.0.0/0 ]; then if ! list_search $interface $need_broadcast ; then interface_has_option $interface detectnets && need_broadcast="$need_broadcast $interface" fi fi ;; esac done if [ -n "$chain1" ]; then for interface in $need_broadcast ; do run_iptables -A OUTPUT -o $interface -d 255.255.255.255 -j $chain1 run_iptables -A OUTPUT -o $interface -d 224.0.0.0/4 -j $chain1 done fi for zone1 in $ZONES; do eval policy=\$${zone}2${zone1}_policy [ "$policy" = NONE ] && continue eval dest_hosts=\$${zone1}_hosts chain="$(rules_chain $zone $zone1)" [ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain. [ -n "$DYNAMIC_ZONES" ] && echo "$zone $zone1 $chain" >> /var/lib/shorewall/chains if [ $zone = $zone1 ]; then # # Try not to generate superfluous intra-zone rules # eval routeback=\"\$${zone}_routeback\" eval interfaces=\"\$${zone}_interfaces\" eval ports="\$${zone}_ports" num_ifaces=$(list_count1 $interfaces) # # If the zone has a single interface then what matters is how many ports it has # [ $num_ifaces -eq 1 -a -n "$ports" ] && num_ifaces=$(list_count1 $ports) # # If we don't need to route back and if we have only one interface or one port to # the zone then assume that hosts in the zone can communicate directly. # if [ $num_ifaces -lt 2 -a -z "$routeback" ] ; then continue fi else routeback= num_ifaces=0 fi if [ -n "$complex" ]; then for host1 in $dest_hosts; do interface1=${host1%%:*} networks1=${host1#*:} # # Only generate an intrazone rule if the zone has more than one interface (port) or if # routeback was specified for this host group # if [ $zone != $zone1 -o $num_ifaces -gt 1 ] || list_search $host1 $routeback ; then run_iptables2 -A $frwd_chain -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain fi done else for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} chain3=$(forward_chain $interface) for host1 in $dest_hosts; do interface1=${host1%%:*} networks1=${host1#*:} if [ "$host" != "$host1" ] || list_search $host $routeback; then run_iptables2 -A $chain3 $(match_source_hosts $networks) -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain fi done done fi done done for interface in $ALL_INTERFACES ; do run_iptables -A FORWARD -i $interface -j $(forward_chain $interface) run_iptables -A INPUT -i $interface -j $(input_chain $interface) addnatjump POSTROUTING $(masq_chain $interface) -o $interface # # Bridges under the 2.4 kernel have the wierd property that REJECTS have the physdev-in and physdev-out set to the input physdev. # To accomodate this feature/bug, we effectively set 'routeback' on bridge ports. # eval ports=\$$(chain_base $interface)_ports for port in $ports; do run_iptables -A $(forward_chain $interface) -o $interface -m physdev --physdev-in $port --physdev-out $port -j ACCEPT done done chain=${FW}2${FW} if havechain $chain; then # # There is a fw->fw chain. Send loopback output through that chain # run_ip link ls | grep LOOPBACK | while read ordinal interface rest ; do run_iptables -A OUTPUT -o ${interface%:*} -j $chain done # # And delete the unconditional ACCEPT rule # run_iptables -D OUTPUT -o lo -j ACCEPT fi complete_standard_chain INPUT all $FW complete_standard_chain OUTPUT $FW all complete_standard_chain FORWARD all all # # Remove rules added to keep the firewall alive during [re]start" # disable_critical_hosts for chain in INPUT OUTPUT FORWARD; do [ -n "$FASTACCEPT" ] || run_iptables -D $chain -m state --state ESTABLISHED,RELATED -j ACCEPT run_iptables -D $chain -p udp --dport 53 -j ACCEPT done process_routestopped -D if [ -n "$LOGALLNEW" ]; then for table in mangle nat filter; do case $table in mangle) chains="PREROUTING INPUT FORWARD POSTROUTING" ;; nat) chains="PREROUTING POSTROUTING OUTPUT" ;; *) chains="INPUT FORWARD OUTPUT" ;; esac for chain in $chains; do log_rule_limit $LOGALLNEW $chain $table $chain "" "" -I -m state --state NEW -t $table done done fi } # # Check for disabled startup # check_disabled_startup() { if [ -z "$STARTUP_ENABLED" ]; then echo " Shorewall Startup is disabled -- to enable startup" echo " after you have completed Shorewall configuration," echo " change the setting of STARTUP_ENABLED to Yes in" echo " /etc/shorewall/shorewall.conf" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 2 fi } # # Start/Restart the Firewall # define_firewall() # $1 = Command (Start or Restart) { check_disabled_startup echo "${1}ing Shorewall..." set_state "${1}ing" verify_os_version verify_ip [ -d /var/lib/shorewall ] || { mkdir -p /var/lib/shorewall ; chmod 700 /var/lib/shorewall; } RESTOREBASE=$(mktempfile /var/lib/shorewall) [ -n "$RESTOREBASE" ] || startup_error "Cannot create temporary file in /var/lib/shorewall" echo '#bin/sh' >> $RESTOREBASE save_command "#" save_command "# Restore base file generated by Shorewall $version - $(date)" save_command "#" save_command ". /usr/share/shorewall/functions" f=$(find_file params) [ -f $f ] && \ save_command ". $(resolve_file $f)" save_command "#" save_command "COMMAND=restore" save_command "MODULESDIR=\"$MODULESDIR\"" save_command "MODULE_SUFFIX=\"$MODULE_SUFFIX\"" save_load_kernel_modules echo "Initializing..."; initialize_netfilter echo "Configuring Proxy ARP"; setup_proxy_arp # # [re]-Establish routing # setup_providers $(find_file providers) [ -n "$ROUTEMARK_INTERFACES" ] && setup_routes echo "Setting up NAT..."; setup_nat echo "Setting up NETMAP..."; setup_netmap echo "Adding Common Rules"; add_common_rules setup_syn_flood_chains setup_ipsec maclist_hosts=$(find_hosts_by_option maclist) [ -n "$maclist_hosts" ] && setup_mac_lists echo "Processing $(find_file rules)..."; process_rules tunnels=$(find_file tunnels) [ -f $tunnels ] && \ echo "Processing $tunnels..." && setup_tunnels $tunnels echo "Processing Actions..."; process_actions2 process_actions3 echo "Processing $(find_file policy)..."; apply_policy_rules masq=$(find_file masq) [ -f $masq ] && setup_masq $masq tos=$(find_file tos) [ -f $tos ] && [ -n "$MANGLE_ENABLED" ] && process_tos $tos ecn=$(find_file ecn) [ -f $ecn ] && [ -n "$MANGLE_ENABLED" ] && setup_ecn $ecn [ -n "$TC_ENABLED" ] && setup_tc echo "Activating Rules..."; activate_rules [ -n "$ALIASES_TO_ADD" ] && \ echo "Adding IP Addresses..." && add_ip_aliases for file in chains nat proxyarp zones; do append_file $file done save_progress_message "Restoring Netfilter Configuration..." save_command 'iptables-restore << __EOF__' # 'shorewall save' appends the iptables-save output and '__EOF__' mv -f $RESTOREBASE /var/lib/shorewall/restore-base-$$ > $RESTOREBASE save_command "#" save_command "# Restore tail file generated by Shorewall $version - $(date)" save_command "#" save_command "date > /var/lib/shorewall/restarted" run_user_exit start [ -n "$DELAYBLACKLISTLOAD" ] && refresh_blacklist createchain shorewall no date > /var/lib/shorewall/restarted run_and_save_command set_state "Started" report "Shorewall ${1}ed" run_user_exit started rm -rf $TMP_DIR mv -f /var/lib/shorewall/restore-base-$$ /var/lib/shorewall/restore-base mv -f $RESTOREBASE /var/lib/shorewall/restore-tail } # # Refresh the firewall # refresh_firewall() { echo "Refreshing Shorewall..." echo "Determining Zones and Interfaces..." determine_zones validate_interfaces_file determine_interfaces run_user_exit refresh # # Blacklist # refresh_blacklist ecn=$(find_file ecn) [ -f $ecn ] && [ -n "$MANGLE_ENABLED" ] && setup_ecn $ecn # # Refresh Traffic Control # [ -n "$TC_ENABLED" ] && refresh_tc report "Shorewall Refreshed" rm -rf $TMP_DIR } # # Add a host or networks to a zone # add_to_zone() # $1...${n-1} = [:] $n = zone { local interface host zone z h z1 z2 chain local dhcp_interfaces blacklist_interfaces maclist_interfaces local tcpflags_interfaces newhostlist= local rulenum source_chain dest_hosts iface hosts hostlist= nat_chain_exists() # $1 = chain name { qt $IPTABLES -t nat -L $1 -n } do_iptables() # $@ = command { [ -n "$BRIDGING" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange if ! $IPTABLES $@ ; then error_message "Can't add $newhost to zone $zone" fi } # # Load $zones # determine_zones # # Validate Interfaces File # validate_interfaces_file # # Validate Hosts File # validate_hosts_file # # Validate IPSec File # f=$(find_file ipsec) [ -f $f ] && setup_ipsec $f # # Normalize host list # while [ $# -gt 1 ]; do interface=${1%%:*} host=${1#*:} # # Be sure that the interface was dynamic at last [re]start # if ! chain_exists $(input_chain $interface) ; then startup_error "Unknown interface $interface" fi if ! chain_exists $(dynamic_in $interface) ; then startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf" fi if [ -z "$host" ]; then hostlist="$hostlist $interface:0.0.0.0/0" else for h in $(separate_list $host); do hostlist="$hostlist $interface:$h" done fi shift done # # Validate Zone # zone=$1 validate_zone $zone || startup_error "Unknown zone: $zone" [ "$zone" = $FW ] && startup_error "Can't add $1 to firewall zone" # # Be sure that Shorewall has been restarted using a DZ-aware version of the code # [ -f /var/lib/shorewall/chains ] || startup_error "/var/lib/shorewall/chains -- file not found" [ -f /var/lib/shorewall/zones ] || startup_error "/var/lib/shorewall/zones -- file not found" # # Check for duplicates and create a new zone state file # > /var/lib/shorewall/zones_$$ while read z hosts; do if [ "$z" = "$zone" ]; then for h in $hostlist; do list_search $h $hosts if [ "$?" -gt 0 ]; then newhostlist="$newhostlist $h" else error_message "$h already in zone $zone" fi done [ -z "$hosts" ] && hosts=$newhostlist || hosts="$hosts $newhostlist" fi eval ${z}_hosts=\"$hosts\" echo "$z $hosts" >> /var/lib/shorewall/zones_$$ done < /var/lib/shorewall/zones mv -f /var/lib/shorewall/zones_$$ /var/lib/shorewall/zones TERMINATOR=fatal_error # # Create a new Zone state file # for newhost in $newhostlist; do # # Isolate interface and host parts # interface=${newhost%%:*} host=${newhost#*:} # # If the zone passed in the command has a dnat chain then insert a rule in # the nat table PREROUTING chain to jump to that chain when the source # matches the new host(s)# # chain=${zone}_dnat if nat_chain_exists $chain; then do_iptables -t nat -A $(dynamic_in $interface) $(source_ip_range $host) $(match_ipsec_in $zone $newhost) -j $chain fi # # Insert new rules into the filter table for the passed interface # while read z1 z2 chain; do [ "$z1" = "$z2" ] && op="-I" || op="-A" if [ "$z1" = "$zone" ]; then if [ "$z2" = "$FW" ]; then do_iptables $op $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j $chain else source_chain=$(dynamic_fwd $interface) if is_ipsec_host $z1 $newhost ; then do_iptables $op $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd else eval dest_hosts=\"\$${z2}_hosts\" for h in $dest_hosts; do iface=${h%%:*} hosts=${h#*:} if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then do_iptables $op $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain fi done fi fi elif [ "$z2" = "$zone" ]; then if [ "$z1" = "$FW" ]; then # # Add a rule to the dynamic out chain for the interface # do_iptables $op $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain else eval source_hosts=\"\$${z1}_hosts\" for h in $source_hosts; do iface=${h%%:*} hosts=${h#*:} if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then if is_ipsec_host $z1 $h; then do_iptables $op ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain else do_iptables $op $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain fi fi done fi fi done < /var/lib/shorewall/chains progress_message "$newhost added to zone $zone" done rm -rf $TMP_DIR } # # Delete a host or networks from a zone # delete_from_zone() # $1 = [:] $2 = zone { local interface host zone z h z1 z2 chain delhost local dhcp_interfaces blacklist_interfaces maclist_interfaces tcpflags_interfaces local rulenum source_chain dest_hosts iface hosts hostlist= # # Load $zones # determine_zones # # Validate Interfaces File # validate_interfaces_file # # Validate Hosts File # validate_hosts_file # # Validate IPSec File # f=$(find_file ipsec) [ -f $f ] && setup_ipsec $f # # Normalize host list # while [ $# -gt 1 ]; do interface=${1%%:*} host=${1#*:} # # Be sure that the interface was dynamic at last [re]start # if ! chain_exists $(input_chain $interface) ; then startup_error "Unknown interface $interface" fi if ! chain_exists $(dynamic_in $interface) ; then startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf" fi if [ -z "$host" ]; then hostlist="$hostlist $interface:0.0.0.0/0" else for h in $(separate_list $host); do hostlist="$hostlist $interface:$h" done fi shift done # # Validate Zone # zone=$1 validate_zone $zone || startup_error "Unknown zone: $zone" [ "$zone" = $FW ] && startup_error "Can't delete from the firewall zone" # # Be sure that Shorewall has been restarted using a DZ-aware version of the code # [ -f /var/lib/shorewall/chains ] || startup_error "/var/lib/shorewall/chains -- file not found" [ -f /var/lib/shorewall/zones ] || startup_error "/var/lib/shorewall/zones -- file not found" # # Delete the passed hosts from the zone state file # > /var/lib/shorewall/zones_$$ while read z hosts; do if [ "$z" = "$zone" ]; then temp=$hosts hosts= for host in $hostlist; do found= for h in $temp; do if [ "$h" = "$host" ]; then found=Yes break fi done [ -n "$found" ] || error_message "Warning: $host does not appear to be in zone $zone" done for h in $temp; do found= for host in $hostlist; do if [ "$h" = "$host" ]; then found=Yes break fi done [ -n "$found" ] || hosts="$hosts $h" done fi eval ${z}_hosts=\"$hosts\" echo "$z $hosts" >> /var/lib/shorewall/zones_$$ done < /var/lib/shorewall/zones mv -f /var/lib/shorewall/zones_$$ /var/lib/shorewall/zones TERMINATOR=fatal_error for delhost in $hostlist; do interface=${delhost%%:*} host=${delhost#*:} # # Delete any nat table entries for the host(s) # qt_iptables -t nat -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $zone $delhost) -j ${zone}_dnat # # Delete rules rules the input chains for the passed interface # while read z1 z2 chain; do if [ "$z1" = "$zone" ]; then if [ "$z2" = "$FW" ]; then qt_iptables -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $delhost) -j $chain else source_chain=$(dynamic_fwd $interface) if is_ipsec_host $z1 $delhost ; then qt_iptables -D $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd else eval dest_hosts=\"\$${z2}_hosts\" [ "$z2" = "$zone" ] && dest_hosts="$dest_hosts $hostlist" for h in $dest_hosts; do iface=${h%%:*} hosts=${h#*:} if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then qt_iptables -D $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain fi done fi fi elif [ "$z2" = "$zone" ]; then if [ "$z1" = "$FW" ]; then qt_iptables -D $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain else eval source_hosts=\"\$${z1}_hosts\" for h in $source_hosts; do iface=${h%%:*} hosts=${h#*:} if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then if is_ipsec_host $z1 $h; then qt_iptables -D ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain else qt_iptables -D $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain fi fi done fi fi done < /var/lib/shorewall/chains progress_message "$delhost removed from zone $zone" done rm -rf $TMP_DIR } # # Determine the value for a parameter that defaults to Yes # added_param_value_yes() # $1 = Parameter Name, $2 = Parameter value { local val="$2" if [ -z "$val" ]; then echo "Yes" else case $val in [Yy][Ee][Ss]) echo "Yes" ;; [Nn][Oo]) echo "" ;; *) startup_error "Invalid value ($val) for $1" ;; esac fi } # # Determine the value for a parameter that defaults to No # added_param_value_no() # $1 = Parameter Name, $2 = Parameter value { local val="$2" if [ -z "$val" ]; then echo "" else case $val in [Yy][Ee][Ss]) echo "Yes" ;; [Nn][Oo]) echo "" ;; *) startup_error "Invalid value ($val) for $1" ;; esac fi } # # Initialize this program # do_initialize() { # Run all utility programs using the C locale # # Thanks to Vincent Planchenault for this tip # export LC_ALL=C # Make sure umask is sane umask 177 PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin # # Establish termination function # TERMINATOR=startup_error # # Clear all configuration variables # version= IPTABLES= FW= SUBSYSLOCK= ALLOWRELATED=Yes LOGRATE= LOGBURST= LOGPARMS= LOGLIMIT= ADD_IP_ALIASES= ADD_SNAT_ALIASES= TC_ENABLED= BLACKLIST_DISPOSITION= BLACKLIST_LOGLEVEL= CLAMPMSS= ROUTE_FILTER= LOG_MARTIANS= DETECT_DNAT_IPADDRS= MUTEX_TIMEOUT= FORWARDPING= MACLIST_DISPOSITION= MACLIST_LOG_LEVEL= TCP_FLAGS_DISPOSITION= TCP_FLAGS_LOG_LEVEL= RFC1918_LOG_LEVEL= MARK_IN_FORWARD_CHAIN= SHARED_DIR=/usr/share/shorewall FUNCTIONS= VERSION_FILE= LOGFORMAT= LOGRULENUMBERS= ADMINISABSENTMINDED= BLACKLISTNEWONLY= MODULE_SUFFIX= ACTIONS= USEDACTIONS= SMURF_LOG_LEVEL= DISABLE_IPV6= BRIDGING= DYNAMIC_ZONES= PKTTYPE= USEPKTYPE= RETAIN_ALIASES= DELAYBLACKLISTLOAD= LOGTAGONLY= LOGALLNEW= RFC1918_STRICT= MACLIST_TTL= SAVE_IPSETS= RESTOREFILE= MAPOLDACTIONS= RESTOREBASE= TMP_DIR= ALL_INTERFACES= ROUTEMARK_INTERFACES= IPSECMARK=256 PROVIDERS= CRITICALHOSTS= IPSECFILE= EXCLUSION_SEQ=1 STOPPING= HAVE_MUTEX= ALIASES_TO_ADD= SECTION=ESTABLISHED SECTIONS= FUNCTIONS=$SHARED_DIR/functions if [ -f $FUNCTIONS ]; then [ -n "$QUIET" ] || echo "Loading $FUNCTIONS..." . $FUNCTIONS else startup_error "$FUNCTIONS does not exist!" fi TMP_DIR=$(mktempdir) [ -n "$TMP_DIR" ] && chmod 700 $TMP_DIR || \ startup_error "Can't create a temporary directory" trap "rm -rf $TMP_DIR; my_mutex_off; exit 2" 1 2 3 4 5 6 9 ensure_config_path VERSION_FILE=$SHARED_DIR/version [ -f $VERSION_FILE ] && version=$(cat $VERSION_FILE) run_user_exit params config=$(find_file shorewall.conf) if [ -f $config ]; then if [ -r $config ]; then [ -n "$QUIET" ] || echo "Processing $config..." . $config else startup_error "Cannot read $config (Hint: Are you root?)" fi else startup_error "$config does not exist!" fi # # Restore CONFIG_PATH if the shorewall.conf file cleared it # ensure_config_path # # Determine the capabilities of the installed iptables/netfilter # We load the kernel modules here to accurately determine # capabilities when module autoloading isn't enabled. # [ -n "${MODULE_SUFFIX:=o gz ko o.gz ko.gz}" ] load_kernel_modules if [ -z "$IPTABLES" ]; then IPTABLES=$(mywhich iptables 2> /dev/null) [ -z "$IPTABLES" ] && startup_error "Can't find iptables executable" else [ -e "$IPTABLES" ] || startup_error "\$IPTABLES=$IPTABLES does not exist or is not executable" fi PKTTYPE=$(added_param_value_no PKTTYPE $PKTTYPE) determine_capabilities [ -d /var/lib/shorewall ] || mkdir -p /var/lib/shorewall ALLOWRELATED="$(added_param_value_yes ALLOWRELATED $ALLOWRELATED)" [ -n "$ALLOWRELATED" ] || \ startup_error "ALLOWRELATED=No is not supported" ADD_IP_ALIASES="$(added_param_value_yes ADD_IP_ALIASES $ADD_IP_ALIASES)" TC_ENABLED="$(added_param_value_yes TC_ENABLED $TC_ENABLED)" if [ -n "${LOGRATE}${LOGBURST}" ]; then LOGLIMIT="--match limit" [ -n "$LOGRATE" ] && LOGLIMIT="$LOGLIMIT --limit $LOGRATE" [ -n "$LOGBURST" ] && LOGLIMIT="$LOGLIMIT --limit-burst $LOGBURST" fi if [ -n "$IP_FORWARDING" ]; then case "$IP_FORWARDING" in [Oo][Nn]|[Oo][Ff][Ff]|[Kk][Ee][Ee][Pp]) ;; *) startup_error "Invalid value ($IP_FORWARDING) for IP_FORWARDING" ;; esac else IP_FORWARDING=On fi if [ -n "$TC_ENABLED" -a -z "$MANGLE_ENABLED" ]; then startup_error "Traffic Control requires Mangle" fi [ -n "${BLACKLIST_DISPOSITION:=DROP}" ] case "$CLAMPMSS" in [0-9]*) ;; *) CLAMPMSS=$(added_param_value_no CLAMPMSS $CLAMPMSS) ;; esac ADD_SNAT_ALIASES=$(added_param_value_no ADD_SNAT_ALIASES $ADD_SNAT_ALIASES) ROUTE_FILTER=$(added_param_value_no ROUTE_FILTER $ROUTE_FILTER) LOG_MARTIANS=$(added_param_value_no LOG_MARTIANS $LOG_MARTIANS) DETECT_DNAT_IPADDRS=$(added_param_value_no DETECT_DNAT_IPADDRS $DETECT_DNAT_IPADDRS) FORWARDPING=$(added_param_value_no FORWARDPING $FORWARDPING) [ -n "$FORWARDPING" ] && \ startup_error "FORWARDPING=Yes is no longer supported" maclist_target=reject if [ -n "$MACLIST_DISPOSITION" ] ; then case $MACLIST_DISPOSITION in REJECT) ;; DROP) maclist_target=DROP ;; ACCEPT) maclist_target=RETURN ;; *) startup_error "Invalid value ($MACLIST_DISPOSITION) for MACLIST_DISPOSITION" ;; esac else MACLIST_DISPOSITION=REJECT fi if [ -n "$TCP_FLAGS_DISPOSITION" ] ; then case $TCP_FLAGS_DISPOSITION in REJECT|ACCEPT|DROP) ;; *) startup_error "Invalid value ($TCP_FLAGS_DISPOSITION) for TCP_FLAGS_DISPOSITION" ;; esac else TCP_FLAGS_DISPOSITION=DROP fi [ -n "${RFC1918_LOG_LEVEL:=info}" ] MARK_IN_FORWARD_CHAIN=$(added_param_value_no MARK_IN_FORWARD_CHAIN $MARK_IN_FORWARD_CHAIN) [ -n "$MARK_IN_FORWARD_CHAIN" ] && MARKING_CHAIN=tcfor || MARKING_CHAIN=tcpre if [ -n "$TC_ENABLED" ]; then CLEAR_TC=$(added_param_value_yes CLEAR_TC $CLEAR_TC) else CLEAR_TC= fi if [ -n "$LOGFORMAT" ]; then if [ -n "$(echo $LOGFORMAT | grep '%d')" ]; then LOGRULENUMBERS=Yes temp=$(printf "$LOGFORMAT" fooxx 1 barxx 2> /dev/null) if [ $? -ne 0 ]; then startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\"" fi else temp=$(printf "$LOGFORMAT" fooxx barxx 2> /dev/null) if [ $? -ne 0 ]; then startup_error "Invalid LOGFORMAT string: \"$LOGFORMAT\"" fi fi [ ${#temp} -le 29 ] || startup_error "LOGFORMAT string is longer than 29 characters: \"$LOGFORMAT\"" else LOGFORMAT="Shorewall:%s:%s:" fi ADMINISABSENTMINDED=$(added_param_value_no ADMINISABSENTMINDED $ADMINISABSENTMINDED) BLACKLISTNEWONLY=$(added_param_value_no BLACKLISTNEWONLY $BLACKLISTNEWONLY) DISABLE_IPV6=$(added_param_value_no DISABLE_IPV6 $DISABLE_IPV6) BRIDGING=$(added_param_value_no BRIDGING $BRIDGING) DYNAMIC_ZONES=$(added_param_value_no DYNAMIC_ZONES $DYNAMIC_ZONES) STARTUP_ENABLED=$(added_param_value_yes STARTUP_ENABLED $STARTUP_ENABLED) RETAIN_ALIASES=$(added_param_value_no RETAIN_ALIASES $RETAIN_ALIASES) DELAYBLACKLISTLOAD=$(added_param_value_no DELAYBLACKLISTLOAD $DELAYBLACKLISTLOAD) LOGTAGONLY=$(added_param_value_no LOGTAGONLY $LOGTAGONLY) RFC1918_STRICT=$(added_param_value_no RFC1918_STRICT $RFC1918_STRICT) SAVE_IPSETS=$(added_param_value_no SAVE_IPSETS $SAVE_IPSETS) MAPOLDACTIONS=$(added_param_value_yes MAPOLDACTIONS $MAPOLDACTIONS) FASTACCEPT=$(added_param_value_no FASTACCEPT $FASTACCEPT) case ${IPSECFILE:=ipsec} in ipsec|zones) ;; *) startup_error "Invalid value ($IPSECFILE) for IPSECFILE option" ;; esac [ "x${SHOREWALL_DIR}" = "x." ] && SHOREWALL_DIR="$PWD" # # Strip the files that we use often # strip_file interfaces strip_file hosts # # Check out the user's shell # [ -n "${SHOREWALL_SHELL:=/bin/sh}" ] temp=$(decodeaddr 192.168.1.1) if [ $(encodeaddr $temp) != 192.168.1.1 ]; then startup_error "Shell $SHOREWALL_SHELL is broken and may not be used with Shorewall" fi rm -f $TMP_DIR/physdev rm -f $TMP_DIR/iprange } # # Give Usage Information # usage() { echo "Usage: $0 [debug] {start|stop|reset|restart|refresh|clear|{add|delete} [:hosts] zone}}" exit 1 } # # E X E C U T I O N B E G I N S H E R E # # # Start trace if first arg is "debug" # [ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; } nolock= [ $# -gt 1 ] && [ "$1" = "nolock" ] && { nolock=Yes; shift ; } trap "my_mutex_off; exit 2" 1 2 3 4 5 6 9 COMMAND="$1" case "$COMMAND" in stop) [ $# -ne 1 ] && usage do_initialize my_mutex_on # # Don't want to do a 'stop' when startup is disabled # check_disabled_startup echo -n "Stopping Shorewall..." stop_firewall [ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK echo "done." my_mutex_off ;; start) [ $# -ne 1 ] && usage do_initialize my_mutex_on if shorewall_is_started ; then [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK echo "Shorewall Already Started" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 0; fi define_firewall "Start" && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK my_mutex_off ;; restart) [ $# -ne 1 ] && usage do_initialize my_mutex_on if shorewall_is_started; then define_firewall "Restart" else echo "Shorewall Not Currently Running" define_firewall "Start" fi [ $? -eq 0 ] && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK my_mutex_off ;; reset) [ $# -ne 1 ] && usage do_initialize my_mutex_on if ! shorewall_is_started ; then echo "Shorewall Not Started" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 2; fi $IPTABLES -Z $IPTABLES -t nat -Z $IPTABLES -t mangle -Z report "Shorewall Counters Reset" date > /var/lib/shorewall/restarted my_mutex_off ;; refresh) [ $# -ne 1 ] && usage do_initialize my_mutex_on if ! shorewall_is_started ; then echo "Shorewall Not Started" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 2; fi refresh_firewall; my_mutex_off ;; clear) [ $# -ne 1 ] && usage do_initialize my_mutex_on echo -n "Clearing Shorewall..." clear_firewall [ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK echo "done." my_mutex_off ;; check) [ $# -ne 1 ] && usage do_initialize check_config ;; add) [ $# -lt 3 ] && usage do_initialize my_mutex_on if ! shorewall_is_started ; then echo "Shorewall Not Started" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 2; fi shift add_to_zone $@ my_mutex_off ;; delete) [ $# -lt 3 ] && usage do_initialize my_mutex_on if ! shorewall_is_started ; then echo "Shorewall Not Started" [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR my_mutex_off exit 2; fi shift delete_from_zone $@ my_mutex_off ;; call) # # Undocumented way to call functions in /usr/share/shorewall/firewall directly # shift; do_initialize EMPTY= $@ ;; *) usage ;; esac