#!/bin/sh # # The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V2.2 # # 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 status Displays firewall status # 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 $STATEDIR to $RESTOREBASE # append_file() # $1 = File Name { save_command "cat > $STATEDIR/$1 << __EOF__" cat $STATEDIR/$1 >> $RESTOREBASE save_command __EOF__ } # # Run iptables and if an error occurs, stop the firewall and quit # run_iptables() { [ -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() { [ -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 } # # 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", create default rules { local c=$(chain_base $1) run_iptables -N $1 if [ $2 = yes ]; then run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT [ -z "$NEWNOTSYN" ] && \ run_iptables -A $1 -m state --state NEW -p tcp ! --syn -j newnotsyn 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 run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT [ -z "$NEWNOTSYN" ] && \ run_iptables -A $1 -m state --state NEW -p tcp ! --syn -j newnotsyn fi eval exists_${c}=Yes fi } # # Determine if a chain exists # # When we create a chain "chain", we create a variable named exists_chain 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 } # # 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 a zone or from an interface # snat_chain() # $1 = zone or 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 } # # 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 "-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 "-d $1" ;; esac } both_ip_ranges() # $1 = Source address or range, $2 = dest address or range { local prefix= match= case $1 in *.*.*.*-*.*.*.*) prefix="-m iprange" match="--src-range $1" ;; *) match="-s $1" ;; esac case $2 in *.*.*.*-*.*.*.*) prefix="-m iprange" match="$match --dst-range $2" ;; *) match="$match -d $2" ;; esac echo "$prefix $match" } # # 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 } # # Check for duplicate zone definitions # check_duplicate_zones() { local localzones= for zone in $zones; do list_search $zone $localzones && startup_error "Zone $zone is defined more than once" localzones="$localzones $zone" done } # # 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 networks in $networks; do if [ -z "$hosts" ]; then hosts=$interface:$networks else hosts="$hosts $interface:$networks" fi if interface_has_option $interface routeback; then eval ${zone}_routeback=\"$interface:$networks \$${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 eval display=\$${zone}_display display_list "$display 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|norfc1918|nobogons|tcpflags|newnotsyn|arp_filter|routefilter|logmartians|sourceroute|blacklist|proxyarp|maclist|nosmurfs|upnp|-) ;; 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 [ -z "$ALL_INTERFACES" ] && startup_error "No Interfaces Defined" done < $TMP_DIR/interfaces } # # 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%%:*} ;; *.*.*.*) ;; *) known_interface $host && \ startup_error "Bridged interfaces may not be defined in /etc/shorewall/interfaces: $host" check_bridge_port $host ;; esac fi for option in $(separate_list $options) ; do case $option in maclist|norfc1918|nobogons|blacklist|tcpflags|nosmurfs|newnotsyn|-) ;; 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= 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 startup_error "Duplicate policy $policy" fi [ "x$loglevel" = "x-" ] && loglevel= [ $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 } # # 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 which 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 which 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 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 ;; *) 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" ] && \ run_iptables $1 OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT for host1 in $hosts; do [ "$host" != "$host1" ] && run_iptables $1 FORWARD -i $interface -o ${host1%:*} $(both_ip_ranges $networks ${host1#*:}) -j ACCEPT done 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 [ -z "$RESTOREFILE" ] && RESTOREFILE=restore RESTOREPATH=/var/lib/shorewall/$RESTOREFILE if [ -x $RESTOREPATH ]; then echo Restoring Shorewall... $RESTOREPATH echo "Shorewall restored from $RESTOREPATH" my_mutex_off kill $$ exit 2 fi ;; esac stopping="Yes" terminator= deletechain shorewall run_user_exit stop [ -n "$MANGLE_ENABLED" ] && \ run_iptables -t mangle -F && \ run_iptables -t mangle -X [ -n "$NAT_ENABLED" ] && delete_nat delete_proxy_arp [ -n "$CLEAR_TC" ] && delete_tc1 [ -n "$DISABLE_IPV6" ] && disable_ipv6_1 if [ -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 hosts= [ -f $TMP_DIR/routestopped ] || strip_file routestopped 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 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 run_iptables -F echo 1 > /proc/sys/net/ipv4/ip_forward setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT if qt which 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 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_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 ;; 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 file # setup_ipsec() { local zone # # 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 eval ${zone}_is_complex=Yes eval ${zone}_ipsec${1}_options=\"${newoptions# }\" fi } strip_file ipsec $1 while read zone ipsec options in_options out_options mss; do expandv zone ipsec options in_options out_options mss [ -n "$POLICY_MATCH" ] || fatal_error "Your kernel and/or iptables does not support policy match" validate_zone1 $zone || fatal_error "Unknown zone: $zone" case $ipsec in -|No|no) ;; Yes|yes) eval ${zone}_is_ipsec=Yes eval ${zone}_is_complex=Yes ;; *) fatal_error "Invalid IPSEC column value: $ipsec" ;; esac do_options "" $options do_options "_in" $in_options do_options "_out" $out_options done < $TMP_DIR/ipsec } # # 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 [ -z "$haveroute" ]; then ensure_and_save_command 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 >> ${STATEDIR}/proxyarp progress_message " Host $address connected to $interface added to ARP on $external" } > ${STATEDIR}/proxyarp save_progress_message "Restoring Proxy ARP..." 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 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 } # # 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 $chain1 run_iptables -A $chain1 -m recent --update --name $chain -j ACCEPT run_iptables -A $chain1 -m recent --set --name $chain -j ACCEPT 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 chain=$(mac_chain $interface) chain1=$(macrecent_target $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 $chain1 else for address in $(separate_list $addresses) ; do run_iptables2 -A $chain $macpart -s $address $physdev_part -j $chain1 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 chain=$(mac_chain $interface) chain1=$(macrecent_target $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 if [ -n "$broadcast" ]; then run_iptables -A $chain -s ${address%/*} -d $broadcast -j $chain1 fi run_iptables -A $chain -s $address -d 255.255.255.255 -j $chain1 run_iptables -A $chain -s $address -d 224.0.0.0/4 -j $chain1 done if [ -n "$MACLIST_LOG_LEVEL" ]; then log_rule $MACLIST_LOG_LEVEL $chain $MACLIST_DISPOSITION 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 } # # Enable SYN flood protection on a chain # # Insert a jump rule to the protection chain from the first chain. Inserted # as the second rule and restrict the jump to SYN packets # enable_syn_flood_protection() # $1 = chain, $2 = protection chain { run_iptables -I $1 2 -p tcp --syn -j @$2 progress_message " Enabled SYN flood protection" } # # Delete existing Proxy ARP # delete_proxy_arp() { if [ -f ${STATEDIR}/proxyarp ]; then while read address interface external haveroute; do qt arp -i $external -d $address pub [ -z "$haveroute" ] && qt ip route del $address dev $interface done < ${STATEDIR}/proxyarp rm -f ${STATEDIR}/proxyarp fi [ -d ${STATEDIR} ] && touch ${STATEDIR}/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 [ -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 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 # > ${STATEDIR}/nat if [ -n "$POLICY_MATCH" ]; then policyin="-m policy --pol none --dir in" policyout="-m policy --pol none --dir out" fi [ -n "$RETAIN_ALIASES" ] || 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 ${STATEDIR}/nat ]; then while read external interface; do qt ip addr del $external dev $interface done < ${STATEDIR}/nat rm -f {$STATEDIR}/nat fi [ -d ${STATEDIR} ] && touch ${STATEDIR}/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 } # # 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 *.*.*) r="$(source_ip_range $source) " ;; ~*) r="$(mac_match $source) " ;; $FW:*) chain=tcout r="$(source_ip_range ${source%:*}) " ;; $FW) 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\"" case "$user" in *:*) r="$r-m owner" temp="${user%:*}" [ -n "$temp" ] && r="$r --uid-owner $temp " temp="${user#*:}" [ -n "$temp" ] && r="$r --gid-owner $temp " ;; *) r="$r-m owner --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) " ;; *) verify_interface $dest || fatal_error "Unknown interface $dest in rule \"$rule\"" r="${r}$(match_dest_dev $dest) " ;; esac fi if [ "x$proto" = xipp2p ]; then [ "x$port" = "x-" ] && port="ipp2p" r="${r}-p tcp -m ipp2p --${port} " else [ "x$proto" = "x-" ] && proto=all [ "x$proto" = "x" ] && proto=all [ "$proto" = "all" ] || r="${r}-p $proto " [ "x$port" = "x-" ] || r="${r}--dport $port " fi [ "x$sport" = "x-" ] || r="${r}--sport $sport " case $chain in tcpost) run_iptables2 -t mangle -A tcpost $r -j CLASSIFY --set-class $mark ;; *) run_iptables2 -t mangle -A $chain $r -j $target $mark ;; esac } 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 ;; esac fi case $mark in SAVE) target="CONNMARK --save-mark" mark= ;; SAVE/*) target="CONNMARK --save-mark --mask" mark=${mark#*/} ;; RESTORE) target="CONNMARK --restore-mark" mark= ;; RESTORE/*) target="CONNMARK --restore-mark --mask" mark=${mark#*/} ;; CONTINUE) target=RETURN mark= ;; 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 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 # run_iptables -t mangle -A FORWARD -j tcfor run_iptables -t mangle -A PREROUTING -j tcpre run_iptables -t mangle -A OUTPUT -j tcout run_iptables -t mangle -A POSTROUTING -j tcpost f=$(find_file tcstart) if [ -f $f ]; then run_user_exit tcstart save_progress_message "Restoring Traffic Control..." save_command . $(find_file tcstart) 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= 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="-s ${source#*:} $(match_source_dev ${source%:*})" ;; *.*.*.*) rule="-s $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 [ -n "$port" ] && case $port in -|any|all) ;; *) rule="$rule --dport $port" ;; esac [ -n "$sport" ] && case $sport in -|any|all) ;; *) rule="$rule --sport $sport" ;; 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" temp="${user%:*}" [ -n "$temp" ] && rule="$rule --uid-owner $temp " temp="${user#*:}" [ -n "$temp" ] && rule="$rule --gid-owner $temp " ;; *) [ "$chain" != OUTPUT ] && \ fatal_error "Invalid use of a user/group: chain is not OUTPUT but $chain" rule="$rule -m owner --uid-owner $user " ;; 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" = "x-" ] && chain=accounting [ -z "$chain" ] && 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 -A $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 check_duplicate_zones [ -z "$zones" ] && startup_error "ERROR: No Zones Defined" display_list "Zones:" $zones ipsecfile=$(find_file ipsec) [ -f $ipsecfile ] && \ echo "Validating ipsec file..." && \ setup_ipsec $ipsecfile 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 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 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 run_user_exit tcstart else setup_tc1 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() { 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 } # 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="-s $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= [ 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" ;; *) [ -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 "${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 $chain $action $logtarget "$ratelimit" "$logtag" -A $userandgroup \ $(fix_bang $proto $sports $multiport $cli $(source_ip_range $srv) $dports) fi run_iptables2 -A $chain $proto $multiport $cli $sports \ $(dest_ip_range $srv) $dports $ratelimit $userandgroup -j $target done done else if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $action $logtarget "$ratelimit" "$logtag" -A $userandgroup \ $(fix_bang $proto $sports $multiport $cli $dest_interface $dports) fi run_iptables2 -A $chain $proto $multiport $cli $dest_interface $sports \ $dports $ratelimit $userandgroup -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 case "$userspec" in !*:*) if [ "$userspec" != "!:" ]; then userandgroup="-m owner" temp="${userspec#!}" temp="${temp%:*}" [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" fi ;; *:*) if [ "$userspec" != ":" ]; then userandgroup="-m owner" temp="${userspec%:*}" [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" fi ;; !*) userandgroup="-m owner ! --uid-owner ${userspec#!}" ;; *) userandgroup="-m owner --uid-owner $userspec" ;; 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= logtag= [ $target = LOG ] && return ;; esac loglevel=${loglevel%\!} fi logtarget="$target" case $target in REJECT) target=reject ;; CONTINUE) target=RETURN ;; *) ;; 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 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 *:*:*) 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!' return ;; *'!') echo ${subordinate%%:*}:$2:$3 return ;; *) case $subordinate in *:*) echo $subordinate 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 } # # 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 dropNonSyn 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_${xaction}=\"\$requiredby_${xaction} $xtarget\" else rule="$(echo $xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec)" startup_error "Invalid TARGET in rule \"$rule\"" 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 "$PKTTYPE" ]; 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 "$PKTTYPE" ]; 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 ;; dropNonSyn) error_message "WARNING: \"dropNonSyn\" has been replaced by \"dropNotSyn\"" if [ "$COMMAND" != check ]; then [ -n "$xlevel" ] && \ log_rule_limit ${xlevel%\!} $xchain dropNonSyn DROP "" "$xtag" -A -p tcp ! --syn run_iptables -A $xchain -p tcp ! --syn -j DROP 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) case ${xaction2%%:*} in ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) # # Builtin target -- Nothing to do # ;; *) # # Not a builtin target -- 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) ;; esac expandv xclients xservers xprotocol xports xcports xratelimit xuserspec rule="$(echo $xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec)" process_action $xchain $xaction1 $xaction2 $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec 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 # 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="$(separate_list ${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 "$excludedests" ]; then chain=nonat${nonat_seq} nonat_seq=$(($nonat_seq + 1)) createnatchain $chain 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 for adr in $excludedests; do addnatrule $chain $(dest_ip_range $adr) -j RETURN done if [ -n "$loglevel" ]; then log_rule $loglevel $chain $logtarget -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 chain=$(dnat_chain $source) if [ -n "${excludezones}${excludedests}" ]; then chain=nonat${nonat_seq} nonat_seq=$(($nonat_seq + 1)) createnatchain $chain 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 for adr in $excludedests; do addnatrule $chain $(dest_ip_range $adr) -j RETURN done if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $chain $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 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= } # # Add one Filter Rule -- Helper function for the rules 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 # servport = Port the server listens on # chain = The canonical chain for this rule # ratelimit = Optional rate limiting clause # userandgroup= -m owner clause # userspec = User name # logtag = Log tag # 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 } # 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= [ 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 "$userspec" ] ; then error_message "Warning -- Rule \"$rule\" is a POLICY" error_message " -- and should be moved to the policy file" fi ;; REDIRECT) [ -n "$serv" ] && \ fatal_error "REDIRECT rules cannot specify a server IP; rule: \"$rule\"" servport=${servport:=$port} natrule=Yes ;; DNAT|SAME) [ -n "$serv" ] || \ fatal_error "$logtarget rules require a server address; rule: \"$rule\"" natrule=Yes ;; LOG) [ -z "$loglevel" ] && \ fatal_error "LOG requires log level" ;; esac if [ -n "${serv}${servport}" ]; then if [ $COMMAND != check ]; then # A specific server or server port given if [ -n "$natrule" ]; then add_nat_rule elif [ -n "$addr" -a "$addr" != "$serv" ] || [ -n "$servport" -a "$servport" != "$port" ]; then fatal_error "Only DNAT, SAME and REDIRECT rules may specify destination mapping; rule \"$rule\"" 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 $chain $logtarget "$ratelimit" "$logtag" -A -m conntrack --ctorigdst $adr \ $userandgroup $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) fi run_iptables2 -A $chain $proto $ratelimit $multiport $cli $sports \ $(dest_ip_range $srv) $dports -m conntrack --ctorigdst $adr $userandgroup -j $target done else if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A $userandgroup \ $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) fi [ -n "$nonat" ] && \ addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $(dest_ip_range $srv) $dports $ratelimit $userandgroup -j RETURN [ "$logtarget" != NONAT ] && \ run_iptables2 -A $chain $proto $multiport $cli $sports \ $(dest_ip_range $srv) $dports $ratelimit $userandgroup -j $target fi done done else if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A $userandgroup \ $(fix_bang $proto $sports $multiport $cli $dports) fi [ -n "$nonat" ] && \ addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $userandgroup -j RETURN [ "$logtarget" != NONAT ] && \ run_iptables2 -A $chain $proto $multiport $cli $sports \ $dports $ratelimit $userandgroup -j $target fi fi fi else # Destination is a simple zone [ -n "$addr" ] && fatal_error \ "An ORIGINAL DESTINATION ($addr) is only allowed in" \ " a DNAT, SAME or REDIRECT: \"$rule\"" if [ $COMMAND != check ]; then if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A $userandgroup \ $(fix_bang $proto $multiport $cli $dest_interface $sports $dports) fi if [ "$logtarget" != LOG ]; then [ -n "$nonat" ] && \ addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $userandgroup -j RETURN [ "$logtarget" != NONAT ] && \ run_iptables2 -A $chain $proto $multiport $cli $dest_interface \ $sports $dports $ratelimit $userandgroup -j $target fi fi fi } # # 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= # Function Body - isolate rate limit [ "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 if [ "$loglevel" = none ]; then [ "$target" = LOG ] && return loglevel= logtag= fi 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 case "$userspec" in !*:*) if [ "$userspec" != "!:" ]; then userandgroup="-m owner" temp="${userspec#!}" temp="${temp%:*}" [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" fi ;; *:*) if [ "$userspec" != ":" ]; then userandgroup="-m owner" temp="${userspec%:*}" [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" fi ;; !*) userandgroup="-m owner ! --uid-owner ${userspec#!}" ;; *) userandgroup="-m owner --uid-owner $userspec" ;; esac fi case $target in ACCEPT+|NONAT) 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*) target=ACCEPT address=${address:=detect} ;; REDIRECT*) target=ACCEPT address=${address:=all} if [ "x-" = "x$servers" ]; then servers=$FW else servers="$FW::$servers" fi ;; 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 if [ "$clientzone" = "${clientzone%!*}" ]; then excludezones= else excludezones="${clientzone#*!}" clientzone="${clientzone%!*}" case $logtarget in DNAT|REDIRECT|SAME) ;; *) fatal_error "Exclude list 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\"" else serverport= [ -z "$serverzone" -o -z "$servers" ] && \ fatal_error "Empty destination zone or qualifier: rule \"$rule\"" fi fi 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} 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\"" # Create the canonical chain if it doesn't already exist [ $COMMAND = check ] || ensurechain $chain # Generate Netfilter rule(s) protocol=${protocol:=all} 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 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() { local yclients yservers ysourcezone ydestzone ypolicy for yclients in $xclients; do for yservers in $xservers; do ysourcezone=${yclients%%:*} ydestzone=${yservers%%:*} if [ "${ysourcezone}" != "${ydestzone}" ] ; then eval ypolicy=\$${ysourcezone}2${ydestzone}_policy if [ "$ypolicy" != NONE ] ; then rule="$(echo $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec)" process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec fi fi done done } do_it() { expandv xprotocol xports xcports xaddress xratelimit xuserspec if [ "x$xclients" = xall ]; then xclients="$zones $FW" if [ "x$xservers" = xall ]; then xservers="$zones $FW" fi process_wildcard_rule return fi if [ "x$xservers" = xall ]; then xservers="$zones $FW" process_wildcard_rule return fi rule="$(echo $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec)" process_rule $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec } 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="$(echo $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 ;; *) if list_search ${xtarget%%:*} $ACTIONS; then if ! list_search $xtarget $USEDACTIONS; then createactionchain $xtarget USEDACTIONS="$USEDACTIONS $xtarget" fi xtarget=$(find_logactionchain $xtarget) do_it else rule="$(echo $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec)" fatal_error "Invalid Action in rule \"$rule\"" fi ;; esac done < $TMP_DIR/rules } # # 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..." run_iptables -t mangle -N pretos run_iptables -t mangle -N outtos strip_file tos $1 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 } # # Display elements of a list with leading white space # display_list() # $1 = List Title, rest of $* = list to display { [ $# -gt 1 ] && echo " $*" } # # Add policy rule ( and possibly logging rule) to the passed chain # 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 } 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. # enable_syn_flood_protection $chain $chain1 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" ] && \ enable_syn_flood_protection $chain $chain1 policy_rules $chain $policy $loglevel ;; *) # # DROP or REJECT policy -- enforce in the policy chain and # enable SYN flood protection if requested. # [ -n "$synparams" ] && \ enable_syn_flood_protection $chain $chain1 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 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} havechain $chain && { echo $chain; return; } [ "$1" = "$2" ] && { echo ACCEPT; return; } eval chain=\$${chain}_policychain [ -n "$chain" ] && { echo $chain; return; } fatal_error "No policy defined for zone $1 to zone $2" } # # 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 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" 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 !*) newchain=masq${masq_seq} createnatchain $newchain destnets=${destnets#!} for destnet in $(separate_list $destnets); do addnatrule $newchain $(dest_ip_range $destnet) -j RETURN done 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 masq_seq=$(($masq_seq + 1)) chain=$newchain destnets=0.0.0.0/0 proto= ports= policy= if [ -n "$nomasq" ]; then for addr in $(separate_list $nomasq); do addnatrule $chain $(source_ip_range $addr) -j RETURN done source="$source except $nomasq" fi ;; *) if [ -n "$nomasq" ]; then newchain=masq${masq_seq} createnatchain $newchain 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 -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 masq_seq=$(($masq_seq + 1)) chain=$newchain networks= destnets=0.0.0.0/0 proto= ports= policy= for addr in $(separate_list $nomasq); do addnatrule $chain $(source_ip_range $addr) -j RETURN done 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 for destnet in $(separate_list $destnets); do addnatrule $chain $(both_ip_ranges $network $destnet) $proto $ports $policy -j $target $addrlist done 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 for destnet in $(separate_list $destnets); do addnatrule $chain $(dest_ip_range $destnet) $proto $ports $policy -j $target $addrlist done 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" ] || 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 [ -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 } # # 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 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 progress_message " $addr added to Black List" 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 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 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 echo "$external $interface" >> ${STATEDIR}/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)" } # # Determine which optional facilities are supported by iptables/netfilter # determine_capabilities() { qt $IPTABLES -t nat -L -n && NAT_ENABLED=Yes || NAT_ENABLED= qt $IPTABLES -t mangle -L -n && MANGLE_ENABLED=Yes || MANGLE_ENABLED= CONNTRACK_MATCH= MULTIPORT= XMULTIPORT= POLICY_MATCH= PHYSDEV_MATCH= IPRANGE_MATCH= RECENT_MATCH= OWNER_MATCH= qt $IPTABLES -N fooX1234 qt $IPTABLES -A fooX1234 -m conntrack --ctorigdst 192.168.1.1 -j ACCEPT && CONNTRACK_MATCH=Yes qt $IPTABLES -A fooX1234 -p tcp -m multiport --dports 21,22 -j ACCEPT && MULTIPORT=Yes qt $IPTABLES -A fooX1234 -p tcp -m multiport --dports 21:22 -j ACCEPT && XMULTIPORT=Yes qt $IPTABLES -A fooX1234 -m policy --pol ipsec --dir in -j ACCEPT && POLICY_MATCH=Yes qt $IPTABLES -A fooX1234 -m physdev --physdev-in eth0 -j ACCEPT && PHYSDEV_MATCH=Yes qt $IPTABLES -A fooX1234 -m iprange --src-range 192.168.1.5-192.168.1.124 -j ACCEPT && IPRANGE_MATCH=Yes qt $IPTABLES -A fooX1234 -m recent --update -j ACCEPT && RECENT_MATCH=Yes qt $IPTABLES -A fooX1234 -m owner --cmd-owner foo -j ACCEPT && OWNER_MATCH=Yes if [ -n "$PKTTYPE" ]; then qt $IPTABLES -A fooX1234 -m pkttype --pkt-type broadcast -j ACCEPT || PKTTYPE= fi qt $IPTABLES -F fooX1234 qt $IPTABLES -X fooX1234 } report_capability() # $1 = Capability Description , $2 Capability Setting (if any) { local setting= [ "x$2" = "xYes" ] && setting="Available" || setting="Not available" echo " " $1: $setting } report_capabilities() { echo "Shorewall has detected the following iptables/netfilter capabilities:" report_capability "NAT" $NAT_ENABLED report_capability "Packet Mangling" $MANGLE_ENABLED report_capability "Multi-port Match" $MULTIPORT [ -n "$MULTIPORT" ] && report_capability "Extended Multi-port Match" $XMULTIPORT report_capability "Connection Tracking Match" $CONNTRACK_MATCH report_capability "Packet Type Match" $PKTTYPE report_capability "Policy Match" $POLICY_MATCH report_capability "Physdev Match" $PHYSDEV_MATCH report_capability "IP range Match" $IPRANGE_MATCH report_capability "Recent Match" $RECENT_MATCH report_capability "Owner Match" $OWNER_MATCH } # # 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 check_duplicate_zones [ -z "$zones" ] && startup_error "No Zones Defined" 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 "$CLEAR_TC" ] && delete_tc echo "Deleting user chains..." exists_INPUT=Yes exists_OUTPUT=Yes exists_FORWARD=Yes setpolicy INPUT DROP setpolicy OUTPUT DROP setpolicy FORWARD DROP deleteallchains setcontinue FORWARD setcontinue INPUT setcontinue OUTPUT 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 [ -n "$DROPINVALID" ] && \ run_iptables -A $chain -p ! icmp -m state --state INVALID -j DROP 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 if [ -z "$NEWNOTSYN" ]; then createchain newnotsyn no for host in $(find_hosts_by_option newnotsyn); do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} network=${host#*:} run_iptables -A newnotsyn -i $interface $(match_source_hosts $network) $policy -p tcp --tcp-flags ACK ACK -j ACCEPT run_iptables -A newnotsyn -i $interface $(match_source_hosts $network) $policy -p tcp --tcp-flags RST RST -j ACCEPT run_iptables -A newnotsyn -i $interface $(match_source_hosts $network) $policy -p tcp --tcp-flags FIN FIN -j ACCEPT run_iptables -A newnotsyn -i $interface $(match_source_hosts $network) $policy -j RETURN done run_user_exit newnotsyn if [ -n "$LOGNEWNOTSYN" ]; then log_rule $LOGNEWNOTSYN newnotsyn DROP fi run_iptables -A newnotsyn -j DROP fi createchain icmpdef no 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 "$PKTTYPE" ]; 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 eval is_bridge=\$$(chain_base $interface)_ports [ -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 # # Bogons # hosts="$(find_hosts_by_option nobogons)" if [ -n "$hosts" ]; then echo "Enabling Bogon Filtering" strip_file bogons createchain nobogons no createchain bogons no log_rule $BOGON_LOG_LEVEL bogons DROP run_iptables -A bogons -j DROP while read networks target; do case $target in logdrop) target=bogons ;; DROP|RETURN) ;; *) fatal_error "Invalid target ($target) for $networks" ;; esac run_iptables2 -A nobogons $(source_ip_range $networks) -j $target done < $TMP_DIR/bogons 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 $(match_source_hosts $network) -j nobogons done 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" 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" done interfaces=$(find_interfaces_by_option arp_filter) if [ -n "$interfaces" ]; 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 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 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 synparams=\$${chain}_synparams [ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams $loglevel if havechain $chain; then [ -n "$synparams" ] && \ run_iptables -I $chain 2 -p tcp --syn -j @$chain else # # The chain doesn't exist. Create the chain and add policy # rules # # We must include the ESTABLISHED and RELATED state # rule here to account for replys and reverse # related sessions associated with sessions going # in the other direction # 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 [ -n "$synparams" ] && \ [ $policy = ACCEPT -o $policy = CONTINUE ] && \ run_iptables -I $chain 2 -p tcp --syn -j @$chain 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 } # # 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 > ${STATEDIR}/chains > ${STATEDIR}/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 eval is_ipsec=\$${zone}_is_ipsec if [ -n "$is_ipsec" ]; then eval source_hosts=\$${zone}_hosts if [ -n "$DYNAMIC_ZONES" ]; then createchain ${zone}_dyn No run_iptables -A $frwd_chain -j ${zone}_dyn fi else eval source_hosts=\$${zone}_ipsec_hosts 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 >> ${STATEDIR}/zones if [ -n "$DYNAMIC_ZONES" ]; then echo "$FW $zone $chain1" >> ${STATEDIR}/chains echo "$zone $FW $chain2" >> ${STATEDIR}/chains fi need_broadcast= for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} run_iptables2 -A OUTPUT -o $interface $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j $chain1 # # Add jumps from the builtin chains for DNAT and SNAT rules # addrulejump PREROUTING $(dnat_chain $zone) -i $interface $(match_source_hosts $networks) $(match_ipsec_in $zone $host) addrulejump POSTROUTING $(snat_chain $zone) -o $interface $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) 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 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 for zone1 in $zones; do eval policy=\$${zone}2${zone1}_policy [ "$policy" = NONE ] && continue eval dest_hosts=\$${zone1}_hosts chain="$(rules_chain $zone $zone1)" [ -n "$DYNAMIC_ZONES" ] && echo "$zone $zone1 $chain" >> ${STATEDIR}/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#*:} chain1=$(forward_chain $interface) for host1 in $dest_hosts; do interface1=${host1%%:*} networks1=${host1#*:} if [ "$host" != "$host1" ] || list_search $host $routeback; then run_iptables2 -A $chain1 $(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" # for chain in INPUT OUTPUT FORWARD; do 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..." 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 ". $f" save_command "#" 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 echo "Setting up NAT..."; setup_nat echo "Setting up NETMAP..."; setup_netmap echo "Adding Common Rules"; add_common_rules tunnels=$(find_file tunnels) [ -f $tunnels ] && \ echo "Processing $tunnels..." && setup_tunnels $tunnels ipsecfile=$(find_file ipsec) [ -f $ipsecfile ] && \ echo "Processing $ipsecfile..." && setup_ipsec $ipsecfile maclist_hosts=$(find_hosts_by_option maclist) [ -n "$maclist_hosts" ] && setup_mac_lists echo "Processing $(find_file rules)..."; process_rules 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 > $STATEDIR/restarted" run_user_exit start [ -n "$DELAYBLACKLISTLOAD" ] && refresh_blacklist createchain shorewall no date > $STATEDIR/restarted report "Shorewall ${1}ed" rm -rf $TMP_DIR mv -f /var/lib/shorewall/restore-base-$$ /var/lib/shorewall/restore-base mv -f $RESTOREBASE /var/lib/shorewall/restore-tail run_user_exit started } # # Refresh the firewall # refresh_firewall() { echo "Refreshing Shorewall..." echo "Determining Zones and Interfaces..." determine_zones validate_interfaces_file [ -z "$zones" ] && startup_error "No Zones Defined" 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 tcpflags_interfaces 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 ${STATEDIR}/chains ] || startup_error "${STATEDIR}/chains -- file not found" [ -f ${STATEDIR}/zones ] || startup_error "${STATEDIR}/zones -- file not found" # # Check for duplicates and create a new zone state file # > ${STATEDIR}/zones_$$ while read z hosts; do if [ "$z" = "$zone" ]; then for h in $hosts; do for host in $hostlist; do if [ "$h" = "$host" ]; then rm -f ${STATEDIR}/zones_$$ startup_error "$host already in zone $zone" fi done done [ -z "$hosts" ] && hosts=$hostlist || hosts="$hosts $hostlist" fi eval ${z}_hosts=\"$hosts\" echo "$z $hosts" >> ${STATEDIR}/zones_$$ done < ${STATEDIR}/zones mv -f ${STATEDIR}/zones_$$ ${STATEDIR}/zones terminator=fatal_error # # Create a new Zone state file # for newhost in $hostlist; 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 if [ "$z1" = "$zone" ]; then if [ "$z2" = "$FW" ]; then do_iptables -A $(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 -A $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 -A $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 -A $(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 -A ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain else do_iptables -A $(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 < ${STATEDIR}/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 ${STATEDIR}/chains ] || startup_error "${STATEDIR}/chains -- file not found" [ -f ${STATEDIR}/zones ] || startup_error "${STATEDIR}/zones -- file not found" # # Delete the passed hosts from the zone state file # > ${STATEDIR}/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: $1 does not appear to be in zone $2" 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" >> ${STATEDIR}/zones_$$ done < ${STATEDIR}/zones mv -f ${STATEDIR}/zones_$$ ${STATEDIR}/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 < ${STATEDIR}/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= STATEDIR= 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= NEWNOTSYN= LOGNEWNOTSYN= FORWARDPING= MACLIST_DISPOSITION= MACLIST_LOG_LEVEL= TCP_FLAGS_DISPOSITION= TCP_FLAGS_LOG_LEVEL= RFC1918_LOG_LEVEL= BOGON_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= RETAIN_ALIASES= DELAYBLACKLISTLOAD= LOGTAGONLY= LOGALLNEW= DROPINVALID= RFC1918_STRICT= MACLIST_TTL= RESTOREBASE= TMP_DIR= ALL_INTERFACES= stopping= have_mutex= masq_seq=1 nonat_seq=1 aliases_to_add= 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 echo " ERROR: Cannot read $config (Hint: Are you root?)" exit 2 fi else echo "$config does not exist!" >&2 exit 2 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" ] || MODULE_SUFFIX="o gz ko o.gz ko.gz" load_kernel_modules if [ -z "$IPTABLES" ]; then IPTABLES=$(which 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 determine_capabilities [ -z "${STATEDIR}" ] && STATEDIR=/var/state/shorewall [ -d $STATEDIR ] || mkdir -p $STATEDIR [ -z "$FW" ] && FW=fw 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 [ -z "$BLACKLIST_DISPOSITION" ] && 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" NEWNOTSYN=$(added_param_value_yes NEWNOTSYN $NEWNOTSYN) maclist_target=reject if [ -n "$MACLIST_DISPOSITION" ] ; then case $MACLIST_DISPOSITION in REJECT) ;; ACCEPT|DROP) maclist_target=$MACLIST_DISPOSITION ;; *) 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 [ -z "$RFC1918_LOG_LEVEL" ] && RFC1918_LOG_LEVEL=info [ -z "$BOGON_LOG_LEVEL" ] && BOGON_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) PKTTYPE=$(added_param_value_no PKTTYPE $PKTTYPE) 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) DROPINVALID=$(added_param_value_yes DROPINVALID $DROPINVALID) RFC1918_STRICT=$(added_param_value_no RFC1918_STRICT $RFC1918_STRICT) # # Strip the files that we use often # strip_file interfaces strip_file hosts # # Check out the user's shell # [ -n "$SHOREWALL_SHELL" ] || 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|status|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 qt $IPTABLES -L shorewall -n ; 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 qt $IPTABLES -L shorewall -n ; then define_firewall "Restart" else echo "Shorewall Not Currently Running" define_firewall "Start" fi [ $? -eq 0 ] && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK my_mutex_off ;; status) [ $# -ne 1 ] && usage echo "Shorewall-$version Status at $HOSTNAME - $(date)" echo $IPTABLES -L -n -v ;; reset) [ $# -ne 1 ] && usage do_initialize my_mutex_on if ! qt $IPTABLES -L shorewall -n ; 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 > $STATEDIR/restarted my_mutex_off ;; refresh) [ $# -ne 1 ] && usage do_initialize my_mutex_on if ! qt $IPTABLES -L shorewall -n ; 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 ! qt $IPTABLES -L shorewall -n ; 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 ! qt $IPTABLES -L shorewall -n ; 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= $@ ;; capabilities) do_initialize report_capabilities ;; *) usage ;; esac