#!/bin/sh # # The Shoreline Firewall (Shorewall) Packet Filtering Firewall Compiler - V4.0 # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # This program is free software; you can redistribute it and/or modify # it under the terms of Version 2 of the GNU General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # If an error occurs while starting or restarting the firewall, the # firewall is automatically stopped. # # Commands are: # # compile check Verify the configuration files. # compile compile Compile into # # Environmental Variables: # # EXPORT=Yes -e option specified to /sbin/shorewall # SHOREWALL_DIR A directory name was passed to /sbin/shorewall # VERBOSE Standard Shorewall verbosity control. BASE_VERSION=40000 BASE_VERSION_PRINTABLE=4.0.0 CONFIG_VERSION=40000 CONFIG_VERSION_PRINTABLE=4.0.0 # # Fatal error -- stops the compiler after issuing the error message # fatal_error() # $* = Error Message { echo " ERROR: $@" >&2 [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR [ -n "$OUTPUT" ] && rm -f $OUTPUT kill $$ exit 2 } # # We include this for compatibility with the 'firewall' script. That script # distinguishes between Fatal Errors (stop or restore required) and Startup # Errors (errors detected before the firewall state has been changed. This # allows us to use common parsing routines in both programs. # startup_error() { echo " ERROR: $@" >&2 [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR [ -n "$OUTPUT" ] && rm -f $OUTPUT kill $$ exit 2 } # # Write the passed args to the compiler output file. # save_command() { [ $# -gt 0 ] && echo "${INDENT}${@}" >&3 || echo >&3 } save_command_unindented() { echo "${@}" >&3 } # # Write a progress_message2 command to the output file. # save_progress_message() { echo >&3 echo "${INDENT}progress_message2 \"$@\"" >&3 echo >&3 } save_progress_message_short() { echo "${INDENT}progress_message \"$@\"" >&3 } progress_message_and_save() { progress_message "$@" echo "${INDENT}progress_message \"$@\"" >&3 } # # Echo the contents of the passed file indented by $INDENT # indent() { if [ -n "$INDENT" ]; then eval sed \'s\/^/"$INDENT"\/\' $1 else cat $1 fi } # # Echo the contents of the passed file indented by $INDENT while handling line # continuation # indent1() { if [ -n "$INDENT" ]; then if [ -n "$HAVEAWK" ]; then eval awk \''BEGIN { indent=1; }; /^[[:space:]]*$/ { print ""; indent=1; next; }; { if (indent == 1) print "'"$INDENT"'" $0; else print; }; { indent=1; }; /\\$/ { indent=0; };'\' $1 else eval sed \'s\/^/"$INDENT"\/\' $1 fi else cat $1 fi } # # Append a file to the compiler's output with indentation. # append_file() # $1 = File Name { local user_exit user_exit=$(find_file $1) case $user_exit in $SHAREDIR/*) # # Don't copy files from /usr/share/shorewall into the compiled script # ;; *) if [ -f $user_exit ]; then save_progress_message "Processing $user_exit ..." indent1 $user_exit >&3 save_command fi ;; esac } # # Generate a command to run iptables # do_iptables() { save_command \$IPTABLES $@ } # # Generate an IPTABLES command. Include hacks to work around iptables limitations # run_iptables() { if [ -z "$KLUDGEFREE" ]; then # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi save_command "$IPTABLES_COMMAND $@" } # # Version of 'run_iptables' that inserts white space after "!" in the arg list # run_iptables2() { if [ -z "$KLUDGEFREE" ]; then # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi save_command run_iptables $(fix_bang $@) } # # Generate command to quietly run iptables # qt_iptables() { if [ -z "$KLUDGEFREE" ]; then # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi save_command qt \$IPTABLES $@ } # # Generate a command to run tc # run_tc() { save_command run_tc $@ } # # Add the implicit ACCEPT rules at the end of a rules file section # finish_chain_section() # $1 = canonical chain $2 = state list { local policy local policychain [ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state $2 -j ACCEPT if list_search RELATED $(separate_list $2) ; then if is_policy_chain $1 ; then if eval test -n \"\$${1}_synparams\" ; then if [ $SECTION = DONE ]; then eval policy=\$${1}_policy case $policy in ACCEPT|CONTINUE|QUEUE) run_iptables -A $1 -p tcp --syn -j @$1 ;; esac else run_iptables -A $1 -p tcp --syn -j @$1 fi fi else eval policychain=\$${1}_policychain if eval test -n \"\$${policychain}_synparams\" ; then run_iptables -A $1 -p tcp --syn -j @$policychain fi fi fi } finish_section() # $1 = Section(s) { local zone local zone1 local chain for zone in $ZONES $FW; do for zone1 in $ZONES $FW; do chain=${zone}2${zone1} if havechain $chain; then finish_chain_section $chain $1 fi done done } # # Create a filter chain # # If the chain isn't one of the common chains then add a rule to the chain # allowing packets that are part of an established connection. Create a # variable exists_${1} and set its value to Yes to indicate that the chain now # exists. # createchain() # $1 = chain name, $2 = If "yes", do section-end processing { local c c=$(chain_base $1) run_iptables -N $1 if [ $2 = yes ]; then case $SECTION in NEW|DONE) finish_chain_section $1 ESTABLISHED,RELATED ;; RELATED) finish_chain_section $1 ESTABLISHED ;; esac fi eval exists_${c}=Yes } # # This version creates the chain if it doesn't already exist # createchain2() # $1 = chain name, $2 = If "yes", create default rules { local c c=$(chain_base $1) ensurechain $1 if [ $2 = yes ]; then case $SECTION in NEW|DONE) finish_chain_section $1 ESTABLISHED,RELATED ;; RELATED) finish_chain_section $1 ESTABLISHED ;; esac fi eval exists_${c}=Yes } # # Determine if a chain exists # # When we create a chain "x", we create a variable named exists_x and # set its value to Yes. This function tests for the "exists_" variable # corresponding to the passed chain having the value of "Yes". # havechain() # $1 = name of chain { local c c=$(chain_base $1) eval test \"\$exists_${c}\" = Yes } # # 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 mangle chain # # Create a variable exists_mangle_${1} and set its value to Yes to indicate that # the chain now exists. # createmanglechain() # $1 = chain name { run_iptables -t mangle -N $1 eval exists_mangle_${1}=Yes } # # Determine if a mangle 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". # havemanglechain() # $1 = name of chain { eval test \"\$exists_mangle_${1}\" = Yes } # # Ensure that a mangle chain exists (create it if it doesn't) # ensuremanglechain() # $1 = chain name { havemanglechain $1 || createmanglechain $1 } # # 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 $@ } # # Create a rule to delete a chain if it exists # deletechain() # $1 = name of chain { save_command "qt \$IPTABLES -L $1 -n && qt \$IPTABLES -F $1 && qt \$IPTABLES -X $1" } # # validate the policy file # validate_policy() { local clientwild local serverwild local zone local zone1 local pc local chain local policy local loglevel local synparams local parents local default local var print_policy() # $1 = source zone, $2 = destination zone { [ $1 = $2 ] || \ [ $1 = all ] || \ [ $2 = all ] || \ progress_message " Policy for $1 to $2 is $policy using chain $chain" } for var in DROP_DEFAULT REJECT_DEFAULT ACCEPT_DEFAULT QUEUE_DEFAULT; do eval default=\$$var case $default in none) ;; *) if ! list_search $default $USEDACTIONS; then if ! list_search $default $DEFAULT_MACROS; then if [ ! -f $(find_file macro.$default) ]; then fatal_error "Default Action/Macro $var=$default not found" fi DEFAULT_MACROS="$DEFAULT_MACROS $default" fi fi esac done ALL_POLICY_CHAINS= for zone in $ZONES $FW; do chain=${zone}2${zone} eval ${chain}_is_policy=Yes eval ${chain}_is_optional=Yes eval ${chain}_policy=ACCEPT eval ${chain}_policychain=$chain ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain" if [ -n "$IMPLICIT_CONTINUE" ]; then eval parents=\$${zone}_parents if [ -n "$parents" ]; then for zone1 in $ZONES $FW; do if [ $zone != $zone1 ]; then chain=${zone}2${zone1} eval ${chain}_is_policy=Yes eval ${chain}_is_optional=Yes eval ${chain}_policy=CONTINUE eval ${chain}_policychain=$chain ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain" chain=${zone1}2${zone} eval ${chain}_is_policy=Yes eval ${chain}_is_optional=Yes eval ${chain}_policy=CONTINUE eval ${chain}_policychain=$chain ALL_POLICY_CHAINS="$ALL_POLICY_CHAINS $chain" fi done fi fi done while read client server policy loglevel synparams; do clientwild= serverwild= case "$client" in all|ALL) clientwild=Yes ;; *) if ! validate_zone $client; then fatal_error "Undefined zone $client" fi esac case "$server" in all|ALL) serverwild=Yes ;; *) if ! validate_zone $server; then fatal_error "Undefined zone $server" fi esac default= case $policy in *:None|*:none) default=none ;; *:*) default=${policy#*:} if list_search $default $ACTIONS; then if ! list_search $default $USEDACTIONS; then USEDACTIONS="$USEDACTIONS $default" fi elif ! list_search $default $DEFAULT_MACROS; then [ -f $(find_file macro.${default}) ] || fatal_error "$client $server $policy $loglevel $synparams: Default Macro $default not found" DEFAULT_MACROS="$DEFAULT_MACROS $default" fi ;; *) ;; esac case ${policy%:*} in DROP) [ -n "${default:=$DROP_DEFAULT}" ] ;; REJECT) [ -n "${default:=$REJECT_DEFAULT}" ] ;; ACCEPT) [ -n "${default:=$ACCEPT_DEFAULT}" ] ;; CONTINUE) ;; QUEUE) [ -n "${default:=$QUEUE_DEFAULT}" ] ;; NONE) [ "$client" = "$FW" -o "$server" = "$FW" ] && \ fatal_error " $client $server $policy $loglevel $synparams: NONE policy not allowed to/from the $FW zone" [ -n "$clientwild" -o -n "$serverwild" ] && \ fatal_error " $client $server $policy $loglevel $synparams: NONE policy not allowed with \"all\"" ;; *) fatal_error "Invalid policy $policy" ;; esac chain=${client}2${server} if is_policy_chain $chain ; then if eval test -n \"\$${chain}_is_optional\" ; then eval ${chain}_is_optional= else fatal_error "Duplicate policy: $client $server $policy" fi fi [ "x$loglevel" = "x-" ] && loglevel= [ "x$synparams" = "x-" ] && synparams= policy=${policy%:*} [ $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 eval ${chain}_default=$default 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 -- if we are compiling a script and 'detect' is specified for an interface # the function returns nothing for that interface # find_broadcasts() { for interface in $ALL_INTERFACES; do eval bcast=\$$(chain_base $interface)_broadcast if [ "x$bcast" != "xdetect" -a "x${bcast}" != "x-" ]; then echo $(separate_list $bcast) fi done } # # Find interfaces with BROADCAST=detect # find_bcastdetect_interfaces() { for interface in $ALL_INTERFACES; do eval bcast=\$$(chain_base $interface)_broadcast [ "x$bcast" = "xdetect" ] && echo $interface done } # # Set /proc/sys/net/ipv4/ip_forward based on $IP_FORWARDING # setup_forwarding() { progress_message2 "Compiling IP Forwarding..." case "$IP_FORWARDING" in On|on|ON|Yes|yes|YES) save_progress_message "IP Forwarding Enabled" save_command "echo 1 > /proc/sys/net/ipv4/ip_forward" save_command "" ;; Off|off|OFF|No|no|NO) save_progress_message "IP Forwarding Disabled!" save_command "echo 0 > /proc/sys/net/ipv4/ip_forward" save_command "" ;; esac } # # For each entry in the CRITICALHOSTS global list, add INPUT and OUTPUT rules to # enable traffic to/from those hosts. # enable_critical_hosts() { for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} do_iptables -A INPUT -i $interface $(source_ip_range $networks) -j ACCEPT do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done } # # For each entry in the CRITICALHOSTS global list, delete the INPUT and OUTPUT rules that # enable traffic to/from those hosts. # disable_critical_hosts() { for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} do_iptables -D INPUT -i $interface $(source_ip_range $networks) -j ACCEPT do_iptables -D OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done } # # Logging Rules # 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 level=$1 local chain chain=$2 local displayChain displayChain=$3 local disposition disposition=$4 local rulenum rulenum= local limit limit= local tag tag=$6 local command command=${7:--A} local prefix local base base=$(chain_base $displayChain) limit="${5:-$LOGLIMIT}" # Do this here rather than in the declaration above to appease /bin/ash. shift 7 save_command "do_log_rule_limit \"$level\" \"$chain\" \"$displayChain\" \"$disposition\" \"$limit\" \"$tag\" \"$command\" $(fix_bang $@)" } log_rule() # $1 = log level, $2 = chain, $3 = disposition , $... = predicates for the rule { local level level=$1 local chain chain=$2 local disposition disposition=$3 shift 3 log_rule_limit $level $chain $chain $disposition "$LOGLIMIT" "" -A $@ } # # Set up SYN flood protection # setup_syn_flood_chain () # $1 = policy chain # $2 = synparams # $3 = loglevel { local chain chain=@$1 local limit limit=$2 local limit_burst limit_burst= case $limit in *:*) limit_burst="--limit-burst ${limit#*:}" limit=${limit%:*} ;; esac if ! havechain $chain ; then createchain $chain no 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 fi } setup_syn_flood_chains() { for chain in $ALL_POLICY_CHAINS; do eval loglevel=\$${chain}_loglevel eval synparams=\$${chain}_synparams [ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams $loglevel done } # # Delete existing Proxy ARP # delete_proxy_arp() { indent >&3 << __EOF__ if [ -s \${VARDIR}/proxyarp ]; then while read address interface external haveroute; do qt arp -i \$external -d \$address pub [ -z "\$haveroute" -a -z "\$NOROUTE" ] && qt ip route del \$address dev \$interface done < \${VARDIR}/proxyarp for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/proxy_arp ] && echo 0 > \$f/proxy_arp done fi rm -f \${VARDIR}/proxyarp __EOF__ [ -d $STATEDIR ] && touch $STATEDIR/proxyarp } # # Delete existing Static NAT # delete_nat() { run_iptables -t nat -F run_iptables -t nat -X [ -d $STATEDIR ] && touch $STATEDIR/nat indent >&3 << __EOF__ if [ -f \${VARDIR}/nat ]; then while read external interface; do del_ip_addr \$external \$interface done < \${VARDIR}/nat rm -f \${VARDIR}/nat fi __EOF__ } # # Setup ECN disabling rules # setup_ecn() # $1 = file name { local interfaces interfaces="" local hosts hosts= local h if [ -s ${TMP_DIR}/ecn ]; then save_progress_message "Setting up ECN..." progress_message2 "$DOING $1..." while read interface host; do list_search $interface $ALL_INTERFACES || \ fatal_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 "$DOING ECN control on${interfaces}..." for interface in $interfaces; do chain=$(ecn_chain $interface) if havemanglechain $chain; then flushmangle $chain else createmanglechain $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_and_save " ECN Disabled to $h through $interface" done fi fi } # # Set up an exclusion chain # build_exclusion_chain() # $1 = variable to store chain name into $2 = table, $3 = SOURCE exclusion list, $4 = DESTINATION exclusion list { local c c=excl_${EXCLUSION_SEQ} local net EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 )) run_iptables -t $2 -N $c for net in $(separate_list $3); do run_iptables -t $2 -A $c $(source_ip_range $net) -j RETURN done for net in $(separate_list $4); do run_iptables -t $2 -A $c $(dest_ip_range $net) -j RETURN done case $2 in filter) eval exists_${c}=Yes ;; nat) eval exists_nat_${c}=Yes ;; esac eval $1=$c } # # Setup queuing and classes # setup_tc1() { local mark_part mark_part= local comment comment= # # Create the TC mangle chains # createmanglechain tcpre if [ -n "$MANGLE_FORWARD" ]; then createmanglechain tcfor createmanglechain tcpost fi createmanglechain tcout # # Process the TC Rules File # if [ -s $TMP_DIR/tcrules ]; then save_progress_message "Setting up TC Rules..." save_command setup_tc_rules save_command fi # # Just in case the file ended with a comment # if [ -n "$COMMENTS" ]; then save_command save_command COMMENT= save_command fi # # Link to the TC mangle chains from the main chains # # # Route marks are restored in PREROUTING/OUTPUT prior to these rules. We only send # packets that are not part of a marked connection to the 'tcpre/tcout' chains. # if [ -n "$ROUTEMARK_INTERFACES" -a -z "$TC_EXPERT" ]; then [ -n "$HIGH_ROUTE_MARKS" ] && mark_part="-m mark --mark 0/0xFF00" || mark_part="-m mark --mark 0/0xFF" # # But let marks in tcpre override those assigned by 'track' # for interface in $ROUTEMARK_INTERFACES; do run_iptables -t mangle -A PREROUTING -i $interface -j tcpre done fi run_iptables -t mangle -A PREROUTING $mark_part -j tcpre run_iptables -t mangle -A OUTPUT $mark_part -j tcout if [ -n "$MANGLE_FORWARD" ]; then run_iptables -t mangle -A FORWARD -j tcfor run_iptables -t mangle -A POSTROUTING -j tcpost fi if [ -n "$HIGH_ROUTE_MARKS" ]; then for chain in INPUT FORWARD POSTROUTING; do run_iptables -t mangle -I $chain -j MARK --and-mark 0xFF done fi if [ -n "$TC_SCRIPT" ]; then save_progress_message "Setting up Traffic Control..." append_file $TC_SCRIPT elif [ "$TC_ENABLED" = Internal ]; then if [ -n "$LIB_tc_LOADED" ]; then save_command save_command setup_traffic_shaping save_command fi fi } setup_tc() { progress_message2 "$DOING Traffic Control Rules..." setup_tc1 } # # Clear Traffic Shaping # delete_tc() { save_progress_message "Clearing Traffic Control/QOS" append_file tcclear indent >&3 << __EOF__ ip link list | while read inx interface details; do case \$inx in [0-9]*) qt tc qdisc del dev \${interface%:} root qt tc qdisc del dev \${interface%:} ingress ;; *) ;; esac done __EOF__ } # # Refresh queuing and classes # refresh_tc() { local comment comment= if [ -n "$CLEAR_TC" ]; then delete_tc save_command fi [ -n "$MARK_IN_FORWARD_CHAIN" ] && chain=tcfor || chain=tcpre # # Flush the TC mangle chains # if [ -n "$MANGLE_FORWARD" ]; then run_iptables -t mangle -F tcfor run_iptables -t mangle -F tcpost fi run_iptables -t mangle -F tcpre run_iptables -t mangle -F tcout # # Remove all exclusion chains from the mangle table # indent >&3 << __EOF__ \$IPTABLES -t mangle -L -n | grep '^Chain excl_' | while read junk chain rest; do run_iptables -t mangle -F \$chain run_iptables -t mangle -X \$chain done __EOF__ # # Process the TC Rules File # if [ -s $TMP_DIR/tcrules ]; then save_progress_message "Refreshing Traffic Control Rules..." save_command setup_tc_rules save_command fi # # Just in case the file ended with a comment # if [ -n "$COMMENTS" ]; then save_command save_command COMMENT= save_command fi if [ -n "$TC_SCRIPT" ]; then save_progress_message "Refreshing Traffic Shaping" run_user_exit $TC_SCRIPT elif [ "$TC_ENABLED" = Internal -a -n "$LIB_tc_LOADED" ]; then save_command save_command setup_traffic_shaping save_command fi } # # Compile refresh of the firewall # compile_refresh_firewall() { local INDENT INDENT="" local DOING DOING="Compiling Refresh of" local DONE DONE="Compiled" local indent save_command "refresh_firewall()" save_command "{" INDENT=" " append_file refresh # # Blacklist # save_command "if chain_exists blacklst; then" indent="$INDENT" INDENT="$INDENT " save_command progress_message2 \"Refreshing Black List...\" run_iptables -F blacklst [ -s ${TMP_DIR}/blacklist ] && save_command load_blacklist INDENT="$indent" save_command "fi" ecn=$(find_file ecn) if [ -f $ecn ] && [ -n "$MANGLE_ENABLED" ]; then setup_ecn $ecn fi # # Refresh Traffic Control # [ -n "$MANGLE_ENABLED" ] && refresh_tc append_file refreshed save_command "[ \$0 = \${VARDIR}/.restore ] || cp -f \$(my_pathname) \${VARDIR}/.restore" INDENT="" save_command "}" save_command } # # Source the extension script for an action, if any # process_action_file() # $1 = File Name { if ! list_search $1 $BUILTIN_ACTIONS; then local user_exit user_exit=$(find_file $1) if [ -f $user_exit ]; then progress_message "Processing $user_exit ..." . $user_exit fi 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 actchain= local action action=$1 local level level=$2 eval actchain=\${${action}_actchain} case ${#action} in 29|30) CHAIN=$(echo $action | truncate 28) # %...n makes 30 ;; *) CHAIN=${action} ;; esac 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)) createchain $CHAIN No LEVEL=${level%:*} if [ "$LEVEL" != "$level" ]; then TAG=${level#*:} else TAG= fi [ none = "${LEVEL%\!}" ] && LEVEL= process_action_file $1 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 { create_simple_chain() { CHAIN=$1 LEVEL= TAG= createchain $CHAIN no process_action_file $CHAIN } case $1 in *::*) fatal_error "Invalid ACTION $1" ;; *:*:*) set -- $(split $1) createlogactionchain $1 $2:$3 ;; *:) create_simple_chain ${1%:*} ;; *:*) set -- $(split $1) if [ "x$2" = xnone ]; then create_simple_chain $1 else createlogactionchain $1 $2 fi ;; *) create_simple_chain $1 ;; 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 fullaction=$1 local action action=${1%%:*} local level level= local chains chains= find_simpleaction() { havechain $action || fatal_error "Fatal error in find_logactionchain" echo $action } case $fullaction in *:) find_simpleaction return ;; *:*) level=${fullaction#*:} if [ "x$level" = xnone ]; then find_simpleaction return fi ;; *) find_simpleaction return ;; esac eval chains="\$${action}_chains" set -- $chains while [ $# -gt 0 ]; do [ "$1" = "$level" ] && { echo $2 ; return ; } shift 2 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 superior=$1 local subordinate subordinate=$2 set -- $(split $1) case $superior in *:*:*) case $2 in 'none!') echo ${subordinate%%:*}:'none!':$3 return ;; *'!') echo ${subordinate%%:*}:$2:$3 return ;; *) case $subordinate in *:*:*) echo $subordinate return ;; *:*) echo $subordinate:$3 return ;; *) echo ${subordinate%%:*}:$2:$3 return ;; esac ;; esac ;; *:*) case $2 in 'none!') echo ${subordinate%%:*}:'none!' return ;; *'!') echo ${subordinate%%:*}:$2 return ;; *) case $subordinate in *:*) echo $subordinate return ;; *) echo ${subordinate%%:*}:$2 return ;; esac ;; esac ;; *) echo $subordinate ;; esac } # # Define the builtin actions. They are available even when USE_ACTIONS=No # define_builtin_actions() { ACTIONS="dropBcast allowBcast dropNotSyn rejNotSyn dropInvalid allowInvalid allowinUPnP allowoutUPnP forwardUPnP Limit" BUILTIN_ACTIONS="$ACTIONS" USEDACTIONS= } # # This function maps old action names into their new macro equivalents # map_old_action() # $1 = Potential Old Action { local macro macro= local aktion if [ -n "$MAPOLDACTIONS" ]; then case $1 in */*) echo $1 return ;; *) if [ -f $(find_file macro.$1) ]; then echo $1 return fi case $1 in Allow*) macro=${1#*w} aktion=ACCEPT ;; Drop*) macro=${1#*p} aktion=DROP ;; Reject*) macro=${1#*t} aktion=REJECT ;; *) echo $1 return ;; esac esac if [ -f $(find_file macro.$macro) ]; then echo $macro/$aktion return fi fi echo $1 } # This function substitutes the second argument for the first part of the first argument up to the first colon (":") # # Example: # # substitute_action DNAT PARAM:info:FTP # # produces "DNAT:info:FTP" # substitute_action() # $1 = parameter, $2 = action { local logpart logpart=${2#*:} case $2 in *:*) echo $1:${logpart%/} ;; *) echo $1 ;; esac } # # Third phase of action processing. It needs to be here in the compiler because # it handles builtin actions. # 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 case $xlevel in none|none'!') ylevel= ;; *) ylevel=$xlevel; ;; esac save_progress_message "Creating action chain $xaction1" # # Handle Builtin actions # case $xaction1 in dropBcast) if [ -n "$USEPKTTYPE" ]; then if [ -n "$ylevel" ]; then log_rule_limit ${ylevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type broadcast log_rule_limit ${ylevel%\!} $xchain dropBcast DROP "" "$xtag" -A -m pkttype --pkt-type multicast fi run_iptables -A dropBcast -m pkttype --pkt-type broadcast -j DROP run_iptables -A dropBcast -m pkttype --pkt-type multicast -j DROP else for interface in $(find_bcastdetect_interfaces); do indent >&3 << __EOF__ ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do __EOF__ [ -n "$ylevel" ] && indent >&3 << __EOF__ log_rule_limit ${ylevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d \$address __EOF__ indent >&3 << __EOF__ run_iptables -A $xchain -d \$address -j DROP done __EOF__ done for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do [ -n "$ylevel" ] && log_rule_limit ${ylevel%\!} $xchain dropBcast DROP "" "$xtag" -A -d $address run_iptables -A $xchain -d $address -j DROP done fi ;; allowBcast) if [ -n "$USEPKTTYPE" ]; then if [ -n "$ylevel" ]; then log_rule_limit ${ylevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type broadcast log_rule_limit ${ylevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -m pkttype --pkt-type multicast fi run_iptables -A allowBcast -m pkttype --pkt-type broadcast -j ACCEPT run_iptables -A allowBcast -m pkttype --pkt-type multicast -j ACCEPT else for interface in $(find_bcastdetect_interfaces); do indent >&3 << __EOF__ ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do __EOF__ [ -n "$ylevel" ] && indent >&3 << __EOF__ log_rule_limit ${ylevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -d \$address __EOF__ indent >&3 << __EOF__ run_iptables -A $xchain -d \$address -j ACCEPT done __EOF__ done for address in $(find_broadcasts) 255.255.255.255 224.0.0.0/4 ; do [ -n "$ylevel" ] && log_rule_limit ${ylevel%\!} $xchain allowBcast ACCEPT "" "$xtag" -A -d $address run_iptables -A $xchain -d $address -j ACCEPT done fi ;; dropNotSyn) [ -n "$ylevel" ] && \ log_rule_limit ${ylevel%\!} $xchain dropNotSyn DROP "" "$xtag" -A -p tcp ! --syn run_iptables -A $xchain -p tcp ! --syn -j DROP ;; rejNotSyn) [ -n "$ylevel" ] && \ log_rule_limit ${ylevel%\!} $xchain rejNotSyn REJECT "" "$xtag" -A -p tcp ! --syn run_iptables -A $xchain -p tcp ! --syn -j REJECT --reject-with tcp-reset ;; dropInvalid) [ -n "$ylevel" ] && \ log_rule_limit ${ylevel%\!} $xchain dropInvalid DROP "" "$xtag" -A -m state --state INVALID run_iptables -A $xchain -m state --state INVALID -j DROP ;; allowInvalid) [ -n "$ylevel" ] && \ log_rule_limit ${ylevel%\!} $xchain allowInvalid ACCEPT "" "$xtag" -A -m state --state INVALID run_iptables -A $xchain -m state --state INVALID -j ACCEPT ;; forwardUPnP) ;; allowinUPnP) if [ -n "$ylevel" ]; then log_rule_limit ${ylevel%\!} $xchain allowinUPnP ACCEPT "" "$xtag" -A -p udp --dport 1900 log_rule_limit ${ylevel%\!} $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 ;; allowoutUPnP) [ -n "$ylevel" ] && \ log_rule_limit ${ylevel%\!} $xchain allowoutUPnP ACCEPT "" "$xtag" -A -m owner --owner-cmd upnpd run_iptables -A $xchain -m owner --cmd-owner upnpd -j ACCEPT ;; Limit) set -- $(separate_list $xtag) [ $# -eq 3 ] || fatal_error "Limit rules must include ,, as the log tag" run_iptables -A $xchain -m recent --name $1 --set if [ -n "$ylevel" ]; then run_iptables -N $xchain% log_rule_limit $ylevel $xchain% $1 DROP "" "" -A run_iptables -A $xchain% -j DROP run_iptables -A $xchain -m recent --name $1 --update --seconds $3 --hitcount $(( $2 + 1 )) -j $xchain% else run_iptables -A $xchain -m recent --update --name $1 --seconds $3 --hitcount $(( $2 + 1 )) -j DROP fi run_iptables -A $xchain -j ACCEPT ;; *) # # Not a builtin # process_action3 ;; esac done } # # Add one Filter Rule # # The caller has established the following variables: # client = SOURCE IP or MAC # server = DESTINATION IP or interface # protocol = Protocol # address = Original Destination Address # port = Destination Port # cport = Source Port # multioption = String to invoke multiport match if appropriate # servport = Port the server listens on # chain = The canonical chain for this rule # logchain = The chain that should be mentioned in log messages # ratelimit = Optional rate limiting clause # userandgroup = -m owner clause # userspec = User name # mark = Packet mark # logtag = Log tag # policy = Applicable Policy # add_a_rule() { local natrule natrule= do_ports() { if [ -n "$port" ]; then dports="--dport" if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then multiport="$multioption" dports="--dports" fi dports="$dports $port" fi if [ -n "$cport" ]; then sports="--sport" if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then multiport="$multioption" sports="--sports" fi sports="$sports $cport" fi } interface_error() { fatal_error "Unknown interface $1 in rule: \"$rule\"" } rule_interface_verify() { verify_interface $1 || interface_error $1 } handle_exclusion() { build_exclusion_chain chain filter "$excludesource" "$excludedest" if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then match='--ctorigdst' if [ -n "$NEW_CONNTRACK_MATCH" ]; then case $adr in !*) match='!--ctorigdst' adr=${adr#!} ;; esac fi for adr in $(separate_list $addr); do run_iptables -A $logchain $state $(fix_bang $proto $multiport $sports $dports) $user -m conntrack $match $adr -j $chain done addr= else run_iptables -A $logchain $state $(fix_bang $cli $proto $multiport $sports $dports) $user -j $chain fi cli= proto= sports= multiport= dports= user= state= } do_ipp2p() { [ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. Rule: \"$rule\"" dports="-m ipp2p --${port:-ipp2p}" case $proto in ipp2p|IPP2P|ipp2p:tcp|IPP2P:TCP) port= proto=tcp do_ports ;; ipp2p:udp|IPP2P:UDP) port= proto=udp do_ports ;; ipp2p:all|IPP2P:ALL) port= proto=all ;; *) fatal_error "Invalid IPP2P protocol ${proto#*:}. Rule: \"$rule\"" ;; esac } # Set source variables. The 'cli' variable will hold the client match predicate(s). cli= case "$client" in -) ;; *:*) rule_interface_verify ${client%:*} cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})" ;; *.*.*|+*) cli="$(source_ip_range $client)" ;; ~*|!~*) cli=$(mac_match $client) ;; *) if [ -n "$client" ]; then rule_interface_verify $client cli="$(match_source_dev $client)" fi ;; esac # Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s). dest_interface= serv= case "$server" in -) ;; *.*.*|+*) serv=$server ;; ~*|!~*) fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address" ;; *) if [ -n "$server" ]; then [ -n "$nonat" ] && fatal_error "Destination interface not allowed with $logtarget" rule_interface_verify $server dest_interface="$(match_dest_dev $server)" fi ;; esac # Setup protocol and port variables sports= dports= proto=$protocol addr=$address servport=$serverport multiport= user="$userandgroup" mrk="$mark" # Restore $chain to the canonical chain. chain=$logchain [ x$port = x- ] && port= [ x$cport = x- ] && cport= case $proto in tcp|TCP|6) do_ports ;; tcp:syn) proto="tcp --syn" do_ports ;; 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|IPP2P|ipp2p:*|IPP2P:*) do_ipp2p ;; *) [ -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 [ "$SECTION" != DONE ]; then # # This function is called from process_default_macro() after rules are DONE # if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" -a -z "$user" -a -z "$excludesource" -a -z "$excludedest" -a -z "$mark" ] ; then error_message "WARNING -- Rule \"$rule\" is a POLICY" error_message " -- and should be moved to the policy file" fi fi ;; REDIRECT) [ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\"" [ -n "$serv" ] && \ fatal_error "REDIRECT rules cannot specify a server IP; rule: \"$rule\"" servport=${servport:=$port} natrule=Yes ;; DNAT|SAME) [ -n "$excludedest" ] && fatal_error "Invalid DEST for this ACTION; rule \"$rule\"" [ -n "$serv" ] || \ fatal_error "$logtarget rules require a server address; rule: \"$rule\"" natrule=Yes ;; LOG) [ -z "$loglevel" ] && \ fatal_error "LOG requires log level" ;; esac case $SECTION in ESTABLISHED|RELATED) [ -n "$FASTACCEPT" ] && fatal_error "Entries in the $SECTION SECTION of the rules file not permitted with FASTACCEPT=Yes" state="-m state --state $SECTION" ;; *) state= ;; esac if [ -n "${serv}${servport}" ]; then # A specific server or server port given if [ -n "$natrule" ]; then lib_load nat "$logtarget Rules" add_nat_rule elif [ -n "$servport" -a "$servport" != "$port" ]; then fatal_error "Only DNAT, SAME and REDIRECT rules may specify destination port mapping; rule \"$rule\"" fi if [ -n "${excludesource}${excludedest}" ]; then handle_exclusion fi if [ -z "$dnat_only" ]; then if [ -n "$serv" ]; then for serv1 in $(separate_list $serv); do for srv in $(firewall_ip_range $serv1); do srv=$(dest_ip_range $srv) if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then if [ "$addr" = detect ]; then indent >&3 << __EOF__ run_iptables -A $chain $state $proto $ratelimit $multiport $cli $sports $srv $dports -m conntrack --ctorigdst \$adr $user $mrk -j $target done __EOF__ else for adr in $(separate_list $addr); do match='--ctorigdst' if [ -n "$NEW_CONNTRACK_MATCH" ]; then case $adr in !*) match='!--ctorigdst' adr=${adr#!} ;; esac fi if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A -m conntrack $match $adr \ $user $mrk $(fix_bang $proto $multiport $sports $cli $srv $dports) $state fi if [ "$logtarget" != LOG ]; then run_iptables2 -A $chain $state $proto $ratelimit $multiport $cli $sports \ $srv $dports -m conntrack $match $adr $user $mrk -j $target fi done fi else if [ "$addr" = detect ]; then save_command 'done' save_command '' fi if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \ $state $(fix_bang $proto $multiport $sports $cli $srv $dports) fi if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $srv $dports $ratelimit $user $mrk -j RETURN fi if [ "$logtarget" != NONAT -a "$logtarget" != LOG ]; then run_iptables2 -A $chain $state $proto $multiport $cli $sports \ $srv $dports $ratelimit $user $mrk -j $target fi fi done done else if [ "$addr" = detect ]; then save_command 'done' save_command '' fi if [ -n "$loglevel" -a -z "$natrule" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \ $state $(fix_bang $proto $multiport $sports $cli $dports) fi [ -n "$nonat" ] && \ addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user $mrk -j RETURN [ "$logtarget" != NONAT -a "$logtarget" != LOG ] && \ run_iptables2 -A $chain $state $proto $multiport $cli $sports \ $dports $ratelimit $user $mrk -j $target fi elif [ -n "$serv" -a "$addr" = detect ]; then save_command 'done' save_command '' fi else # Destination is a simple zone if [ -n "${excludesource}${excludedest}" ]; then handle_exclusion fi if [ -n "$addr" ]; then for adr in $(separate_list $addr); do match='--ctorigdst' if [ -n "$NEW_CONNTRACK_MATCH" ]; then case $adr in !*) match='!--ctorigdst' adr=${adr#!} ;; esac fi if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \ $state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports -m conntrack $match $adr) fi if [ "$logtarget" != LOG ]; then if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user $mrk -m conntrack $match $adr -j RETURN fi if [ "$logtarget" != NONAT ]; then run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \ $sports $dports $ratelimit $user $mrk -m conntrack $match $adr -j $target fi fi done else if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user $mrk \ $state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports) fi if [ "$logtarget" != LOG ]; then if [ -n "$nonat" ]; then addnatrule $(dnat_chain $source) $proto $multiport \ $cli $sports $dports $ratelimit $user $mrk -j RETURN fi if [ "$logtarget" != NONAT ]; then run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \ $sports $dports $ratelimit $user $mrk -j $target fi fi fi fi if [ "$logtarget" = LOG -a -z "$KLUDGEFREE" ]; then # # Purge the temporary files that we use to prevent duplicate '-m' specifications # [ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi } # # Process the contents of the USER/GROUP column # process_userspec() { [ "x$userspec" = x- ] && userspec= if [ -n "$userspec" ]; then userandgroup="-m owner" case "$userspec" in !*+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; *+*) if [ -n "${userspec#*+}" ]; then userandgroup="$userandgroup --cmd-owner ${userspec#*+}" fi userspec=${userspec%+*} ;; esac case "$userspec" in !*:*) if [ "$userspec" != "!:" ]; then temp="${userspec#!}" temp="${temp%:*}" [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" fi ;; *:*) if [ "$userspec" != ":" ]; then temp="${userspec%:*}" [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" temp="${userspec#*:}" [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" fi ;; !*) [ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}" ;; *) [ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec" ;; esac [ "$userandgroup" = "-m owner" ] && userandgroup= fi } # # Process the RATE/LIMIT column contents # process_ratelimit() { [ "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 } # # Process the MARK column contents # process_mark() { [ "x$mark" = "x-" ] && mark= if [ -n "$mark" ]; then if [ "$mark" = "${mark%!*}" ]; then mark="-m mark --mark $mark" else mark="-m mark ! --mark ${mark#*!}" fi fi } # # Combine a source/dest from the macro body with one from the macro invocation # merge_macro_source_dest() # $1 = source/dest from macro body, $2 = source/dest from invocation { case $2 in -) echo ${1} ;; *.*.*|+*|~*|!~*) # # Value in the invocation is an address -- put it behind the value from the macro # echo ${1}:${2} ;; *) echo ${2}:${1} ;; esac } # # Process a record from the rules file # process_rule() # $1 = target # $2 = clients # $3 = servers # $4 = protocol # $5 = ports # $6 = cports # $7 = address # $8 = ratelimit # $9 = userspec # $10= mark { local target target="$1" local clients clients="$2" local servers servers="$3" local protocol protocol="$4" local ports ports="$5" local cports cports="$6" local address address="$7" local ratelimit ratelimit="$8" local userspec userspec="$9" local mark mark="${10}" local userandgroup userandgroup= local logtag logtag= local nonat nonat= # # # # # F u n c t i o n B o d y # # # # # process_mark process_ratelimit # Isolate log level if [ "$target" = "${target%:*}" ]; then loglevel= else loglevel="${target#*:}" target="${target%%:*}" if [ "$loglevel" != "${loglevel%:*}" ]; then logtag="${loglevel#*:}" loglevel="${loglevel%:*}" fi case $loglevel in none*) loglevel= [ $target = LOG ] && return ;; *) [ -n "$loglevel" ] || target=${target%:*} ;; esac loglevel=${loglevel%\!} fi # # Save the original target in 'logtarget' for logging rules # logtarget=${target%-} # # Targets ending in "-" only apply to the nat table # [ $target = $logtarget ] && dnat_only= || dnat_only=Yes # Tranform the rule: # # - parse the user specification # - set 'target' to the filter table target. # - make $FW the destination for REDIRECT # - remove '-' suffix from logtargets while setting 'dnat_only' # - clear 'address' if it has been set to '-' [ "x$address" = "x-" ] && address= process_userspec case $target in *!) target=${target%!} ;; esac case $target in ACCEPT+|NONAT) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" nonat=Yes target=ACCEPT ;; ACCEPT|LOG) ;; DROP) [ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with DROP" ;; REJECT) [ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with REJECT" target=reject ;; CONTINUE) target=RETURN ;; DNAT*|SAME*) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" target=ACCEPT address=${address:=detect} ;; REDIRECT*) [ $SECTION = NEW ] || fatal_error "REDIRECT rules are not allowed in the $SECTION SECTION" target=ACCEPT address=${address:=all} if [ "x-" = "x$servers" ]; then servers=$FW else servers="$FW::$servers" fi ;; *-) [ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION" ;; esac # Parse and validate source if [ "$clients" = "${clients%:*}" ]; then clientzone="$clients" clients= else clientzone="${clients%%:*}" clients="${clients#*:}" [ -z "$clientzone" -o -z "$clients" ] && \ fatal_error "Empty source zone or qualifier: rule \"$rule\"" fi excludesource= case $clients in *!*!*) fatal_error "Invalid SOURCE in rule \"$rule\"" ;; !*) if [ $(list_count $clients) -gt 1 ]; then excludesource=${clients#!} clients= fi ;; *!*) excludesource=${clients#*!} clients=${clients%!*} ;; esac validate_zone $clientzone || fatal_error "Undefined Client Zone in rule \"$rule\"" source=$clientzone if [ $source != $FW -a -n "$userspec" ]; then fatal_error "Invalid use of a user-qualification: rule \"$rule\"" fi # Parse and validate destination if [ "$servers" = "${servers%:*}" ] ; then serverzone="$servers" servers= serverport= else serverzone="${servers%%:*}" servers="${servers#*:}" if [ "$servers" != "${servers%:*}" ] ; then serverport="${servers#*:}" servers="${servers%:*}" [ -z "$serverzone" -o -z "$serverport" ] && \ fatal_error "Empty destination zone or server port: rule \"$rule\"" if [ $(list_count $servers) -gt 1 ]; then case $servers in !*) fatal_error "Exclude lists not supported in the DEST column" ;; esac fi else serverport= [ -z "$serverzone" -o -z "$servers" ] && \ fatal_error "Empty destination zone or qualifier: rule \"$rule\"" fi fi excludedest= case $servers in *!*!*) fatal_error "Invalid DEST in rule \"$rule\"" ;; !*) if [ $(list_count $servers) -gt 1 ]; then excludedest=${servers#*!} servers= fi ;; *!*) excludedest=${servers#*!} servers=${servers%!*} ;; esac if ! validate_zone $serverzone; then fatal_error "Undefined Server Zone in rule \"$rule\"" fi dest=$serverzone # Ensure that this rule doesn't apply to a NONE policy pair of zones chain=${source}2${dest} # If we have one or more exclusion lists, we will create a new chain and # store it's name in 'chain'. We still want log rules to reflect the # canonical chain so we store it's name in $logchain. logchain=$chain eval policy=\$${chain}_policy [ -z "$policy" ] && \ fatal_error "No policy defined from zone $source to zone $dest" [ $policy = NONE ] && \ fatal_error "Rules may not override a NONE policy: rule \"$rule\"" [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all} ensurechain $chain # Generate Netfilter rule(s) case $logtarget in DNAT*|SAME) if [ -n "$XMULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] then # # Extended MULTIPORT is enabled, and less than # 16 ports are listed (port ranges count as two ports) - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do # # add_a_rule() modifies these so we must set their values each time # server=${servers:=-} port=${ports:=-} cport=${cports:=-} add_a_rule done elif [ -n "$MULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ "$ports" = "${ports%:*}" -a \ "$cports" = "${cports%:*}" -a \ $(list_count $ports) -le 15 -a \ $(list_count $cports) -le 15 ] then # # MULTIPORT is enabled, there are no port ranges in the rule and less than # 16 ports are listed - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do # # add_a_rule() modifies these so we must set their values each time # server=${servers:=-} port=${ports:=-} cport=${cports:=-} add_a_rule done else # # MULTIPORT is disabled or the rule isn't compatible with multiport match # multioption= for client in $(separate_list ${clients:=-}); do for port in $(separate_list ${ports:=-}); do for cport in $(separate_list ${cports:=-}); do server=${servers:=-} add_a_rule done done done fi ;; *) if [ -n "$XMULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] then # # Extended MULTIPORT is enabled, and less than # 16 ports are listed (port ranges count as two ports) - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do # # add_a_rule() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_a_rule done done elif [ -n "$MULTIPORT" ] && \ ! list_search $protocol "icmp" "ICMP" "1" && \ [ "$ports" = "${ports%:*}" -a \ "$cports" = "${cports%:*}" -a \ $(list_count $ports) -le 15 -a \ $(list_count $cports) -le 15 ] then # # MULTIPORT is enabled, there are no port ranges in the rule and less than # 16 ports are listed - use multiport match. # multioption="-m multiport" for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do # # add_a_rule() modifies these so we must set their values each time # port=${ports:=-} cport=${cports:=-} add_a_rule done done else # # MULTIPORT is disabled or the rule isn't compatible with multiport match # multioption= for client in $(separate_list ${clients:=-}); do for server in $(separate_list ${servers:=-}); do for port in $(separate_list ${ports:=-}); do for cport in $(separate_list ${cports:=-}); do add_a_rule done done done done fi ;; esac # # Report Result # progress_message " Rule \"$rule\" $DONE." save_progress_message_short " Rule \\\"$rule\\\" added." } # # Process a macro invocation in the rules file # process_macro() # $1 = target # $2 = param # $2 = clients # $3 = servers # $4 = protocol # $5 = ports # $6 = cports # $7 = address # $8 = ratelimit # $9 = userspec # $10= mark { local itarget itarget="$1" local param param="$2" local iclients iclients="$3" local iservers iservers="$4" local iprotocol iprotocol="$5" local iports iports="$6" local icports icports="$7" local iaddress iaddress="$8" local iratelimit iratelimit="$9" local iuserspec iuserspec="${10}" local imark imark="${11}" progress_message "..Expanding Macro $(find_file macro.${itarget%%:*})..." while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do [ $mtarget = COMMENT ] && continue mtarget=$(merge_levels $itarget $mtarget) case $mtarget in PARAM|PARAM:*) [ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation" ;; esac case ${mtarget%%:*} in ACCEPT|ACCEPT!|ACCEPT+|NONAT|DROP|DROP!|REJECT|REJECT!|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|CONTINUE!|QUEUE|SAME|SAME-) ;; *) if list_search ${mtarget%%:*} $ACTIONS; then if ! list_search $mtarget $USEDACTIONS; then createactionchain $mtarget USEDACTIONS="$USEDACTIONS $mtarget" fi mtarget=$(find_logactionchain $mtarget) else fatal_error "Invalid Action in rule \"$mtarget ${mclients:--} ${mservers:--} ${mprotocol:--} ${mports:--} ${mcports:--} ${xaddress:--} ${mratelimit:--} ${muserspec:--}\"" fi ;; esac if [ -n "$mclients" ]; then case $mclients in -|SOURCE) mclients=${iclients} ;; DEST) mclients=${iservers} ;; *) mclients=$(merge_macro_source_dest $mclients $iclients) ;; esac else mclients=${iclients} fi if [ -n "$mservers" ]; then case $mservers in -|DEST) mservers=${iservers} ;; SOURCE) mservers=${iclients} ;; *) mservers=$(merge_macro_source_dest $mservers $iservers) ;; esac else mservers=${iservers} fi [ -n "$iprotocol" ] && [ "x${iprotocol}" != x- ] && mprotocol=$iprotocol [ -n "$iports" ] && [ "x${iports}" != x- ] && mports=$iports [ -n "$icports" ] && [ "x${icports}" != x- ] && mcports=$icports [ -n "$iratelimit" ] && [ "x${iratelimit}" != x- ] && mratelimit=$iratelimit [ -n "$iuserspec" ] && [ "x${iuserspec}" != x- ] && muserspec=$iuserspec rule="$mtarget ${mclients=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${xaddress:=-} ${mratelimit:=-} ${muserspec:=-}" process_rule $mtarget $mclients $mservers $mprotocol $mports $mcports ${iaddress:=-} $mratelimit $muserspec $imark done < $TMP_DIR/macro.${itarget%%:*} progress_message "..End Macro" } # # Process the rules file # process_rules() { local comment comment= local optimize # # Process a rule where the source or destination is "all" # process_wildcard_rule() # $1 = Yes, if this is a macro, $2 = Yes if we want intrazone traffic { local yclients local yservers local ysourcezone local ydestzone local ypolicy for yclients in $xclients; do for yservers in $xservers; do ysourcezone=${yclients%%:*} ydestzone=${yservers%%:*} if [ "${ysourcezone}" != "${ydestzone}" -o "$2" = Yes ] ; then eval ypolicy=\$${ysourcezone}2${ydestzone}_policy if [ "$ypolicy" != NONE ]; then if [ $optimize -gt 0 ]; then eval yloglevel=\$${ysourcezone}2${ydestzone}_loglevel if [ -n "$yloglevel" ]; then if [ x$ypolicy:$yloglevel = x$xtarget ]; then continue fi elif [ x$ypolicy = x$xtarget ]; then continue fi fi if [ "$1" = Yes ]; then process_macro $xtarget "$xparam" $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark else rule="$xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark" process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark fi fi fi done done } do_it() # $1 = "Yes" if the target is a macro. { local intrazone intrazone= if [ -z "$SECTIONS" ]; then finish_section ESTABLISHED,RELATED SECTIONS="ESTABLISHED RELATED NEW" SECTION=NEW fi case $xclients in all+) xclients=all intrazone=Yes ;; all+-|all-+) xclients=all- intrazone=Yes ;; esac case $xservers in all+) xservers=all intrazone=Yes ;; all+-|all-+) xservers=all- intrazone=Yes ;; esac case $xclients in all|all-) [ $xclients = all ] && xclients="$ZONES $FW" || xclients="$ZONES" if [ "x$xservers" = xall ]; then xservers="$ZONES $FW" elif [ "x$xservers" = xall- ]; then xservers="$ZONES" fi process_wildcard_rule "$1" $intrazone return ;; esac case $xservers in all|all-) if [ "x$xservers" = xall ]; then xservers="$ZONES $FW" elif [ "x$xservers" = xall- ]; then xservers="$ZONES" fi process_wildcard_rule "$1" $intrazone return ;; esac if [ "$1" = Yes ]; then process_macro $xtarget "$xparam" $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark" process_rule $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark fi } while read xtarget xclients xservers xprotocol xports xcports xaddress xratelimit xuserspec xmark; do if [ "x$xclients" = xnone -o "x$servers" = xnone ]; then rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark" progress_message " Rule \"$rule\" ignored." continue fi optimize=$OPTIMIZE; case "${xtarget%%:*}" in ACCEPT|ACCEPT+|NONAT|DROP|REJECT|DNAT|DNAT-|REDIRECT|REDIRECT-|LOG|CONTINUE|QUEUE|SAME|SAME-) do_it No ;; ACCEPT!|DROP!|REJECT!|QUEUE!|CONTINUE!) optimize=0 do_it No ;; COMMENT) if [ -n "$COMMENTS" ]; then comment=$(echo $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark) save_command COMMENT=\"$comment\" else error_message "COMMENT ignored -- requires comment support in iptables/Netfilter" fi continue ;; SECTION) list_search $xclients $SECTIONS && fatal_error "Duplicate or out of order SECTION $xclients" case $xclients in ESTABLISHED) SECTIONS=ESTABLISHED ;; RELATED) finish_section ESTABLISHED SECTIONS="ESTABLISHED RELATED" ;; NEW) [ $SECTION = RELATED ] && finish_section RELATED || finish_section ESTABLISHED,RELATED SECTIONS="ESTABLISHED RELATED NEW" ;; *) fatal_error "Invalid SECTION $xclients" ;; esac [ -n "$xservers" ] && fatal_error "Invalid SECTION $xclients $xservers" SECTION=$xclients ;; *) if list_search ${xtarget%%:*} $ACTIONS; then if ! list_search $xtarget $USEDACTIONS; then createactionchain $xtarget USEDACTIONS="$USEDACTIONS $xtarget" fi xtarget=$(find_logactionchain $xtarget) do_it No else xtarget1=$(map_old_action ${xtarget%%:*}) case $xtarget1 in */*) xparam=${xtarget1#*/} xtarget1=${xtarget1%%/*} xtarget=$(substitute_action $xtarget1 $xtarget) ;; *) xparam= ;; esac f=macro.$xtarget1 if [ -f $TMP_DIR/$f ]; then do_it Yes else fn=$(find_file $f) if [ -f $fn ]; then strip_file $f $fn do_it Yes else rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec $xmark" fatal_error "Invalid Action in rule \"$rule\"" fi fi fi ;; esac done < $TMP_DIR/rules # # Just in case the file ended with a comment # if [ -n "$COMMENTS" ]; then save_command save_command COMMENT= fi case $SECTION in ESTABLISHED) finish_section ESTABLISHED,RELATED ;; RELATED) finish_section RELATED ;; esac SECTION=DONE } # # Process a default macro # process_default_macro() # $1 = macro name { local macro macro=$1 local address address= local multioption multioption= local servport servport= local chain chain=$1 local logchain logchain=$1 local userandgroup userandgroup= local logtag logtag= local excludesource excludesource= local target local client local server local protocol local port local cport local ratelimit local userspec local rule local f f=$(find_file macro.${macro}) havechain $macro && fatal_error "Illegal duplicate default macro name: $macro" createchain $macro no strip_file macro.$macro $f progress_message "..Expanding Default Macro $f into chain $macro..." while read target client server protocol port cport ratelimit userspec; do rule="$target ${client:--} ${server:--} ${protocol:--} ${port:--} ${cport:--} ${ratelimit:--} ${userspec:--}" case $target in PARAM|PARAM:*) fatal_error "Invalid target ($target) in default macro $macro" ;; esac case ${target} in ACCEPT|DROP|REJECT) ;; *) if ! list_search $target $USEDACTIONS; then if list_search $target $ACTIONS; then createactionchain $target USEDACTIONS="$USEDACTIONS $target" else fatal_error "Invalid target ($target) in default macro $macro" fi fi ;; esac if [ $(list_count ${port}${cport}) -gt 1 ]; then multioption="-m multiport" fi if [ -n "$client" ]; then case $client in -|SOURCE) client= ;; esac fi if [ -n "$server" ]; then case $server in -|DEST) server= ;; *) ;; esac fi process_userspec process_ratelimit add_a_rule progress_message "Rule \"$target $protocol $port $cport $ratelimit $userspec\" $DONE" done < $TMP_DIR/macro.$macro progress_message "..End Macro" } # # 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". The caller # has also set the variable 'chain' to contain the name of the mangle table # chain where forward rules are to be in placed in. # 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}" [ "x$mark" = x- ] && mark= if [ -n "$mark" ]; then if [ "$mark" = "${mark%!*}" ]; then mark="-m mark --mark $mark" else mark="-m mark ! --mark ${mark#*!}" fi fi tos="-j TOS --set-tos $tos" case "$dstzone" in all|ALL) dst=0.0.0.0/0 ;; *) if [ -z "$MANGLE_FORWARD" ]; then error_message "WARNING: A zone name in the DEST column requires Mangle FORWARD Chain support in your kernel and iptables: rule \"$rule\" ignored" return fi [ -z "$dst" ] && eval dst=\$${dstzone}_hosts ;; esac for dest in $dst; do dest="$(match_dest $dest)" case $srczone in $FW) run_iptables2 -t mangle -A outtos \ $protocol $dest $dports $sports $mark $tos ;; all|ALL) run_iptables2 -t mangle -A outtos \ $protocol $dest $dports $sports $mark $tos run_iptables2 -t mangle -A $chain \ $protocol $dest $dports $sports $mark $tos ;; *) if [ -n "$src" ]; then run_iptables2 -t mangle -A $chain $src \ $protocol $dest $dports $sports $mark $tos else eval hosts=\$${srczone}_hosts for host in $hosts; do run_iptables2 -t mangle -A $chain $(match_source $host) \ $protocol $dest $dports $sports $mark $tos done fi ;; esac done progress_message " Rule \"$rule\" $DONE." save_progress_message "Rule \\\"$rule\\\" Added." } # # Process the tos file # process_tos() # $1 = name of tos file { local chain chain=pretos local stdchain stdchain=PREROUTING if [ -n "$MANGLE_FORWARD" ]; then chain=fortos stdchain=FORWARD fi if [ -s $TMP_DIR/tos ] ; then save_progress_message "Setting up TOS..." progress_message2 "$DOING $1..." createmanglechain $chain createmanglechain outtos while read src dst protocol sport dport tos mark; do rule="$(echo $src $dst $protocol $sport $dport $tos $mark)" process_tos_rule done < $TMP_DIR/tos run_iptables -t mangle -A $stdchain -j $chain run_iptables -t mangle -A OUTPUT -j outtos fi } policy_rules() # $1 = chain to add rules to # $2 = policy # $3 = loglevel # $4 = Default Action/Macro { local target target="$2" local default default="$4" if [ -n "$default" ]; then [ "$default" = none ] || run_iptables -A $1 -j $default fi if [ $# -ge 3 -a "x${3}" != "x-" ]; then log_rule $3 $1 $2 fi if [ -n "$target" ]; then case $target in REJECT) run_iptables -A $1 -j reject ;; CONTINUE) ;; *) run_iptables -A $1 -j $target ;; esac fi } # # 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 chain="${1}2${2}" local policy policy= local loglevel loglevel= local chain1 jump_to_policy_chain() { # # Add a jump to from the canonical chain to the policy chain. On return, # $chain is set to the name of the policy chain # run_iptables -A $chain -j $chain1 chain=$chain1 } report_syn_flood_protection() { progress_message " Enabled SYN flood protection" } apply_default() { # # Generate policy file column values for the policy chain # eval policy=\$${chain1}_policy eval loglevel=\$${chain1}_loglevel eval synparams=\$${chain1}_synparams eval default=\$${chain1}_default # # 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:--}" $default else # # The policy chain is different from the canonical chain -- approach # depends on the policy # case $policy in ACCEPT|QUEUE) if [ -n "$synparams" ]; then # # To avoid double-counting SYN packets, enforce the policy # in this chain. # report_syn_flood_protection policy_rules $chain $policy "${loglevel:--}" $default else # # No problem with double-counting so just jump to the # policy chain. # jump_to_policy_chain fi ;; CONTINUE) # # Silly to jump to the policy chain -- add any logging # rules and enable SYN flood protection if requested # [ -n "$synparams" ] && \ report_syn_flood_protection policy_rules $chain $policy "${loglevel:--}" $default ;; *) # # DROP or REJECT policy -- enforce in the policy chain and # enable SYN flood protection if requested. # [ -n "$synparams" ] && \ report_syn_flood_protection jump_to_policy_chain ;; esac fi progress_message " Policy $policy for $1 to $2 using chain $chain" } eval chain1=\$${1}2${2}_policychain if [ -n "$chain1" ]; then apply_default $1 $2 else fatal_error "No default policy for zone $1 to zone $2" fi } # # Complete a standard chain # # - run any supplied user exit # - search the policy file for an applicable policy and add rules as # appropriate # - If no applicable policy is found, add rules for an assummed # policy of DROP INFO # complete_standard_chain() # $1 = chain, $2 = source zone, $3 = destination zone { local policy policy= local loglevel loglevel= local policychain policychain= local default default= run_user_exit $1 [ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state ESTABLISHED,RELATED -j ACCEPT eval policychain=\$${2}2${3}_policychain if [ -n "$policychain" ]; then eval policy=\$${policychain}_policy eval loglevel=\$${policychain}_loglevel eval default=\$${policychain}_default eval policy_rules $1 $policy "${loglevel:--}" $default else policy_rules $1 DROP info $DROP_DEFAULT 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 chain=${1}2${2} local policy havechain $chain && { echo $chain; return; } [ "$1" = "$2" ] && { echo ACCEPT; return; } eval chain=\$${chain}_policychain eval policy=\$${chain}_policy if [ "$policy" != CONTINUE ] ; then [ -n "$chain" ] && { echo $chain; return; } fatal_error "No policy defined for zone $1 to zone $2" fi } # # Add a record to the blacklst chain # # $source = address match # $proto = protocol selector # $dport = destination port selector # add_blacklist_rule() { run_iptables2 -A blacklst $source $proto $dport -j $target } # # Process a record from the blacklist file # # $networks = address/networks # $protocol = Protocol Number/Name # $port = Port Number/Name # process_blacklist_rec() { local source local addr local proto local dport local temp local setname for addr in $(separate_list $networks); do case $addr in -) source= ;; ~*|!~*) 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_and_save " $addr added to Black List" done } process_blacklist() { local disposition disposition=$BLACKLIST_DISPOSITION local f f=$(find_file blacklist) local target if [ -s $TMP_DIR/blacklist ]; then [ "$disposition" = REJECT ] && disposition=reject [ -n "$BLACKLIST_LOGLEVEL" ] && target=blacklog || target=$disposition progress_message2 "Compiling $f..." cat >&3 << __EOF__ # # Load the blacklist # load_blacklist() { __EOF__ INDENT=" " while read networks protocol ports; do process_blacklist_rec done < $TMP_DIR/blacklist INDENT= save_command "}" save_command fi } # # Setup the Black List # setup_blacklist() { local hosts hosts="$(find_hosts_by_option blacklist)" local ipsec local policy if [ -n "$hosts" -a -s ${TMP_DIR}/blacklist ]; then progress_message2 "$DOING Blacklisting..." createchain blacklst no if [ -n "$BLACKLIST_LOGLEVEL" ]; then createchain blacklog no log_rule_limit $BLACKLIST_LOGLEVEL blacklog blacklst $BLACKLIST_DISPOSITION "$LOGLIMIT" "" -A if [ $BLACKLIST_DISPOSITION = REJECT ]; then run_iptables -A blacklog -j reject else run_iptables -A blacklog -j $BLACKLIST_DISPOSITION fi fi [ -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_and_save " Blacklisting enabled on ${interface}${network}" done if [ -z "$DELAYBLACKLISTLOAD" -a -s ${TMP_DIR}/blacklist ]; then save_command load_blacklist fi fi } # Construct zone-independent rules # add_common_rules() { local savelogparms savelogparms="$LOGPARMS" local broadcasts broadcasts="$(find_broadcasts) 255.255.255.255 224.0.0.0/4" # # Populate the smurf chain # save_progress_message "Setting up SMURF control..." for interface in $(find_bcastdetect_interfaces); do indent >&3 << __EOF__ ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do __EOF__ [ -n "$SMURF_LOG_LEVEL" ] && \ indent >&3 << __EOF__ do_log_rule $SMURF_LOG_LEVEL smurfs DROP -s \$address __EOF__ indent >&3 << __EOF__ run_iptables -A smurfs -s \$address -j DROP done __EOF__ done for address in $broadcasts ; do [ -n "$SMURF_LOG_LEVEL" ] && log_rule $SMURF_LOG_LEVEL smurfs DROP -s $address run_iptables -A smurfs $(source_ip_range $address) -j DROP done # # Reject Rules -- Don't respond to broadcasts with an ICMP # if [ -n "$USEPKTTYPE" ]; then run_iptables -A reject -m pkttype --pkt-type broadcast -j DROP run_iptables -A reject -m pkttype --pkt-type multicast -j DROP else for interface in $(find_bcastdetect_interfaces); do indent >&3 << __EOF__ ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u | while read address; do run_iptables -A reject -d \$address -j DROP done __EOF__ done for address in $broadcasts ; do run_iptables -A reject -d $address -j DROP done fi # # Don't feed the smurfs # for address in $broadcasts ; do run_iptables -A reject -s $address -j DROP done # # Don't respond to IGMP with an ICMP # run_iptables -A reject -p 2 -j DROP 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 # if [ -n "$ENHANCED_REJECT" ]; then run_iptables -A reject -p icmp -j REJECT --reject-with icmp-host-unreachable run_iptables -A reject -j REJECT --reject-with icmp-host-prohibited else run_iptables -A reject -j REJECT fi # # Create default action chains # for action in $USEDACTIONS; do createactionchain $action done append_file initdone # # Process Black List # save_progress_message "Setting up Black List..." setup_blacklist # # SMURFS # hosts=$(find_hosts_by_option nosmurfs) if [ -n "$hosts" ]; then progress_message2 "Adding Anti-smurf Rules" save_progress_message "Adding Anti-smurf Jumps..." 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 progress_message2 "Adding rules for DHCP" save_progress_message "Setting up rules for DHCP..." for interface in $interfaces; do if [ -n "$BRIDGING" ]; then indent >&3 << __EOF__ is_bridge="\$( brctl show 2> /dev/null | grep '^$interface[[:space:]]' )" [ -n "\$is_bridge" ] && run_iptables -A $(forward_chain $interface) -p udp -o $interface --dport 67:68 -j ACCEPT __EOF__ fi run_iptables -A $(input_chain $interface) -p udp --dport 67:68 -j ACCEPT run_iptables -A $(out_chain $interface) -p udp --dport 67:68 -j ACCEPT done fi # # RFC 1918 # hosts="$(find_hosts_by_option norfc1918)" if [ -n "$hosts" ]; then progress_message2 "Enabling RFC1918 Filtering" save_progress_message "Setting up RFC1918 Filtering..." 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 # createmanglechain man1918 createmanglechain 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 # match='--ctorigdst' if [ -n "$NEW_CONNTRACK_MATCH" ]; then case $network in !*) match='!--ctorigdst' network=${network#!} ;; esac fi run_iptables2 -A $chain -m conntrack $match $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) $policy -j man1918 done fi hosts=$(find_hosts_by_option tcpflags) if [ -n "$hosts" ]; then progress_message2 "$DOING TCP Flags checking..." save_progress_message "Setting up TCP Flags checking..." createchain tcpflags no if [ -n "$TCP_FLAGS_LOG_LEVEL" ]; then createchain logflags no savelogparms="$LOGPARMS" [ "$TCP_FLAGS_LOG_LEVEL" = ULOG ] || LOGPARMS="$LOGPARMS --log-ip-options" log_rule $TCP_FLAGS_LOG_LEVEL logflags $TCP_FLAGS_DISPOSITION LOGPARMS="$savelogparms" case $TCP_FLAGS_DISPOSITION in REJECT) run_iptables -A logflags -j REJECT --reject-with tcp-reset ;; *) run_iptables -A logflags -j $TCP_FLAGS_DISPOSITION ;; esac disposition="-j logflags" else disposition="-j $TCP_FLAGS_DISPOSITION" fi run_iptables -A tcpflags -p tcp --tcp-flags ALL FIN,URG,PSH $disposition run_iptables -A tcpflags -p tcp --tcp-flags ALL NONE $disposition run_iptables -A tcpflags -p tcp --tcp-flags SYN,RST SYN,RST $disposition run_iptables -A tcpflags -p tcp --tcp-flags SYN,FIN SYN,FIN $disposition # # There are a lot of probes to ports 80, 3128 and 8080 that use a source # port of 0. This catches them even if they are directed at an IP that # hosts a web server. # run_iptables -A tcpflags -p tcp --syn --sport 0 $disposition for host in $hosts; do ipsec=${host%^*} host=${host#*^} [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy= interface=${host%%:*} network=${host#*:} for chain in $(first_chains $interface); do run_iptables -A $chain -p tcp $(match_source_hosts $network) $policy -j tcpflags done done fi # # ARP Filtering # save_progress_message "Setting up ARP filtering..." indent >&3 << __EOF__ for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/arp_filter ] && echo 0 > \$f/arp_filter [ -f \$f/arp_ignore ] && echo 0 > \$f/arp_ignore done __EOF__ interfaces=$(find_interfaces_by_option arp_filter) interfaces1=$(find_interfaces_by_option1 arp_ignore) if [ -n "${interfaces}${interfaces1}" ]; then progress_message2 "$DOING ARP Filtering..." for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/arp_filter indent >&3 << __EOF__ if [ -f $file ]; then echo 1 > $file else error_message "WARNING: Cannot set ARP filtering on $interface" fi __EOF__ done for interface in $interfaces1; do file=/proc/sys/net/ipv4/conf/$interface/arp_ignore eval value="\$$(chain_base $interface)_arp_ignore" indent >&3 << __EOF__ if [ -f $file ]; then echo $value > $file else error_message "WARNING: Cannot set ARP filtering on $interface" fi __EOF__ done fi # # Route Filtering # interfaces="$(find_interfaces_by_option routefilter)" if [ -n "$interfaces" -o -n "$ROUTE_FILTER" ]; then progress_message2 "$DOING Kernel Route Filtering..." save_progress_message "Setting up Route Filtering..." if [ "$ROUTE_FILTER" = no ]; then indent >&3 << __EOF__ for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/rp_filter ] && echo 0 > \$f/rp_filter done __EOF__ fi for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/rp_filter indent >&3 << __EOF__ if [ -f $file ]; then echo 1 > $file else error_message "WARNING: Cannot set route filtering on $interface" fi __EOF__ done save_command "echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter" if [ "$ROUTE_FILTER" = yes ]; then save_command "echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter" elif [ "$ROUTE_FILTER" = no ]; then save_command "echo 0 > /proc/sys/net/ipv4/conf/default/rp_filter" fi save_command "[ -n \"\$NOROUTES\" ] || ip route flush cache" fi # # Martian Logging # interfaces="$(find_interfaces_by_option logmartians)" if [ -n "$interfaces" -o -n "$LOG_MARTIANS" ]; then progress_message2 "$DOING Martian Logging..." save_progress_message "Setting up Martian Logging..." if [ "$LOG_MARTIANS" = no ]; then indent >&3 << __EOF__ for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/log_martians ] && echo 0 > \$f/log_martians done __EOF__ fi for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/log_martians indent >&3 << __EOF__ if [ -f $file ]; then echo 1 > $file else error_message "WARNING: Cannot set Martian logging on $interface" fi __EOF__ done if [ "$LOG_MARTIANS" = yes ]; then save_command "echo 1 > /proc/sys/net/ipv4/conf/all/log_martians" save_command "echo 1 > /proc/sys/net/ipv4/conf/default/log_martians" elif [ "$LOG_MARTIANS" = no ]; then save_command "echo 0 > /proc/sys/net/ipv4/conf/all/log_martians" save_command "echo 0 > /proc/sys/net/ipv4/conf/default/log_martians" fi fi # # Source Routing # save_progress_message "Setting up Accept Source Routing..." indent >&3 << __EOF__ for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/accept_source_route ] && echo 0 > \$f/accept_source_route done __EOF__ interfaces=$(find_interfaces_by_option sourceroute) if [ -n "$interfaces" ]; then progress_message2 "$DOING Accept Source Routing..." save_progress_message "Setting up Source Routing..." for interface in $interfaces; do file=/proc/sys/net/ipv4/conf/$interface/accept_source_route indent >&3 << __EOF__ if [ -f $file ]; then echo 1 > $file else error_message "WARNING: Cannot set Accept Source Routing on $interface" fi __EOF__ done fi if [ -n "$DYNAMIC_ZONES" ]; then progress_message "$DOING 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 $(out_chain $interface) -j $(dynamic_out $interface) done fi # # UPnP # interfaces=$(find_interfaces_by_option upnp) if [ -n "$interfaces" ]; then progress_message2 "$DOING UPnP..." save_progress_message "Setting up UPnP..." createnatchain UPnP for interface in $interfaces; do run_iptables -t nat -A PREROUTING -i $interface -j UPnP done fi } # # Scan the policy file defining the necessary chains # Add the appropriate policy rule(s) to the end of each canonical chain # apply_policy_rules() { # # Create policy chains # for chain in $ALL_POLICY_CHAINS; do eval policy=\$${chain}_policy eval loglevel=\$${chain}_loglevel eval optional=\$${chain}_is_optional eval default=\$${chain}_default if [ "$policy" != NONE ]; then if ! havechain $chain && [ -z "$optional" -a "$policy" != CONTINUE ]; then # # The chain doesn't exist. Create the chain and add policy # rules # createchain $chain yes # # If either client or server is 'all' then this MUST be # a policy chain and we must apply the appropriate policy rules # # Otherwise, this is a canonical chain which will be handled in # the for loop below # case $chain in all2*|*2all) run_user_exit $chain policy_rules $chain $policy "${loglevel:--}" $default ;; esac fi 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 PREROUTING_rule=1 local POSTROUTING_rule 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 sourcechain=$1 local destchain destchain=$2 shift shift if havenatchain $destchain ; then run_iptables2 -t nat -A $sourcechain $@ -j $destchain elif [ -z "$KLUDGEFREE" ]; then [ -n "$PHYSDEV_MATCH" -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 inserted before jumps to one-to-one NAT chains. # addrulejump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments { local sourcechain sourcechain=$1 local destchain 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\)\) elif [ -z "$KLUDGEFREE" ]; then [ -n "$PHYSDEV_MATCH" -a -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev [ -n "$IPRANGE_MATCH" -a -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange fi } # # Create a dynamic chain for a zone and jump to it from a second chain # create_zone_dyn_chain() # $1 = zone, $2 = second chain { createchain ${1}_dyn No run_iptables -A $2 -j ${1}_dyn } # # Insert a set of exclusions at the front of a chain # insert_exclusions() # $1 = table $2 = chain name, $3 - $n = exclusions { local t t=$1 local c c=$2 local num num=0 local host1 local interface1 local networks1 shift 2 for host1 in $*; do interface1=${host1%%:*} networks1=${host1#*:} num=$(($num + 1)) run_iptables -t $t -I $c $num -o $interface1 -d $networks1 -j RETURN done } # # Add a set of exclusions to the end of a chain # add_exclusions() # $1 = table $2 = chain name, $3 - $n = exclusions { local t t=$1 local c c=$2 local host1 local interface1 local networks1 shift 2 for host1 in $*; do interface1=${host1%%:*} networks1=${host1#*:} run_iptables -t $t -A $c -o $interface1 -d $networks1 -j RETURN done } # # E x e c u t i o n S t a r t s H e r e # # 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 echo "$FW firewall" > $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 eval exclusions=\"\$${zone}_exclusions\" if [ -n "$exclusions" ]; then local num num=1 in_chain=${zone}_input out_chain=${zone}_output createchain $in_chain No createchain $out_chain No if [ "$(rules_chain $zone $zone)" = ACCEPT ]; then createchain ${zone}2${zone} yes run_iptables -A ${zone}2${zone} -j ACCEPT fi for host in $exclusions; do interface=${host%%:*} address=${host#*:} run_iptables -A $frwd_chain -i $interface -s $address -j RETURN run_iptables -A $in_chain -i $interface -s $address -j RETURN run_iptables -A $out_chain -i $interface -s $address -j RETURN done fi if [ -n "$POLICY_MATCH" ]; then # # Because policy match only matches an 'in' or an 'out' policy (but not both), we have to place the # '--pol ipsec --dir in' rules at the front of the interface forwarding chains. Otherwise, decrypted packets # can match '--pol none --dir out' rules and send the packets down the wrong rules chain. # eval is_ipsec=\$${zone}_is_ipsec if [ -n "$is_ipsec" ]; then eval source_hosts=\$${zone}_hosts [ -n "$DYNAMIC_ZONES" ] && create_zone_dyn_chain $zone $frwd_chain else eval source_hosts=\$${zone}_ipsec_hosts [ -n "$DYNAMIC_ZONES" -a -n "$source_hosts" ] && create_zone_dyn_chain $zone $frwd_chain fi for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} run_iptables2 -A $(forward_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $frwd_chain done fi fi done # # Main source zone rule-activation loop # for zone in $ZONES; do eval source_hosts=\$${zone}_hosts chain1=$(rules_chain $FW $zone) chain2=$(rules_chain $zone $FW) chain3=$(rules_chain $zone $zone) eval complex=\$${zone}_is_complex eval type=\$${zone}_type eval exclusions=\"\$${zone}_exclusions\" if [ -n "$exclusions" ]; then echo "$zone $type $source_hosts exclude $exclusions" >> $STATEDIR/zones else echo "$zone $type $source_hosts" >> $STATEDIR/zones fi if [ -n "$DYNAMIC_ZONES" ]; then [ -n "$chain1" ] && echo "$FW $zone $chain1" >> $STATEDIR/chains [ -n "$chain2" ] && echo "$zone $FW $chain2" >> $STATEDIR/chains fi need_broadcast= if [ -n "$complex" ]; then frwd_chain=${zone}_frwd chain=$(dnat_chain $zone) if havenatchain $chain; then insert_exclusions nat $chain $exclusions fi fi # # Take care of PREROUTING, INPUT and OUTPUT jumps # for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} if [ -n "$chain1" ]; then if [ -n "$exclusions" ]; then run_iptables2 -A $(out_chain $interface) $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j ${zone}_output run_iptables -A ${zone}_output -j $chain1 else run_iptables2 -A $(out_chain $interface) $(match_dest_hosts $networks) $(match_ipsec_out $zone $host) -j $chain1 fi fi # # Add jumps from the builtin chain for DNAT rules # addrulejump PREROUTING $(dnat_chain $zone) -i $interface $(match_source_hosts $networks) $(match_ipsec_in $zone $host) if [ -n "$chain2" ]; then if [ -n "$exclusions" ]; then run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j ${zone}_input run_iptables -A ${zone}_input -j $chain2 else run_iptables2 -A $(input_chain $interface) $(match_source_hosts $networks) $(match_ipsec_in $zone $host) -j $chain2 fi fi 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 if interface_has_option $interface detectnets; then need_broadcast="$need_broadcast $interface" iface=$(chain_base $interface) eval need_bcast_$iface=\"$(match_source_hosts $networks)\" fi fi fi ;; esac done if [ -n "$chain1" ]; then for interface in $need_broadcast ; do run_iptables -A $(out_chain $interface) -d 255.255.255.255 -j $chain1 run_iptables -A $(out_chain $interface) -d 224.0.0.0/4 -j $chain1 done fi # # F O R W A R D I N G # temp_zones= last_chain= if [ $OPTIMIZE -gt 0 ]; then dest_zones= # # The following loop attempts to eliminate redundant sequences of jumps to # all2all or 2all. It does so by combining all trailing # jumps to the same policy-only chain. # for zone1 in $ZONES; do eval policy=\$${zone}2${zone1}_policy [ "$policy" = NONE ] && continue chain="$(rules_chain $zone $zone1)" [ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain. 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" -a -z "$exclusions" ] ; then continue fi fi case $chain in *2all) # # Rules chain is a policy-only chain that could be used more than once (all2all or ${zone}2all # if [ -n "$last_chain" ]; then # # And the last rules chain was a policy-only chain # if [ "$chain" != "$last_chain" ]; then # # But it was a different one -- back to square 1 # last_chain=$chain dest_zones="$dest_zones $temp_zones" temp_zones=$zone1 else # # Same chain -- add this dest zone to the running list of # zones using the same rules chain # temp_zones="$temp_zones $zone1" fi elif [ $policy = ACCEPT ]; then # # We don't wild-card ACCEPT policies -- could open up security holes through interfaces # that aren't described in /etc/shorewall/interfaces # dest_zones="$dest_zones $zone1" else # # First in a potential run of rules using this chain # last_chain=$chain temp_zones=$zone1 fi ;; *) # # Not a policy-only chain -- add accumulated sequence of dest zones to those needing processing # dest_zones="$dest_zones $temp_zones $zone1" temp_zones= last_chain= ;; esac done # # If there is no reduction in the number of rules then don't bother with the optimization # if [ -n "$last_chain" -a $(list_count1 $temp_zones) -eq 1 ]; then dest_zones="$dest_zones $temp_zones" last_chain= fi else dest_zones=$ZONES fi # # We now loop through the destination zones creating jumps to the rules chain for each source/dest combination. # $dest_zones is the list of destination zones that we need to handle from this source zone # for zone1 in $dest_zones; do eval policy=\$${zone}2${zone1}_policy [ "$policy" = NONE ] && continue eval dest_hosts=\$${zone1}_hosts eval exclusions1=\"\$${zone1}_exclusions\" chain="$(rules_chain $zone $zone1)" [ -z "$chain" ] && continue # CONTINUE policy and there is no canonical chain. [ -n "$DYNAMIC_ZONES" ] && echo "$zone $zone1 $chain" >> $STATEDIR/chains if [ $zone = $zone1 ]; then eval routeback=\"\$${zone}_routeback\" eval interfaces=\"\$${zone}_interfaces\" eval ports="\$${zone}_ports" num_ifaces=$(list_count1 $interfaces) [ $num_ifaces -eq 1 -a -n "$ports" ] && num_ifaces=$(list_count1 $ports) if [ $num_ifaces -lt 2 -a -z "$routeback" -a -z "$exclusions" ] ; then continue fi if [ -n "$chain3" ]; then for interface in $need_broadcast ; do if interface_has_option $interface routeback; then iface=$(chain_base $interface) eval source=\"\$need_bcast_$iface\" run_iptables -A $(forward_chain $interface) $source $(match_dest_dev $interface) -d 255.255.255.255 -j $chain3; run_iptables -A $(forward_chain $interface) $source $(match_dest_dev $interface) -d 224.0.0.0/4 -j $chain3; fi done fi else routeback= num_ifaces=0 fi if [ -n "$exclusions1" ]; then # # We handle exclusions in the dest zone by inserting RETURN rules at the front of # each rules chain where the zone is the destination # case $chain in all2$zone1) # # We only want to add the exclusions once # if eval test -z \"\$${chain}_exclusions\"; then eval ${chain}_exclusions=Yes insert_exclusions filter $chain $exclusions1 fi ;; *2all) # # A policy-only chain -- we create one exclusion chain for this # dest zone/chain combination, and re-use # it if the occasion presents itself # eval chain1=\$${chain}_${zone1}_ex if [ -z "$chain1" ]; then # # Must create the chain # chain1=excl_${EXCLUSION_SEQ} EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 )) eval ${chain}_${zone1}_ex=$chain1 createchain $chain1 no add_exclusions filter $chain1 $exclusions1 run_iptables -A $chain1 -j $chain fi # # We must jump to the exclusion chain rather than to the policy chain # chain=$chain1 ;; *) insert_exclusions filter $chain $exclusions1 ;; esac 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#*:} chain4=$(forward_chain $interface) for host1 in $dest_hosts; do interface1=${host1%%:*} networks1=${host1#*:} if [ "$host" != "$host1" ] || list_search $host $routeback; then run_iptables2 -A $chain4 $(match_source_hosts $networks) -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain fi done done fi done # # E N D F O R W A R D I N G # # Now add (an) unconditional jump(s) to the last unique policy-only chain determined above, if any # if [ -n "$last_chain" ]; then if [ -n "$complex" ]; then run_iptables -A $frwd_chain -j $last_chain else for host in $source_hosts; do interface=${host%%:*} networks=${host#*:} chain=$(forward_chain $interface) run_iptables -A $chain $(match_source_hosts $networks) -j $last_chain done fi fi done # # Now add the jumps to the interface chains from FORWARD, INPUT, OUTPUT and POSTROUTING # 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) run_iptables -A OUTPUT -o $interface -j $(out_chain $interface) addnatjump POSTROUTING $(masq_chain $interface) -o $interface done # # Handle fw->fw # chain=${FW}2${FW} if havechain $chain; then # # There is a fw->fw chain. Send loopback output through that chain # run_iptables -A OUTPUT -o lo -j $chain # # And delete the unconditional ACCEPT rule # run_iptables -D OUTPUT -o lo -j ACCEPT fi # # Add policy enforcement to the builtin filter chains to catch underfined hosts # complete_standard_chain INPUT all $FW complete_standard_chain OUTPUT $FW all complete_standard_chain FORWARD all all # # Remove rules added to keep the firewall alive during [re]start" # disable_critical_hosts for chain in INPUT OUTPUT FORWARD; do [ -n "$FASTACCEPT" ] || run_iptables -D $chain -m state --state ESTABLISHED,RELATED -j ACCEPT run_iptables -D $chain -p udp --dport 53 -j ACCEPT done process_routestopped -D if [ -n "$LOGALLNEW" ]; then for table in mangle nat filter; do case $table in mangle) [ -n "$MANGLE_FORWARD" ] && chains="PREROUTING INPUT FORWARD POSTROUTING" || chains="PREROUTING INPUT" ;; 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 } # # Compile a script that will stop the firewall # # This function is called by compile_firewall() so all of the overloaded functions # from that script are available here # compile_stop_firewall() { local IPTABLES_COMMAND IPTABLES_COMMAND="\$IPTABLES" local INDENT INDENT=" " cat >&3 << __EOF__ # # Stop/restore the firewall after an error or because of a "stop" or "clear" command # stop_firewall() { deletechain() { qt \$IPTABLES -L \$1 -n && qt \$IPTABLES -F \$1 && qt \$IPTABLES -X \$1 } deleteallchains() { \$IPTABLES -F \$IPTABLES -X } setcontinue() { \$IPTABLES -A \$1 -m state --state ESTABLISHED,RELATED -j ACCEPT } delete_nat() { \$IPTABLES -t nat -F \$IPTABLES -t nat -X if [ -f \${VARDIR}/nat ]; then while read external interface; do del_ip_addr \$external \$interface done < \${VARDIR}/nat rm -f \${VARDIR}/nat fi } case \$COMMAND in stop|clear|restore) ;; *) set +x case \$COMMAND in start) logger -p kern.err "ERROR:\$PRODUCT start failed" ;; restart) logger -p kern.err "ERROR:\$PRODUCT restart failed" ;; restore) logger -p kern.err "ERROR:\$PRODUCT restore failed" ;; esac if [ "\$RESTOREFILE" = NONE ]; then COMMAND=clear clear_firewall echo "\$PRODUCT Cleared" kill \$\$ exit 2 else RESTOREPATH=\${VARDIR}/\$RESTOREFILE if [ -x \$RESTOREPATH ]; then if [ -x \${RESTOREPATH}-ipsets ]; then progress_message2 Restoring Ipsets... # # We must purge iptables to be sure that there are no # references to ipsets # for table in mangle nat filter; do \$IPTABLES -t \$table -F \$IPTABLES -t \$table -X done \${RESTOREPATH}-ipsets fi echo Restoring \${PRODUCT:=Shorewall}... if \$RESTOREPATH restore; then echo "\$PRODUCT restored from \$RESTOREPATH" set_state "Started" else set_state "Unknown" fi kill \$\$ exit 2 fi fi ;; esac set_state "Stopping" STOPPING="Yes" TERMINATOR= deletechain shorewall determine_capabilities __EOF__ append_file stop cat >&3 << __EOF__ if [ -n "\$MANGLE_ENABLED" ]; then run_iptables -t mangle -F run_iptables -t mangle -X for chain in PREROUTING INPUT FORWARD POSTROUTING; do qt \$IPTABLES -t mangle -P \$chain ACCEPT done fi if [ -n "\$RAW_TABLE" ]; then run_iptables -t raw -F run_iptables -t raw -X for chain in PREROUTING OUTPUT; do qt \$IPTABLES -t raw -P \$chain ACCEPT done fi if [ -n "\$NAT_ENABLED" ]; then delete_nat for chain in PREROUTING POSTROUTING OUTPUT; do qt \$IPTABLES -t nat -P \$chain ACCEPT done fi if [ -f \${VARDIR}/proxyarp ]; then while read address interface external haveroute; do qt arp -i \$external -d \$address pub [ -z "\${haveroute}\${NOROUTES}" ] && qt ip route del \$address dev \$interface done < \${VARDIR}/proxyarp for f in /proc/sys/net/ipv4/conf/*; do [ -f \$f/proxy_arp ] && echo 0 > \$f/proxy_arp done fi rm -f \${VARDIR}/proxyarp __EOF__ [ -n "$CLEAR_TC" ] && save_command "delete_tc1" [ -n "$DISABLE_IPV6" ] && save_command "disable_ipv6" save_command "undo_routing" save_command "restore_default_route" process_criticalhosts if [ -n "$CRITICALHOSTS" ]; then if [ -z "$ADMINISABSENTMINDED" ]; then cat >&3 << __EOF__ for chain in INPUT OUTPUT; do setpolicy \$chain ACCEPT done setpolicy FORWARD DROP deleteallchains __EOF__ for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} do_iptables -A INPUT -i $interface $(source_ip_range $networks) -j ACCEPT do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done cat >&3 << __EOF__ for chain in INPUT OUTPUT; do setpolicy \$chain DROP done __EOF__ else cat >&3 << __EOF__ for chain in INPUT OUTPUT; do setpolicy \$chain ACCEPT done setpolicy FORWARD DROP deleteallchains __EOF__ for host in $CRITICALHOSTS; do interface=${host%:*} networks=${host#*:} do_iptables -A INPUT -i $interface $(source_ip_range $networks) -j ACCEPT do_iptables -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT done cat >&3 << __EOF__ setpolicy INPUT DROP for chain in INPUT FORWARD; do setcontinue \$chain done __EOF__ fi elif [ -z "$ADMINISABSENTMINDED" ]; then cat >&3 << __EOF__ for chain in INPUT OUTPUT FORWARD; do setpolicy \$chain DROP done deleteallchains __EOF__ else cat >&3 << __EOF__ for chain in INPUT FORWARD; do setpolicy \$chain DROP done setpolicy OUTPUT ACCEPT deleteallchains for chain in INPUT FORWARD; do setcontinue \$chain done __EOF__ fi process_routestopped -A save_command "\$IPTABLES -A INPUT -i lo -j ACCEPT" [ -z "$ADMINISABSENTMINDED" ] && \ save_command "\$IPTABLES -A OUTPUT -o lo -j ACCEPT" for interface in $(find_interfaces_by_option dhcp); do save_command "\$IPTABLES -A INPUT -p udp -i $interface --dport 67:68 -j ACCEPT" [ -z "$ADMINISABSENTMINDED" ] && \ save_command "\$IPTABLES -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT" # # This might be a bridge # save_command "\$IPTABLES -A FORWARD -p udp -i $interface -o $interface --dport 67:68 -j ACCEPT" done save_command case "$IP_FORWARDING" in On|on|ON) save_command "echo 1 > /proc/sys/net/ipv4/ip_forward" save_command "progress_message2 IP Forwarding Enabled" ;; Off|off|OFF) save_command "echo 0 > /proc/sys/net/ipv4/ip_forward" save_command "progress_message2 IP Forwarding Disabled!" ;; esac append_file stopped cat >&3 << __EOF__ set_state "Stopped" logger -p kern.info "\$PRODUCT Stopped" 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 # kill \$\$ ;; esac } __EOF__ } # # Conditionally add an option to .conf file (FD 3) # conditionally_add_option() { # $1 = option name local value eval value=\"\$$1\" if [ -n "$value" ]; then cat >&3 << __EOF__ [ -n "\${$1:=$value}" ] __EOF__ fi } conditionally_add_option1() { # $1 = option name local value eval value=\"\$$1\" if [ -n "$value" ]; then cat >&3 << __EOF__ $1="$value" __EOF__ fi } # # Post-process generated script to: # # - Suppress redundant blank lines # - Replace leading spaces with tabs # mycat() { if [ -n "$HAVEAWK" ]; then awk 'BEGIN {blnk=0;}; /^[[:space:]]*$/ {blnk=1; next; }; { while (/^\t* /) sub(/ /, "\t" ); if (blnk == 1 ) { print ""; blnk=0; }; print; }' $* else cat $* fi } # # Compile a Firewall Script # compile_firewall() # $1 = File Name { local IPTABLES_COMMAND IPTABLES_COMMAND=run_iptables local INDENT INDENT="" local checking checking= local outfile outfile=$1 local dir dir= local match match= setup_mss() { case $CLAMPMSS in Yes) option="--clamp-mss-to-pmtu" ;; *) option="--set-mss $CLAMPMSS" [ "$TCPMSS_MATCH" ] && match="-m tcpmss --mss $CLAMPMSS: " ;; esac run_iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN ${match}-j TCPMSS $option } progress_message2 "Initializing..." # # So that mktempdir doesn't have to jump through hoops when there isn't a working 'mktemp', # we create the compiler's temporary directory in TMP_DIR # STATEDIR=$TMP_DIR/compiler_state/ mkdir $STATEDIR || fatal_error "Cannot create temporary directory in $TMP_DIR" if [ $COMMAND = compile ]; then dir=$(dirname $1) [ -d $dir ] || fatal_error "Directory $dir does not exist" [ -h $dir ] && fatal_error "$dir is a Symbolic Link" [ -d $outfile ] && fatal_error "$outfile is a Directory" [ -h $outfile ] && fatal_error "$outfile is a Symbolic Link" [ -f $outfile -a ! -x $outfile ] && fatal_error "$outfile exists and is not a restore file" DOING=Compiling DONE=compiled OUTPUT=$(mktempfile $STATEDIR) [ -n "$OUTPUT" ] || fatal_error "Cannot create temporary file in /tmp" exec 3>>$OUTPUT else DOING=Checking DONE=checked checking=Yes COMMAND=compile exec 3>/dev/null fi cat >&3 << __EOF__ # # Compiled firewall script generated by Shorewall-shell $VERSION - $(date)" # __EOF__ if [ -n "$EXPORT" ]; then cat >&3 << __EOF__ SHAREDIR=/usr/share/shorewall-lite CONFDIR=/etc/shorewall-lite [ -f \${CONFDIR}/vardir ] && . \${CONFDIR}/vardir [ -n "\${VARDIR:=/var/lib/shorewall-lite}" ] __EOF__ cat ${SHAREDIR}/lib.base >&3 cat >&3 << __EOF__ ################################################################################ # End of ${SHAREDIR}/lib.base ################################################################################ __EOF__ else cat >&3 << __EOF__ SHAREDIR=/usr/share/shorewall CONFDIR=/etc/shorewall [ -f \${CONFDIR}/vardir ] && . \${CONFDIR}/vardir [ -n "\${VARDIR:=/var/lib/shorewall}" ] . \${SHAREDIR}/lib.base __EOF__ fi cat >&3 << __EOF__ # # Set policy of chain \$1 to \$2 # setpolicy() { \$IPTABLES -P \$1 \$2 } __EOF__ compile_stop_firewall cat >&3 << __EOF__ # # Remove all Shorewall-added rules # clear_firewall() { stop_firewall setpolicy INPUT ACCEPT setpolicy FORWARD ACCEPT setpolicy OUTPUT ACCEPT run_iptables -F echo 1 > /proc/sys/net/ipv4/ip_forward __EOF__ if [ -n "$DISABLE_IPV6" ]; then cat >&3 << __EOF__ if qt mywhich ip6tables; then ip6tables -P INPUT ACCEPT 2> /dev/null ip6tables -P OUTPUT ACCEPT 2> /dev/null ip6tables -P FORWARD ACCEPT 2> /dev/null fi __EOF__ fi append_file clear cat >&3 << __EOF__ set_state "Cleared" logger -p kern.info "\$PRODUCT Cleared" } # # Issue a message and stop/restore the firewall # fatal_error() { echo " ERROR: \$@" >&2 stop_firewall exit 2 } # # Issue a message and stop # startup_error() # \$* = Error Message { echo " ERROR: \$@" >&2 case \$COMMAND in start) logger -p kern.err "ERROR:\$PRODUCT start failed" ;; restart) logger -p kern.err "ERROR:\$PRODUCT restart failed" ;; restore) logger -p kern.err "ERROR:\$PRODUCT restore failed" ;; esac kill \$\$ exit 2 } # # Run iptables and if an error occurs, stop/restore the firewall # run_iptables() { if [ -n "\$COMMENT" ]; then \$IPTABLES \$@ -m comment --comment "\$COMMENT" else \$IPTABLES \$@ fi if [ \$? -ne 0 ]; then error_message "ERROR: Command \"\$IPTABLES \$@\" Failed" stop_firewall exit 2 fi } # # Run iptables and if an error occurs, stop/restore the firewall # run_ip() { if ! ip \$@; then error_message "ERROR: Command \"ip \$@\" Failed" stop_firewall exit 2 fi } # # Run tc and if an error occurs, stop/restore the firewall # run_tc() { if ! tc \$@ ; then error_message "ERROR: Command \"tc \$@\" Failed" stop_firewall exit 2 fi } # # Functions to appease unconverted extension scripts # save_command() { return 0 } run_and_save_command() { eval \$@ } ensure_and_save_command() { eval \$@ || fatal_error "Command \"\$@\" failed" } # # Initialize environment # initialize() { __EOF__ INDENT=" " if [ -n "$EXPORT" ]; then cat >&3 << __EOF__ # # These variables are required by the library functions called in this script # CONFIG_PATH="/etc/shorewall-lite:/usr/share/shorewall-lite" __EOF__ else cat >&3 << __EOF__ if [ ! -f \${SHAREDIR}/version ]; then fatal_error "This script requires Shorewall which do not appear to be installed on this system (did you forget "-e" when you compiled?)" fi local version version=\$(cat \${SHAREDIR}/version) if [ \${SHOREWALL_LIBVERSION:-0} -lt 30203 ]; then fatal_error "This script requires Shorewall version 3.3.3 or later; current version is \$version" fi # # These variables are required by the library functions called in this script # CONFIG_PATH="$CONFIG_PATH" __EOF__ fi cat >&3 << __EOF__ [ -n "\${COMMAND:=restart}" ] [ -n "\${VERBOSE:=0}" ] [ -n "\${RESTOREFILE:=$RESTOREFILE}" ] MODULESDIR="$MODULESDIR" MODULE_SUFFIX="$MODULE_SUFFIX" LOGLIMIT="$LOGLIMIT" LOGTAGONLY="$LOGTAGONLY" LOGRULENUMBERS="$LOGRULENUMBERS" __EOF__ if [ -n "$LOGFORMAT" ]; then cat >&3 << __EOF__ LOGFORMAT="$LOGFORMAT" __EOF__ else cat >&3 << __EOF__ [ -n "\$LOGFORMAT\" ] || LOGFORMAT="Shorewall:%s:%s:" __EOF__ fi cat >&3 << __EOF__ VERSION="$VERSION" SUBSYSLOCK="$SUBSYSLOCK" LOCKFILE="$LOCKFILE" PATH="$PATH" TERMINATOR=fatal_error DONT_LOAD="$DONT_LOAD" __EOF__ if [ -n "$IPTABLES" ]; then cat >&3 << __EOF__ IPTABLES="$IPTABLES" [ -x "$IPTABLES" ] || startup_error "IPTABLES=$IPTABLES does not exist or is not executable" __EOF__ else cat >&3 << __EOF__ [ -z "\$IPTABLES" ] && IPTABLES=\$(mywhich iptables 2> /dev/null) [ -n "\$IPTABLES" -a -x "\$IPTABLES" ] || startup_error "Can't find iptables executable" __EOF__ fi [ -n "$EXPORTPARAMS" ] && append_file params cat >&3 << __EOF__ STOPPING= COMMENT= # # The library requires that ${VARDIR} exist # [ -d \${VARDIR} ] || mkdir -p \${VARDIR} } __EOF__ report_capabilities if [ -n "$BRIDGING" ]; then [ -n "$PHYSDEV_MATCH" ] || fatal_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 fatal_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" ] && \ fatal_error "RFC1918_STRICT=Yes requires Connection Tracking match" progress_message2 "Determining Zones..." determine_zones if [ $VERBOSE -ge 1 ]; then display_list "IPv4 Zones:" $IPV4_ZONES [ -n "$IPSEC_ZONES" ] && \ display_list "IPSEC Zones:" $IPSEC_ZONES display_list "Firewall Zone:" $FW fi progress_message2 "Validating interfaces file..." validate_interfaces_file progress_message2 "Validating hosts file..." validate_hosts_file define_builtin_actions if [ -n "$USE_ACTIONS" ]; then progress_message2 "Pre-processing Actions..." process_actions1 fi progress_message2 "Validating Policy file..." validate_policy progress_message2 "Determining Hosts in Zones..." determine_interfaces determine_hosts if [ -s $TMP_DIR/tcrules ]; then progress_message2 "Compiling $(find_file tcrules)..." process_tc_rules fi if [ "$TC_ENABLED" = Internal ]; then [ -n "$LIB_tc_LOADED" ] && setup_traffic_shaping fi if [ -n "$(find_hosts_by_option blacklist)" ]; then process_blacklist fi cat >&3 << __EOF__ # # Start/Restart/Reload the firewall # define_firewall() { local restore_file restore_file=\$1 __EOF__ INDENT=" " save_progress_message "Initializing..." if [ -n "$EXPORT" ]; then f=$(find_file modules) if [ "$f" != ${SHAREDIR}/modules -a -f $f ]; then save_command 'echo MODULESDIR="$MODULESDIR" > ${VARDIR}/.modulesdir' save_command "cat > \${VARDIR}/.modules $LEFTSHIFT EOF" grep loadmodule $f | sed 's/^\s*//' >&3 save_command_unindented EOF save_command "reload_kernel_modules < \${VARDIR}/.modules" else save_command load_kernel_modules Yes fi else save_command load_kernel_modules Yes fi for interface in $ALL_INTERFACES; do if interface_has_option $interface norfc1918; then indent >&3 << __EOF__ addr=\$(ip -f inet addr show $interface 2> /dev/null | grep 'inet\ ' | head -n1) if [ -n "\$addr" ]; then addr=\$(echo \$addr | sed 's/inet //;s/\/.*//;s/ peer.*//') for network in 10.0.0.0/8 176.16.0.0/12 192.168.0.0/16; do if in_network \$addr \$network; then error_message "WARNING: The 'norfc1918' option has been specified on an interface with an RFC 1918 address. Interface:$interface" fi done fi __EOF__ fi done append_file init TERMINATOR=fatal_error deletechain shorewall if [ -n "$NAT_ENABLED" ]; then delete_nat for chain in PREROUTING POSTROUTING OUTPUT; do qt_iptables -t nat -P $chain ACCEPT done fi delete_proxy_arp if [ -n "$MANGLE_ENABLED" ]; then run_iptables -t mangle -F run_iptables -t mangle -X for chain in PREROUTING INPUT FORWARD POSTROUTING; do qt_iptables -t mangle -P $chain ACCEPT done fi if [ -n "$RAW_TABLE" ]; then run_iptables -t raw -F run_iptables -t raw -X for chain in PREROUTING OUTPUT; do qt_iptables -t raw -P $chain ACCEPT done fi [ -n "$CLEAR_TC" ] && delete_tc progress_message2 "Deleting user chains..." save_progress_message "Deleting user chains..." exists_INPUT=Yes exists_OUTPUT=Yes exists_FORWARD=Yes process_criticalhosts if [ -n "$CRITICALHOSTS" ]; then setpolicy INPUT ACCEPT setpolicy OUTPUT ACCEPT setpolicy FORWARD DROP deleteallchains enable_critical_hosts setpolicy INPUT DROP setpolicy OUTPUT DROP [ -n "$CLAMPMSS" ] && setup_mss setcontinue FORWARD setcontinue INPUT setcontinue OUTPUT else setpolicy INPUT DROP setpolicy OUTPUT DROP setpolicy FORWARD DROP deleteallchains [ -n "$CLAMPMSS" ] && setup_mss setcontinue FORWARD setcontinue INPUT setcontinue OUTPUT fi indent >&3 << __EOF__ f=\$(find_file ipsets) if [ -f \$f ]; then progress_message2 "Restoring IPSETS..." ipset -U :all: :all: ipset -U :all: :default: ipset -F ipset -X ipset -R < \$f fi __EOF__ append_file continue f=$(find_file routestopped) progress_message2 "$DOING $f ..." process_routestopped -A if [ -n "$DISABLE_IPV6" ]; then save_command disable_ipv6 fi save_progress_message "Enabling Loopback and DNS Lookups" # # Enable the Loopback interface for now # run_iptables -A INPUT -i lo -j ACCEPT run_iptables -A OUTPUT -o lo -j ACCEPT # # Allow DNS lookups during startup for FQDNs # for chain in INPUT OUTPUT FORWARD; do run_iptables -A $chain -p udp --dport 53 -j ACCEPT done [ -n "$LIB_accounting_LOADED" ] && setup_accounting $(find_file accounting) createchain reject no createchain dynamic no createchain logdrop no createchain logreject no createchain smurfs no log_rule ${BLACKLIST_LOGLEVEL:-info} logdrop DROP log_rule ${BLACKLIST_LOGLEVEL:-info} logreject REJECT run_iptables -A logdrop -j DROP run_iptables -A logreject -j reject indent >&3 << __EOF__ if [ -f \${VARDIR}/save ]; then progress_message2 "Setting up dynamic rules..." rangematch='source IP range' while read target ignore1 ignore2 address ignore3 rest; do case \$target in DROP|reject|logdrop|logreject) case \$rest in \$rangematch*) run_iptables -A dynamic -m iprange --src-range \${rest#source IP range} -j \$target ;; *) if [ -z "\$rest" ]; then run_iptables -A dynamic -s \$address -j \$target else error_message "WARNING: Unable to restore dynamic rule \"\$target \$ignore1 \$ignore2 \$address \$ignore3 \$rest\"" fi ;; esac ;; esac done < \${VARDIR}/save fi __EOF__ [ -n "$BLACKLISTNEWONLY" ] && state="-m state --state NEW,INVALID" || state= progress_message2 "Creating Interface Chains..." save_progress_message "Creating Interface Chains..." for interface in $ALL_INTERFACES; do for chain in $(input_chain $interface) $(forward_chain $interface); do createchain $chain no run_iptables -A $chain $state -j dynamic done createchain $(out_chain $interface) no done if [ -n "$LIB_proxyarp_LOADED" ]; then progress_message2 "$DOING Proxy ARP" setup_proxy_arp else > $STATEDIR/proxyarp fi # # [re]-Establish routing # if [ -s $TMP_DIR/providers ]; then setup_providers $(find_file providers) [ -n "$ROUTEMARK_INTERFACES" ] && setup_route_marking else save_command save_command undo_routing save_command restore_default_route fi if [ -s $TMP_DIR/nat ]; then progress_message2 "$DOING NAT..." setup_nat else > $STATEDIR/nat fi if [ -s $TMP_DIR/netmap ]; then progress_message2 "$DOING NETMAP..." setup_netmap fi progress_message2 "$DOING Common Rules" add_common_rules save_progress_message "Setting up SYN Flood Protection..." setup_syn_flood_chains setup_ipsec maclist_hosts=$(find_hosts_by_option maclist) if [ -n "$maclist_hosts" ]; then save_progress_message "Setting up MAC Filtration -- Phase 1..." setup_mac_lists 1 fi progress_message2 "$DOING $(find_file rules)..." save_progress_message "Setting up Rules..." process_rules if [ -s $TMP_DIR/tunnels ]; then tunnels=$(find_file tunnels) progress_message2 "$DOING $tunnels..." save_progress_message "Setting up Tunnels..." setup_tunnels $tunnels fi if [ -n "$DEFAULT_MACROS" ]; then progress_message2 "$DOING default macros..." save_progress_message "Creating default macro chains..." for macro in $DEFAULT_MACROS; do process_default_macro $macro done fi if [ -n "$USEDACTIONS" ]; then save_progress_message "Setting up Actions..." progress_message2 "$DOING Actions..."; [ -n "$USE_ACTIONS" ] && process_actions2 process_actions3 fi if [ -n "$maclist_hosts" ]; then save_progress_message "Setting up MAC Filtration -- Phase 2..." setup_mac_lists 2 fi save_progress_message "Applying Policies..." progress_message2 "$DOING $(find_file policy)..."; apply_policy_rules if [ -s $TMP_DIR/masq ]; then setup_masq $(find_file masq) fi if [ -n "$MANGLE_ENABLED" ]; then tos=$(find_file tos) [ -f $tos ] && process_tos $tos ecn=$(find_file ecn) [ -f $ecn ] && setup_ecn $ecn setup_tc fi progress_message2 "$DOING Rule Activation..." save_progress_message "Activating Rules..." activate_rules for file in chains nat proxyarp zones; do save_command "cat > \${VARDIR}/$file $LEFTSHIFT __EOF__" cat $STATEDIR/$file >&3 save_command_unindented __EOF__ done if [ -n "$ALIASES_TO_ADD" ]; then save_command add_ip_aliases $ALIASES_TO_ADD fi cat >&3 << __EOF__ if [ \$COMMAND = restore ]; then iptables-restore < \$restore_file fi __EOF__ setup_forwarding save_command "date > \${VARDIR}/restarted" append_file start if [ -n "$DELAYBLACKLISTLOAD" -a -s ${TMP_DIR}/blacklist ]; then save_command save_command progress_message2 \"Loading Black List...\" save_command load_blacklist save_command fi createchain shorewall no save_command set_state "Started" append_file started cat >&3 << __EOF__ cp -f \$(my_pathname) \${VARDIR}/.restore case \$COMMAND in start) logger -p kern.info "\$PRODUCT started" ;; restart) logger -p kern.info "\$PRODUCT restarted" ;; restore) logger -p kern.info "\$PRODUCT restored" ;; esac } # # Silently define Firewall and ignore errors # restore_firewall() { iptables_save_file=\${VARDIR}/\$(basename \$0)-iptables fatal_error() { echo " ERROR: \$@" >&2 } startup_error() # \$@ = Error Message { echo " ERROR: \$@" >&2 } run_iptables() { return 0; } VERBOSE=-1 # The progress messages don't make sense without iptables IPTABLES=run_iptables if [ -f \$iptables_save_file ]; then { define_firewall \$iptables_save_file } else fatal_error "\$iptables_save_file does not exist" exit 2 fi } __EOF__ compile_refresh_firewall exec 3>&- if [ -n "$checking" ]; then progress_message3 "Shorewall configuration verified" else INDENT= mycat ${SHELLSHAREDIR}/prog.header $OUTPUT ${SHELLSHAREDIR}/prog.footer > $outfile chmod 700 $outfile if [ -n "$EXPORT" ]; then exec 3>${outfile}.conf cat >&3 << __EOF__ # # Shorewall auxiliary configuration file created by Shorewall version $VERSION - $(date) # __EOF__ for option in VERBOSITY LOGFILE LOGFORMAT IPTABLES PATH SHOREWALL_SHELL SUBSYSLOCK RESTOREFILE LOCKFILE SAVE_IPSETS; do conditionally_add_option $option done conditionally_add_option1 TC_ENABLED exec 3>&- fi progress_message3 "Shorewall configuration compiled to $(resolve_file $outfile)" rm -f $OUTPUT fi rm -rf $TMP_DIR } # # Give Usage Information # usage() { echo "Usage: $0 [debug] check|compile }" 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" or "trace" # [ $# -gt 1 ] && [ "x$1" = xdebug -o "x$1" = xtrace ] && { set -x ; shift ; } NOLOCK= [ $# -gt 1 ] && [ "$1" = "nolock" ] && { NOLOCK=Yes; shift ; } trap "exit 2" 1 2 3 4 5 6 9 SHAREDIR=/usr/share/shorewall VARDIR=/var/lib/shorewall [ -z "$EXPORT" ] && CONFDIR=/etc/shorewall || CONFDIR=${SHAREDIR}/configfiles [ -n "${VERBOSE:=2}" ] for library in lib.base lib.config; do FUNCTIONS=${SHAREDIR}/${library} if [ -f $FUNCTIONS ]; then [ $VERBOSE -ge 2 ] && echo "Loading $FUNCTIONS..." . $FUNCTIONS else fatal_error "Installation Error: $FUNCTIONS does not exist!" fi done VERSION=$(cat $SHELLSHAREDIR/version) [ "$SHOREWALL_LIBVERSION" -eq $BASE_VERSION ] || fatal_error "Shorewall-shell $VERSION requires Shorewall-common lib.base version $BASE_VERSION_PRINTABLE" [ "$SHOREWALL_CONFIGVERSION" -eq $CONFIG_VERSION ] || fatal_error "Shorewall-shell $VERSION requires Shorewall-common lib.config version $CONFIG_VERSION_PRINTABLE" PROGRAM=compiler COMMAND="$1" case "$COMMAND" in check) [ $# -ne 1 ] && usage do_initialize compile_firewall ;; compile) [ $# -ne 2 ] && usage do_initialize compile_firewall $2 ;; call) # # Undocumented way to call functions in ${SHAREDIR}/compiler directly # shift do_initialize EMPTY= $@ ;; *) usage ;; esac