shorewall_code/Shorewall/firewall
2005-09-19 14:17:29 +00:00

9372 lines
222 KiB
Bash
Executable File

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