shorewall_code/Shorewall-common/compiler
2007-04-03 01:33:28 +00:00

5700 lines
132 KiB
Bash
Executable File

#!/bin/sh
#
# The Shoreline Firewall (Shorewall) Packet Filtering Firewall Compiler - V3.4
#
# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
# (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., 675 Mass Ave, Cambridge, MA 02139, 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 <path name> Compile into <path name>
#
# 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.
#
# 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=$(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 policychain
[ -n "$FASTACCEPT" ] || run_iptables -A $1 -m state --state $2 -j ACCEPT
if list_search RELATED $(separate_list $2) ; then
if is_policy_chain $1 ; then
if eval test -n \"\$${1}_synparams\" ; then
if [ $SECTION = DONE ]; then
eval policy=\$${1}_policy
case $policy in
ACCEPT|CONTINUE|QUEUE)
run_iptables -A $1 -p tcp --syn -j @$1
;;
esac
else
run_iptables -A $1 -p tcp --syn -j @$1
fi
fi
else
eval policychain=\$${1}_policychain
if eval test -n \"\$${policychain}_synparams\" ; then
run_iptables -A $1 -p tcp --syn -j @$policychain
fi
fi
fi
}
finish_section() # $1 = Section(s)
{
local zone zone1 chain
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=$(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=$(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=$(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)
save_progress_message "IP Forwarding Enabled"
save_command "echo 1 > /proc/sys/net/ipv4/ip_forward"
;;
Off|off)
save_progress_message "IP Forwarding Disabled!"
save_command "echo 0 > /proc/sys/net/ipv4/ip_forward"
;;
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=$1
local chain=$2
local displayChain=$3
local disposition=$4
local rulenum=
local limit=
local tag=$6
local command=${7:--A}
local prefix
local 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\" $@"
}
log_rule() # $1 = log level, $2 = chain, $3 = disposition , $... = predicates for the rule
{
local level=$1
local chain=$2
local 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=@$1
local limit=$2
local limit_burst=
case $limit in
*:*)
limit_burst="--limit-burst ${limit#*:}"
limit=${limit%:*}
;;
esac
run_iptables -N $chain
run_iptables -A $chain -m limit --limit $limit $limit_burst -j RETURN
[ -n "$3" ] && \
log_rule_limit $3 $chain $chain DROP "-m limit --limit 5/min --limit-burst 5" "" ""
run_iptables -A $chain -j DROP
}
setup_syn_flood_chains()
{
for chain in $ALL_POLICY_CHAINS; do
eval loglevel=\$${chain}_loglevel
eval synparams=\$${chain}_synparams
[ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams $loglevel
done
}
#
# Delete existing Proxy ARP
#
delete_proxy_arp() {
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=""
local 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
if [ -n "$MANGLE_FORWARD" ]; then
run_iptables -t mangle -A PREROUTING -p tcp -o $interface -j $chain
else
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
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=excl_${EXCLUSION_SEQ} net
EXCLUSION_SEQ=$(( $EXCLUSION_SEQ + 1 ))
run_iptables -t $2 -N $c
for net in $(separate_list $3); do
run_iptables -t $2 -A $c $(source_ip_range $net) -j RETURN
done
for net in $(separate_list $4); do
run_iptables -t $2 -A $c $(dest_ip_range $net) -j RETURN
done
case $2 in
filter)
eval exists_${c}=Yes
;;
nat)
eval exists_nat_${c}=Yes
;;
esac
eval $1=$c
}
#
# Setup queuing and classes
#
setup_tc1() {
local mark_part= 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
mark_part="-m mark --mark 0/0xFF00"
#
# 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=
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=""
local DOING="Compiling Refresh of"
local 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 "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=$(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 <action>, we maintain two variables:
#
# <action>_actchain - The action chain number.
# <action>_chains - List of ( level[:tag] , chainname ) pairs
#
# The maximum length of a chain name is 30 characters -- since the log
# action chain name is 2-3 characters longer than the base chain name,
# this function truncates the original chain name where necessary before
# it adds the leading "%" and trailing sequence number.
createlogactionchain() # $1 = Action Name, $2 = Log Level [: Log Tag ]
{
local actchain= action=$1 level=$2
eval actchain=\${${action}_actchain}
case ${#action} in
29|30)
CHAIN=$(echo $action | truncate 28) # %...n makes 30
;;
*)
CHAIN=${action}
;;
esac
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
;;
*:*)
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=$1 action=${1%%:*} level= chains=
find_simpleaction() {
havechain $action || fatal_error "Fatal error in find_logactionchain"
echo $action
}
case $fullaction in
*:*)
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=$1 subordinate=$2
set -- $(split $1)
case $superior in
*:*:*)
case $2 in
'none!')
echo ${subordinate%%:*}:'none!':$3
return
;;
*'!')
echo ${subordinate%%:*}:$2:$3
return
;;
*)
case $subordinate in
*:*:*)
echo $subordinate
return
;;
*:*)
echo $subordinate:$3
return
;;
*)
echo ${subordinate%%:*}:$2:$3
return
;;
esac
;;
esac
;;
*:*)
case $2 in
'none!')
echo ${subordinate%%:*}:'none!'
return
;;
*'!')
echo ${subordinate%%:*}:$2
return
;;
*)
case $subordinate in
*:*)
echo $subordinate
return
;;
*)
echo ${subordinate%%:*}:$2
return
;;
esac
;;
esac
;;
*)
echo $subordinate
;;
esac
}
#
# 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= 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=${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 <set name>,<max connections>,<interval> 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
# logtag = Log tag
# policy = Applicable Policy
#
add_a_rule() {
local natrule=
do_ports() {
if [ -n "$port" ]; then
dports="--dport"
if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then
multiport="$multioption"
dports="--dports"
fi
dports="$dports $port"
fi
if [ -n "$cport" ]; then
sports="--sport"
if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then
multiport="$multioption"
sports="--sports"
fi
sports="$sports $cport"
fi
}
interface_error()
{
fatal_error "Unknown interface $1 in rule: \"$rule\""
}
rule_interface_verify()
{
verify_interface $1 || interface_error $1
}
handle_exclusion()
{
build_exclusion_chain chain filter "$excludesource" "$excludedest"
if [ -n "$addr" -a -n "$CONNTRACK_MATCH" ]; then
for adr in $(separate_list $addr); do
run_iptables -A $logchain $state $(fix_bang $proto $sports $multiport $dports) $user -m conntrack --ctorigdst $adr -j $chain
done
addr=
else
run_iptables -A $logchain $state $(fix_bang $cli $proto $sports $multiport $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"
# 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" ] ; 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
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 $(dest_ip_range $srv) $dports -m conntrack --ctorigdst \$adr $user -j $target
done
__EOF__
else
for adr in $(separate_list $addr); do
if [ -n "$loglevel" -a -z "$natrule" ]; then
log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A -m conntrack --ctorigdst $adr \
$user $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) $state
fi
run_iptables2 -A $chain $state $proto $ratelimit $multiport $cli $sports \
$(dest_ip_range $srv) $dports -m conntrack --ctorigdst $adr $user -j $target
done
fi
else
if [ -n "$loglevel" -a -z "$natrule" ]; then
log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
$state $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports)
fi
if [ -n "$nonat" ]; then
addnatrule $(dnat_chain $source) $proto $multiport \
$cli $sports $(dest_ip_range $srv) $dports $ratelimit $user -j RETURN
fi
if [ "$logtarget" != NONAT ]; then
run_iptables2 -A $chain $state $proto $multiport $cli $sports \
$(dest_ip_range $srv) $dports $ratelimit $user -j $target
fi
fi
done
done
else
if [ -n "$loglevel" -a -z "$natrule" ]; then
log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
$state $(fix_bang $proto $sports $multiport $cli $dports)
fi
[ -n "$nonat" ] && \
addnatrule $(dnat_chain $source) $proto $multiport \
$cli $sports $dports $ratelimit $user -j RETURN
[ "$logtarget" != NONAT ] && \
run_iptables2 -A $chain $state $proto $multiport $cli $sports \
$dports $ratelimit $user -j $target
fi
fi
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
if [ -n "$loglevel" ]; then
log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
$state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports -m conntrack --ctorigdst $adr)
fi
if [ "$logtarget" != LOG ]; then
if [ -n "$nonat" ]; then
addnatrule $(dnat_chain $source) $proto $multiport \
$cli $sports $dports $ratelimit $user -m conntrack --ctorigdst $adr -j RETURN
fi
if [ "$logtarget" != NONAT ]; then
run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \
$sports $dports $ratelimit $user -m conntrack --ctorigdst $adr -j $target
fi
fi
done
else
if [ -n "$loglevel" ]; then
log_rule_limit $loglevel $chain $logchain $logtarget "$ratelimit" "$logtag" -A $user \
$state $(fix_bang $proto $multiport $cli $dest_interface $sports $dports)
fi
if [ "$logtarget" != LOG ]; then
if [ -n "$nonat" ]; then
addnatrule $(dnat_chain $source) $proto $multiport \
$cli $sports $dports $ratelimit $user -j RETURN
fi
if [ "$logtarget" != NONAT ]; then
run_iptables2 -A $chain $state $proto $multiport $cli $dest_interface \
$sports $dports $ratelimit $user -j $target
fi
fi
fi
fi
}
#
# 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
}
#
# 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
{
local target="$1"
local clients="$2"
local servers="$3"
local protocol="$4"
local ports="$5"
local cports="$6"
local address="$7"
local ratelimit="$8"
local userspec="$9"
local userandgroup=
local logtag=
local nonat=
# # # # # F u n c t i o n B o d y # # # # #
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
;;
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%!}
;;
ACCEPT+|NONAT)
[ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
nonat=Yes
target=ACCEPT
;;
ACCEPT|LOG)
;;
DROP)
[ -n "$ratelimit" ] && fatal_error "Rate Limiting not available with DROP"
;;
REJECT)
target=reject
;;
CONTINUE)
target=RETURN
;;
DNAT*|SAME*)
[ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
target=ACCEPT
address=${address:=detect}
;;
REDIRECT*)
[ $SECTION = NEW ] || fatal_error "REDIRECT rules are not allowed in the $SECTION SECTION"
target=ACCEPT
address=${address:=all}
if [ "x-" = "x$servers" ]; then
servers=$FW
else
servers="$FW::$servers"
fi
;;
*-)
[ $SECTION = NEW ] || fatal_error "$target rules are not allowed in the $SECTION SECTION"
;;
esac
# Parse and validate source
if [ "$clients" = "${clients%:*}" ]; then
clientzone="$clients"
clients=
else
clientzone="${clients%%:*}"
clients="${clients#*:}"
[ -z "$clientzone" -o -z "$clients" ] && \
fatal_error "Empty source zone or qualifier: rule \"$rule\""
fi
excludesource=
case $clients in
*!*!*)
fatal_error "Invalid SOURCE in rule \"$rule\""
;;
!*)
if [ $(list_count $clients) -gt 1 ]; then
excludesource=${clients#!}
clients=
fi
;;
*!*)
excludesource=${clients#*!}
clients=${clients%!*}
;;
esac
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
{
local itarget="$1"
local param="$2"
local iclients="$3"
local iservers="$4"
local iprotocol="$5"
local iports="$6"
local icports="$7"
local iaddress="$8"
local iratelimit="$9"
local iuserspec="${10}"
progress_message "..Expanding Macro $(find_file macro.${itarget%%:*})..."
while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do
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|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
done < $TMP_DIR/macro.${itarget%%:*}
progress_message "..End Macro"
}
#
# Process the rules file
#
process_rules()
{
local comment= 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 yservers ysourcezone ydestzone ypolicy
for yclients in $xclients; do
for yservers in $xservers; do
ysourcezone=${yclients%%:*}
ydestzone=${yservers%%:*}
if [ "${ysourcezone}" != "${ydestzone}" -o "$2" = Yes ] ; then
eval ypolicy=\$${ysourcezone}2${ydestzone}_policy
if [ "$ypolicy" != NONE ]; then
if [ $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
else
rule="$xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
process_rule $xtarget $yclients $yservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
fi
fi
fi
done
done
}
do_it() # $1 = "Yes" if the target is a macro.
{
local 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-)
xservers="$ZONES $FW"
process_wildcard_rule "$1" $intrazone
return
;;
esac
if [ "$1" = Yes ]; then
process_macro $xtarget "$xparam" $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
else
rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
process_rule $xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec
fi
}
while read xtarget xclients xservers xprotocol xports xcports xaddress xratelimit xuserspec; do
if [ "x$xclients" = xnone -o "x$servers" = xnone ]; then
rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xaddress $xratelimit $xuserspec"
progress_message " Rule \"$rule\" ignored."
continue
fi
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)
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"
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=$1
local address=
local multioption=
local servport=
local chain=$1
local logchain=$1
local userandgroup=
local logtag=
local excludesource=
local target client server protocol port cport ratelimit userspec rule
local 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}"
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 $tos
;;
all|ALL)
run_iptables2 -t mangle -A outtos \
$protocol $dest $dports $sports $tos
run_iptables2 -t mangle -A $chain \
$protocol $dest $dports $sports $tos
;;
*)
if [ -n "$src" ]; then
run_iptables2 -t mangle -A $chain $src \
$protocol $dest $dports $sports $tos
else
eval hosts=\$${srczone}_hosts
for host in $hosts; do
run_iptables2 -t mangle -A $chain $(match_source $host) \
$protocol $dest $dports $sports $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=pretos 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; do
rule="$(echo $src $dst $protocol $sport $dport $tos)"
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="$2"
local 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="${1}2${2}"
local policy=
local loglevel=
local chain1
jump_to_policy_chain() {
#
# Add a jump to from the canonical chain to the policy chain. On return,
# $chain is set to the name of the policy chain
#
run_iptables -A $chain -j $chain1
chain=$chain1
}
report_syn_flood_protection()
{
progress_message " Enabled SYN flood protection"
}
apply_default()
{
#
# Generate policy file column values 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=
local loglevel=
local policychain=
local default=
run_user_exit $1
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=${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=$BLACKLIST_DISPOSITION
local 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="$(find_hosts_by_option blacklist)"
local ipsec 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="$LOGPARMS"
local 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
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
is_bridge=$( brctl show 2> /dev/null | grep "^$interface[[:space:]]" )
[ -n "$is_bridge" ] && \
do_iptables -A $(forward_chain $interface) -p udp -o $interface --dport 67:68 -j ACCEPT
fi
run_iptables -A $(input_chain $interface) -p udp --dport 67:68 -j ACCEPT
run_iptables -A $(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
#
run_iptables2 -A $chain -m conntrack --ctorigdst $network -j $target
elif [ -n "$MANGLE_ENABLED" ]; then
#
# No connection tracking match but we have mangling -- add a rule to
# the mangle table
#
run_iptables2 -t mangle -A man1918 $(dest_ip_range $network) -j $target
fi
done
done < $TMP_DIR/rfc1918
[ -n "$RFC1918_STRICT" ] && run_iptables -A norfc1918 -j rfc1918d
for host in $hosts; do
ipsec=${host%^*}
host=${host#*^}
[ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
interface=${host%%:*}
networks=${host#*:}
for chain in $(first_chains $interface); do
run_iptables -A $chain -m state --state NEW $(match_source_hosts $networks) $policy -j norfc1918
done
[ -n "$MANGLE_ENABLED" -a -z "$CONNTRACK_MATCH" ] && \
run_iptables -t mangle -A PREROUTING -m state --state NEW -i $interface $(match_source_hosts $networks) $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..."
indent >&3 << __EOF__
for f in /proc/sys/net/ipv4/conf/*; do
[ -f \$f/log_martians ] && echo 0 > \$f/rp_filter
done
__EOF__
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 [ -n "$ROUTE_FILTER" ]; then
save_command "echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter"
save_command "echo 1 > /proc/sys/net/ipv4/conf/all/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..."
indent >&3 << __EOF__
for f in /proc/sys/net/ipv4/conf/*; do
[ -f \$f/log_martians ] && echo 0 > \$f/log_martians
done
__EOF__
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 [ -n "$LOG_MARTIANS" ]; then
save_command "echo 1 > /proc/sys/net/ipv4/conf/default/log_martians"
save_command "echo 1 > /proc/sys/net/ipv4/conf/all/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
setup_forwarding
}
#
# Scan the policy file defining the necessary chains
# Add the appropriate policy rule(s) to the end of each canonical chain
#
apply_policy_rules() {
#
# Create policy chains
#
for chain in $ALL_POLICY_CHAINS; do
eval policy=\$${chain}_policy
eval loglevel=\$${chain}_loglevel
eval optional=\$${chain}_is_optional
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=1
local POSTROUTING_rule=1
#
# Jump to a NAT chain from one of the builtin nat chains
#
addnatjump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments
{
local sourcechain=$1 destchain=$2
shift
shift
if havenatchain $destchain ; then
run_iptables2 -t nat -A $sourcechain $@ -j $destchain
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=$1 destchain=$2
shift
shift
if havenatchain $destchain; then
eval run_iptables2 -t nat -I $sourcechain \
\$${sourcechain}_rule $@ -j $destchain
eval ${sourcechain}_rule=\$\(\(\$${sourcechain}_rule + 1\)\)
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=$1 c=$2 num=0 host1 interface1 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=$1 c=$2 host1 interface1 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=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)
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
echo "$FW $zone $chain1" >> $STATEDIR/chains
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
interface_has_option $interface detectnets && need_broadcast="$need_broadcast $interface"
fi
fi
;;
esac
done
if [ -n "$chain1" ]; then
for interface in $need_broadcast ; do
run_iptables -A $(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 <source zone>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
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#*:}
chain3=$(forward_chain $interface)
for host1 in $dest_hosts; do
interface1=${host1%%:*}
networks1=${host1#*:}
if [ "$host" != "$host1" ] || list_search $host $routeback; then
run_iptables2 -A $chain3 $(match_source_hosts $networks) -o $interface1 $(match_dest_hosts $networks1) $(match_ipsec_out $zone1 $host1) -j $chain
fi
done
done
fi
done
#
# 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"
local 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=run_iptables
local INDENT=""
local checking= outfile=$1 dir=
setup_mss()
{
case $CLAMPMSS in
Yes)
option="--clamp-mss-to-pmtu"
;;
*)
option="--set-mss $CLAMPMSS"
;;
esac
run_iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS $option
}
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 $VERSION - $(date)"
#
__EOF__
if [ -n "$EXPORT" ]; then
cat >&3 << __EOF__
SHAREDIR=/usr/share/shorewall-lite
CONFDIR=/etc/shorewall-lite
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
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=\$(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"
PATH="$PATH"
TERMINATOR=fatal_error
__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=\$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
startup_error "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 -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 [ -s $TMP_DIR/proxyarp ]; 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
if [ -n "$ALIASES_TO_ADD" ]; then
save_command add_ip_aliases $ALIASES_TO_ADD
fi
for file in chains nat proxyarp zones; do
save_command "cat > \${VARDIR}/$file $LEFTSHIFT __EOF__"
cat $STATEDIR/$file >&3
save_command_unindented __EOF__
done
cat >&3 << __EOF__
if [ \$COMMAND = restore ]; then
iptables-restore < \$restore_file
fi
__EOF__
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 $(find_file prog.header) $OUTPUT $(find_file 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 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 <filename>}"
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"
#
debug='-w'
[ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; }
if [ "$PROFILE" ]; then
debug='-wd:DProf'
elif [ "$DEBUG" ]; then
debug='-wd'
fi
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
PROGRAM=compiler
COMMAND="$1"
case "$COMMAND" in
check)
[ $# -ne 1 ] && usage
do_initialize
[ "$SHOREWALL_COMPILER" = perl ] && exec perl $debug /usr/share/shorewall-perl/compiler.pl || compile_firewall
;;
compile)
[ $# -ne 2 ] && usage
do_initialize
[ "$SHOREWALL_COMPILER" = perl ] && exec perl $debug /usr/share/shorewall-perl/compiler.pl $2 || compile_firewall $2
;;
call)
#
# Undocumented way to call functions in ${SHAREDIR}/compiler directly
#
shift
do_initialize
EMPTY=
$@
;;
*)
usage
;;
esac