forked from extern/shorewall_code
5d60471420
git-svn-id: https://shorewall.svn.sourceforge.net/svnroot/shorewall/trunk@321 fbd18981-670d-0410-9b5c-8dc0c1a9a2bb
4334 lines
94 KiB
Bash
Executable File
4334 lines
94 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V1.3 6/14/2002
|
|
#
|
|
# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
|
|
#
|
|
# (c) 1999,2000,2001,2002 - Tom Eastep (teastep@shorewall.net)
|
|
#
|
|
# On most distributions, this file should be called:
|
|
# /etc/rc.d/init.d/shorewall or /etc/init.d/shorewall
|
|
#
|
|
# Complete documentation is available at http://shorewall.net
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of Version 2 of the GNU General Public License
|
|
# as published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
|
|
#
|
|
# If an error occurs while starting or restarting the firewall, the
|
|
# firewall is automatically stopped.
|
|
#
|
|
# Commands are:
|
|
#
|
|
# shorewall start Starts the firewall
|
|
# shorewall restart Restarts the firewall
|
|
# shorewall stop Stops the firewall
|
|
# shorewall status Displays firewall status
|
|
# shorewall reset Resets iptabless 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.
|
|
|
|
#
|
|
# Search a list looking for a match -- returns zero if a match found
|
|
# 1 otherwise
|
|
#
|
|
list_search() # $1 = element to search for , $2-$n = list
|
|
{
|
|
local e=$1
|
|
|
|
while [ $# -gt 1 ]; do
|
|
shift
|
|
[ "x$e" = "x$1" ] && return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
#
|
|
# Function to count list elements
|
|
#
|
|
list_count() {
|
|
local temp="`separate_list $1`"
|
|
|
|
echo $temp | wc -w
|
|
}
|
|
|
|
#
|
|
# Mutual exclusion -- These functions are jackets for the mutual exclusion
|
|
# routines in /usr/lib/shorewall/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 " $@" >&2
|
|
stop_firewall
|
|
exit 2
|
|
}
|
|
|
|
#
|
|
# Fatal error during startup -- generate an error message and abend with
|
|
# altering the state of the firewall
|
|
#
|
|
startup_error() # $* = Error Message
|
|
{
|
|
echo " $@" >&2
|
|
my_mutex_off
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
kill $$
|
|
exit 2
|
|
}
|
|
|
|
#
|
|
# Send a message to STDOUT and the System Log
|
|
#
|
|
report () { # $* = message
|
|
echo "$@"
|
|
logger "$@"
|
|
}
|
|
|
|
#
|
|
# Perform variable substitution on the passed argument and echo the result
|
|
#
|
|
expand() # $1 = contents of variable which may be the name of another variable
|
|
{
|
|
eval echo \"$1\"
|
|
}
|
|
|
|
#
|
|
# Perform variable substitition on the values of the passed list of variables
|
|
#
|
|
expandv() # $* = list of variable names
|
|
{
|
|
local varval
|
|
|
|
while [ $# -gt 0 ]; do
|
|
eval varval=\$${1}
|
|
eval $1=\"$varval\"
|
|
shift
|
|
done
|
|
}
|
|
|
|
#
|
|
# Run iptables and if an error occurs, stop the firewall and quit
|
|
#
|
|
run_iptables() {
|
|
if ! iptables `echo $@ | sed 's/!/! /g'`; then
|
|
[ -z "$stopping" ] && { stop_firewall; exit 2; }
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Run ip and if an error occurs, stop the firewall and quit
|
|
#
|
|
run_ip() {
|
|
if ! ip $@ ; then
|
|
[ -z "$stopping" ] && { stop_firewall; exit 2; }
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Run arp and if an error occurs, stop the firewall and quit
|
|
#
|
|
run_arp() {
|
|
if ! arp $@ ; then
|
|
[ -z "$stopping" ] && { stop_firewall; exit 2; }
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Run tc and if an error occurs, stop the firewall and quit
|
|
#
|
|
run_tc() {
|
|
if ! tc $@ ; then
|
|
[ -z "$stopping" ] && { stop_firewall; exit 2; }
|
|
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 ${1}_exists and set its value to Yes to indicate that the chain now
|
|
# exists.
|
|
#
|
|
createchain() # $1 = chain name, $2 = If non-null, don't create default rules
|
|
{
|
|
local target
|
|
|
|
run_iptables -N $1
|
|
|
|
if [ $# -eq 1 ]; then
|
|
state="ESTABLISHED"
|
|
[ -n "$ALLOWRELATED" ] && state="$state,RELATED"
|
|
run_iptables -A $1 -m state --state $state -j ACCEPT
|
|
[ -z "$NEWNOTSYN" ] && \
|
|
run_iptables -A $1 -m state --state NEW -p tcp !--syn -j newnotsyn
|
|
fi
|
|
|
|
eval ${1}_exists=Yes
|
|
}
|
|
|
|
#
|
|
# Determine if a chain exists
|
|
#
|
|
# When we create a chain "chain", we create a variable named chain_exists 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
|
|
{
|
|
eval test \"\$${1}_exists\" = Yes
|
|
}
|
|
|
|
#
|
|
# Ensure that a chain exists (create it if it doesn't)
|
|
#
|
|
ensurechain() # $1 = chain name
|
|
{
|
|
havechain $1 || createchain $1
|
|
}
|
|
|
|
#
|
|
# 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 $@
|
|
}
|
|
|
|
#
|
|
# Create a nat chain
|
|
#
|
|
# Create a variable ${1}_nat_exists and set its value to Yes to indicate that
|
|
# the chain now exists.
|
|
#
|
|
createnatchain() # $1 = chain name
|
|
{
|
|
run_iptables -t nat -N $1
|
|
|
|
eval ${1}_nat_exists=Yes
|
|
}
|
|
|
|
#
|
|
# Determine if a nat chain exists
|
|
#
|
|
# When we create a chain "chain", we create a variable named chain_nat_exists
|
|
# 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 \"\$${1}_nat_exists\" = Yes
|
|
}
|
|
|
|
#
|
|
# Ensure that a 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_iptables -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
|
|
}
|
|
|
|
#
|
|
# 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 connections
|
|
#
|
|
setcontinue() # $1 = name of chain
|
|
{
|
|
run_iptables -A $1 -m state --state ESTABLISHED -j ACCEPT
|
|
}
|
|
|
|
#
|
|
# Flush one of the NAT table chains
|
|
#
|
|
flushnat() # $1 = name of chain
|
|
{
|
|
run_iptables -t nat -F $1
|
|
}
|
|
|
|
#
|
|
# Find interfaces to a given zone
|
|
#
|
|
# Read 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
|
|
|
|
while read z interface subnet options; do
|
|
[ "x`expand $z`" = "x$zne" ] && echo `expand $interface`
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Chain name base for an interface
|
|
#
|
|
chain_base() #$1 = interface
|
|
{
|
|
local c=${1%%+*}
|
|
|
|
echo ${c:=common}
|
|
}
|
|
|
|
#
|
|
# 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
|
|
}
|
|
|
|
#
|
|
# Input Chains (input and forward) for an interface
|
|
#
|
|
input_chains() # $1 = interface
|
|
{
|
|
local base=`chain_base $1`
|
|
|
|
echo ${base}_in ${base}_fwd
|
|
}
|
|
|
|
#
|
|
# 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
|
|
}
|
|
|
|
#
|
|
# DNAT Chain from a zone
|
|
#
|
|
dnat_chain() # $1 = zone
|
|
{
|
|
echo ${1}_dnat
|
|
}
|
|
|
|
#
|
|
# SNAT Chain to a zone
|
|
#
|
|
snat_chain() # $1 = zone
|
|
{
|
|
echo ${1}_snat
|
|
}
|
|
|
|
#
|
|
# First chains for an interface
|
|
#
|
|
first_chains() #$1 = interface
|
|
{
|
|
local c=`chain_base $1`
|
|
|
|
echo ${c}_fwd ${c}_in
|
|
}
|
|
|
|
#
|
|
# 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
|
|
|
|
while read z hosts options; do
|
|
[ "x`expand $z`" = "x$1" ] && expandv hosts && echo `separate_list $hosts`
|
|
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 the defined hosts in each zone and generate report
|
|
#
|
|
determine_hosts() {
|
|
do_a_zone()
|
|
{
|
|
eval interfaces=\$${zone}_interfaces
|
|
|
|
for interface in $interfaces; do
|
|
if [ -z "$hosts" ]; then
|
|
hosts=$interface:0.0.0.0/0
|
|
else
|
|
hosts="$hosts $interface:0.0.0.0/0"
|
|
fi
|
|
done
|
|
}
|
|
|
|
recalculate_interfaces()
|
|
{
|
|
interfaces=
|
|
|
|
for host in $hosts; do
|
|
interface=${host%:*}
|
|
if ! list_search $interface $interfaces; then
|
|
if [ -z "$interfaces" ]; then
|
|
interfaces=$interface
|
|
else
|
|
interfaces="$interfaces $interface"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
eval ${zone}_interfaces="\$interfaces"
|
|
}
|
|
|
|
for zone in $zones; do
|
|
hosts=`find_hosts $zone`
|
|
hosts=`echo $hosts` # Remove extra trash
|
|
|
|
if [ -n "MERGE_HOSTS" ]; then
|
|
#
|
|
# Zone will be the union of its host and interface definitions
|
|
#
|
|
do_a_zone
|
|
recalculate_interfaces
|
|
elif [ -n "$hosts" ]; then
|
|
#
|
|
# Zone is defined in terms of hosts -- derive the interface list
|
|
# from the host list
|
|
#
|
|
recalculate_interface
|
|
else
|
|
#
|
|
# If no hosts are defined for a zone then the zone consists of any
|
|
# host that can send us messages via the interfaces to the zone
|
|
#
|
|
do_a_zone
|
|
fi
|
|
|
|
eval ${zone}_hosts="\$hosts"
|
|
|
|
if [ -n "$hosts" ]; then
|
|
eval display=\$${zone}_display
|
|
display_list "$display Zone:" $hosts
|
|
else
|
|
error_message "Warning: Zone $zone is empty"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# Ensure that the passed zone is defined in the zones file or is the firewall
|
|
#
|
|
validate_zone() # $1 = zone
|
|
{
|
|
list_search $1 $zones $FW
|
|
}
|
|
|
|
#
|
|
# Validate the zone names and options in the interfaces file
|
|
#
|
|
validate_interfaces_file() {
|
|
while read z interface subnet options; do
|
|
expandv z interface subnet options
|
|
r="$z $interface $subnet $options"
|
|
[ "x$z" = "x-" ] || validate_zone $z || startup_error "Invalid zone ($z) in record \"$r\""
|
|
|
|
list_search $interface $all_interfaces && \
|
|
startup_error "Error: Duplicate Interface $interface"
|
|
|
|
all_interfaces="$all_interfaces $interface"
|
|
|
|
for option in `separate_list $options`; do
|
|
case $option in
|
|
dhcp|noping|filterping|routestopped|norfc1918|multi)
|
|
;;
|
|
routefilter|dropunclean|logunclean|blacklist|proxyarp|maclist|-)
|
|
;;
|
|
*)
|
|
error_message "Warning: Invalid option ($option) in record \"$r\""
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[ -z "$all_interfaces" ] && startup_error "Error: No Interfaces Defined"
|
|
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Validate the zone names and options in the hosts file
|
|
#
|
|
validate_hosts_file() {
|
|
while read z hosts options; do
|
|
expandv z hosts options
|
|
r="$z $hosts $options"
|
|
validate_zone $z || startup_error "Invalid zone ($z) in record \"$r\""
|
|
|
|
for host in `separate_list $hosts`; do
|
|
interface=${host%:*}
|
|
|
|
list_search $interface $all_interfaces || \
|
|
startup_error "Unknown interface ($interface) in record \"$r\""
|
|
|
|
for option in `separate_list $options`; do
|
|
case $option in
|
|
routestopped|maclist|-)
|
|
;;
|
|
*)
|
|
error_message "Warning: Invalid option ($option) in record \"$r\""
|
|
;;
|
|
esac
|
|
done
|
|
done
|
|
done < $TMP_DIR/hosts
|
|
}
|
|
|
|
#
|
|
# 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 a record from the rules file
|
|
#
|
|
# The caller has loaded the column contents from the record into the following
|
|
# variables:
|
|
#
|
|
# target clients servers protocol ports cports address
|
|
#
|
|
# and has loaded a space-separated list of their values in "rule".
|
|
#
|
|
validate_rule() {
|
|
#
|
|
# Ensure that the passed comma-separated list has 15 or fewer elements
|
|
#
|
|
validate_list() {
|
|
local temp="`separate_list $1`"
|
|
|
|
[ `echo $temp | wc -w` -le 15 ]
|
|
}
|
|
|
|
#
|
|
# validate one rule
|
|
#
|
|
validate_a_rule() {
|
|
#
|
|
# Determine the format of the client
|
|
#
|
|
cli=
|
|
|
|
[ -n "$client" ] && case "$client" in
|
|
-)
|
|
;;
|
|
*:*)
|
|
cli="-i ${client%:*} -s ${client#*:}"
|
|
;;
|
|
~*)
|
|
cli=`mac_match $client`
|
|
;;
|
|
*.*.*)
|
|
#
|
|
# IP Address, address or subnet
|
|
#
|
|
cli="-s $client"
|
|
;;
|
|
*)
|
|
#
|
|
# Assume that this is a device name
|
|
#
|
|
cli="-i $client"
|
|
;;
|
|
esac
|
|
|
|
dest_interface=
|
|
|
|
[ -n "$server" ] && case "$server" in
|
|
-)
|
|
serv=
|
|
;;
|
|
*.*.*)
|
|
serv=$server
|
|
;;
|
|
~*)
|
|
fatal_error "Error: Rule \"$rule\" - Server may not be specified by MAC Address"
|
|
;;
|
|
*)
|
|
dest_interface="-o $server"
|
|
serv=
|
|
;;
|
|
esac
|
|
#
|
|
# Setup PROTOCOL, PORT and STATE variables
|
|
#
|
|
sports=""
|
|
dports=""
|
|
state="-m state --state NEW"
|
|
proto=$protocol
|
|
addr=$address
|
|
servport=$serverport
|
|
|
|
case $proto in
|
|
tcp|udp|TCP|UDP|6|17)
|
|
[ -n "$port" ] && [ "x${port}" != "x-" ] && \
|
|
dports="--dport $port"
|
|
[ -n "$cport" ] && [ "x${cport}" != "x-" ] && \
|
|
sports="--sport $cport"
|
|
;;
|
|
icmp|ICMP|0)
|
|
[ -n "$port" ] && dports="--icmp-type $port"
|
|
state=""
|
|
;;
|
|
related|RELATED)
|
|
proto=
|
|
state="-m state --state RELATED"
|
|
;;
|
|
*)
|
|
state=
|
|
[ -n "$port" ] && [ "x${port}" != "x-" ] && \
|
|
startup_error "Port number not allowed with protocol " \
|
|
"\"$proto\"; rule: \"$rule\""
|
|
;;
|
|
esac
|
|
|
|
proto="${proto:+-p $proto}"
|
|
|
|
case "$logtarget" in
|
|
REJECT)
|
|
target=reject
|
|
[ -n "$servport" ] && \
|
|
startup_error "Error: server port may not be specified in a REJECT rule;"\
|
|
"rule: \"$rule\""
|
|
;;
|
|
ACCEPT)
|
|
[ -n "$servport" ] && \
|
|
startup_error "Error: server port may not be specified in an ACCEPT rule;"\
|
|
"rule: \"$rule\""
|
|
;;
|
|
REDIRECT)
|
|
[ -n "$serv" ] && startup_error "Error: REDIRECT rules cannot"\
|
|
" specify a server IP; rule: \"$rule\""
|
|
servport=${servport:=$port}
|
|
;;
|
|
DNAT)
|
|
[ -n "$serv" ] || startup_error "Error: DNAT rules require a" \
|
|
" server address; rule: \"$rule\""
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" ]; then
|
|
error_message "Warning -- Rule \"$rule\" is a POLICY"
|
|
error_message " -- and should be moved to the policy file"
|
|
fi
|
|
|
|
if [ -n "${serv}${servport}" ]; then
|
|
#
|
|
# Destination is a Specific Server or we're redirecting a port
|
|
#
|
|
if [ -n "$addr" -a "$addr" != "$serv" ]; then
|
|
#
|
|
# Must use Prerouting DNAT
|
|
#
|
|
if [ -z "$NAT_ENABLED" ]; then
|
|
startup_error \
|
|
"Error - Rule \"$rule\" requires NAT which is disabled"
|
|
fi
|
|
|
|
if [ "$target" != "ACCEPT" ]; then
|
|
startup_error "Error - Only ACCEPT rules may specify " \
|
|
"port mapping; rule \"$rule\""
|
|
fi
|
|
fi
|
|
else
|
|
[ -n "$addr" ] && startup_error \
|
|
"Error: An ADDRESS ($addr) is only allowed in" \
|
|
" a DNAT or REDIRECT rule: \"$rule\""
|
|
fi
|
|
}
|
|
#
|
|
# V a l i d a t e _ R u l e S t a r t s H e r e
|
|
#
|
|
# Parse the Target and Clients columns
|
|
#
|
|
if [ "$target" = "${target%:*}" ]; then
|
|
loglevel=
|
|
else
|
|
loglevel="${target#*:}"
|
|
target="${target%:*}"
|
|
expandv loglevel
|
|
fi
|
|
|
|
logtarget="$target"
|
|
#
|
|
# DNAT and REDIRECT targets were implemented in version 1.3 to replace
|
|
# an older syntax. We simply map the new syntax into the old and proceed;
|
|
# that way, people who have files with the old syntax don't need to
|
|
# convert right away.
|
|
#
|
|
case $target in
|
|
DNAT)
|
|
target=ACCEPT
|
|
address=${address:=detect}
|
|
;;
|
|
REDIRECT)
|
|
target=ACCEPT
|
|
address=${address:=all}
|
|
if [ "x-" = "x$servers" ]; then
|
|
servers=$FW
|
|
else
|
|
servers="fw::$servers"
|
|
fi
|
|
;;
|
|
ACCEPT|DROP|REJECT)
|
|
;;
|
|
*)
|
|
startup_error "Error: Invalid target;" \
|
|
" rule: \"$rule\""
|
|
|
|
esac
|
|
|
|
if [ "$clients" = "${clients%:*}" ]; then
|
|
clientzone="$clients"
|
|
clients=
|
|
else
|
|
clientzone="${clients%%:*}"
|
|
clients="${clients#*:}"
|
|
[ -z "$clientzone" -o -z "$clients" ] && \
|
|
startup_error "Error: Empty source zone or qualifier: rule \"$rule\""
|
|
fi
|
|
|
|
if [ "$clientzone" = "${clientzone%\!*}" ]; then
|
|
excludezones=
|
|
else
|
|
excludezones="${clientzone#*\!}"
|
|
clientzone="${clientzone%\!*}"
|
|
|
|
[ "$logtarget" = DNAT ] || [ "$logtarget" = REDIRECT ] ||\
|
|
startup_error "Error: Exclude list only allowed with DNAT or REDIRECT"
|
|
fi
|
|
#
|
|
# Validate the Source Zone
|
|
#
|
|
if ! validate_zone $clientzone; then
|
|
startup_error "Error: Undefined Client Zone in rule \"$rule\""
|
|
fi
|
|
|
|
source=$clientzone
|
|
|
|
[ $source = $FW ] && source_hosts= || eval source_hosts=\"\$${source}_hosts\"
|
|
|
|
#
|
|
# Parse the servers column
|
|
#
|
|
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" ] && \
|
|
startup_error "Error: Empty destination zone or server port: rule \"$rule\""
|
|
else
|
|
serverport=
|
|
[ -z "$serverzone" -o -z "$servers" ] && \
|
|
startup_error "Error: Empty destination zone or qualifier: rule \"$rule\""
|
|
fi
|
|
fi
|
|
#
|
|
# Validate the destination zone
|
|
#
|
|
if ! validate_zone $serverzone; then
|
|
startup_error "Error: Undefined Server Zone in rule \"$rule\""
|
|
fi
|
|
|
|
dest=$serverzone
|
|
#
|
|
# Check length of port lists if MULTIPORT set
|
|
#
|
|
if [ -n "$MULTIPORT" ]; then
|
|
validate_list $ports ||
|
|
error_message "Warning: Too many destination ports: Rule \"$rule\""
|
|
validate_list $cports ||
|
|
error_message "Warning: Too many source ports: Rule \"$rule\""
|
|
fi
|
|
|
|
#
|
|
# Iterate through the various lists validating individual rules
|
|
#
|
|
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
|
|
validate_a_rule
|
|
done
|
|
done
|
|
done
|
|
done
|
|
|
|
echo " Rule \"$rule\" validated."
|
|
}
|
|
|
|
#
|
|
# validate the rules file
|
|
#
|
|
validate_rules() # $1 = name of rules file
|
|
{
|
|
strip_file rules
|
|
|
|
while read target clients servers protocol ports cports address; do
|
|
expandv clients servers protocol ports cports address
|
|
case "$target" in
|
|
|
|
ACCEPT*|DROP*|REJECT*|DNAT*|REDIRECT*)
|
|
rule="`echo $target $clients $servers $protocol $ports $cports $address`"
|
|
validate_rule
|
|
;;
|
|
*)
|
|
rule="`echo $target $clients $servers $protocol $ports $cports $address`"
|
|
startup_error "Error: Invalid Target - rule \"$rule\" ignored"
|
|
;;
|
|
esac
|
|
done < $TMP_DIR/rules
|
|
}
|
|
|
|
#
|
|
# validate the policy file
|
|
#
|
|
validate_policy()
|
|
{
|
|
strip_file policy $policy
|
|
|
|
while read client server policy loglevel synparams; do
|
|
expandv client server policy loglevel synparams
|
|
case "$client" in
|
|
all|ALL)
|
|
;;
|
|
*)
|
|
if ! validate_zone $client; then
|
|
startup_error "Error: Undefined zone $client"
|
|
fi
|
|
esac
|
|
|
|
case "$server" in
|
|
all|ALL)
|
|
;;
|
|
*)
|
|
if ! validate_zone $server; then
|
|
startup_error "Error: Undefined zone $server"
|
|
fi
|
|
esac
|
|
|
|
case $policy in
|
|
ACCEPT|REJECT|DROP|CONTINUE)
|
|
;;
|
|
*)
|
|
startup_error "Error: Invalid policy $policy"
|
|
;;
|
|
esac
|
|
|
|
done < $TMP_DIR/policy
|
|
}
|
|
|
|
#
|
|
# Find broadcast addresses
|
|
#
|
|
find_broadcasts() {
|
|
while read z interface bcast options; do
|
|
expandv interface bcast
|
|
if [ "x$bcast" = "xdetect" ]; then
|
|
addr="`ip addr show $interface 2> /dev/null`"
|
|
if [ -n "`echo "$addr" | grep 'inet.*brd '`" ]; then
|
|
addr="`echo "$addr" | \
|
|
grep "inet " | sed 's/^.* inet.*brd //;s/scope.*//'`"
|
|
echo $addr | cut -d' ' -f 1
|
|
fi
|
|
elif [ "x${bcast}" != "x-" ]; then
|
|
echo `separate_list $bcast`
|
|
fi
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Find interface broadcast addresses
|
|
#
|
|
find_interface_broadcasts() # $1 = Interface name
|
|
{
|
|
while read z interface bcast options; do
|
|
expandv interface bcast
|
|
if [ "$interface" = "$1" ]; then
|
|
if [ "x$bcast" = "xdetect" ]; then
|
|
addr="`ip addr show $interface 2> /dev/null`"
|
|
if [ -n "`echo "$addr" | grep 'inet.*brd '`" ]; then
|
|
addr="`echo "$addr" | \
|
|
grep "inet " | sed 's/^.* inet.*brd //;s/scope.*//'`"
|
|
echo $addr | cut -d' ' -f 1
|
|
fi
|
|
elif [ "x${bcast}" != "x-" ]; then
|
|
echo `separate_list $bcast`
|
|
fi
|
|
|
|
return
|
|
fi
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Find interface address--returns the first IP address assigned to the passed
|
|
# device
|
|
#
|
|
find_interface_address() # $1 = interface
|
|
{
|
|
#
|
|
# get the line of output containing the first IP address
|
|
#
|
|
addr=`ip 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
|
|
{
|
|
while read ignore interface subnet options; do
|
|
expandv options
|
|
list_search $1 `separate_list $options` && \
|
|
echo `expand $interface`
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Find hosts with the passed option
|
|
#
|
|
find_hosts_by_option() # $1 = option
|
|
{
|
|
while read ignore hosts options; do
|
|
expandv options
|
|
list_search $1 `separate_list $options` && \
|
|
echo `expand $hosts`
|
|
done < $TMP_DIR/hosts
|
|
|
|
while read ignore interface ignore1 options; do
|
|
expandv options
|
|
list_search $1 `separate_list $options` && \
|
|
echo `expand $interface`:0.0.0.0/0
|
|
done < $TMP_DIR/interfaces
|
|
}
|
|
|
|
#
|
|
# Determine if there are interfaces of the given zone and option
|
|
#
|
|
# Returns zero if any such interfaces are found and returns one otherwise.
|
|
#
|
|
have_interfaces_in_zone_with_option() # $1 = zone, $2 = option
|
|
{
|
|
local zne=$1
|
|
|
|
while read z interface broadcast options; do
|
|
[ "x`expand $z`" = "x$zne" ] && expandv options && \
|
|
list_search $1 `separate_list $options` && \
|
|
return 0
|
|
done < $TMP_DIR/interfaces
|
|
return 1
|
|
}
|
|
|
|
#
|
|
# 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
|
|
echo "Processing $user_exit ..."
|
|
. $user_exit
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Stop the Firewall
|
|
#
|
|
stop_firewall() {
|
|
stopping="Yes"
|
|
|
|
deletechain shorewall
|
|
|
|
run_user_exit stop
|
|
|
|
[ -n "$MANGLE_ENABLED" ] && \
|
|
run_iptables -t mangle -F && \
|
|
run_iptables -t mangle -X
|
|
|
|
[ -n "$NAT_ENABLED" ] && delete_nat
|
|
delete_proxy_arp
|
|
[ -n "$TC_ENABLED" ] && delete_tc
|
|
|
|
setpolicy INPUT DROP
|
|
setpolicy OUTPUT DROP
|
|
setpolicy FORWARD DROP
|
|
|
|
deleteallchains
|
|
|
|
hosts="`find_hosts_by_option routestopped`"
|
|
|
|
strip_file routestopped
|
|
|
|
while read interface host; do
|
|
expandv interface host
|
|
[ "x$host" = "x-" ] && host=
|
|
hosts="$hosts $interface:${host:-0.0.0.0/0}"
|
|
done < $TMP_DIR/routestopped
|
|
|
|
for host in $hosts; do
|
|
interface=${host%:*}
|
|
subnet=${host#*:}
|
|
iptables -A INPUT -i $interface -s $subnet -j ACCEPT
|
|
iptables -A OUTPUT -o $interface -d $subnet -j ACCEPT
|
|
|
|
for host1 in $hosts; do
|
|
[ "$host" != "$host1" ] && \
|
|
iptables -A FORWARD -i $interface -s $subnet \
|
|
-o ${host1%:*} -d ${host1#*:} -j ACCEPT
|
|
done
|
|
done
|
|
|
|
iptables -A INPUT -i lo -j ACCEPT
|
|
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
|
|
iptables -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT
|
|
done
|
|
|
|
case "$IP_FORWARDING" in
|
|
[Oo][Nn])
|
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
;;
|
|
[Oo][Ff][Ff])
|
|
echo 0 > /proc/sys/net/ipv4/ip_forward
|
|
;;
|
|
esac
|
|
|
|
run_user_exit stopped
|
|
|
|
logger "Shorewall Stopped"
|
|
|
|
rm -rf $TMP_DIR
|
|
|
|
case $command in
|
|
stop|clear)
|
|
;;
|
|
*)
|
|
#
|
|
# The firewall is being stopped when we were trying to do something
|
|
# else. Remove the lock file and Kill the shell in case we're in a
|
|
# subshell
|
|
#
|
|
my_mutex_off
|
|
kill $$
|
|
;;
|
|
esac
|
|
}
|
|
|
|
#
|
|
# Remove all rules and remove all user-defined chains
|
|
#
|
|
clear_firewall() {
|
|
stop_firewall
|
|
|
|
run_iptables -F
|
|
|
|
echo 1 > /proc/sys/net/ipv4/ip_forward
|
|
|
|
setpolicy INPUT ACCEPT
|
|
setpolicy FORWARD ACCEPT
|
|
setpolicy OUTPUT ACCEPT
|
|
|
|
run_user_exit clear
|
|
|
|
logger "Shorewall Cleared"
|
|
}
|
|
|
|
#
|
|
# Set up ipsec tunnels
|
|
#
|
|
setup_tunnels() # $1 = name of tunnels file
|
|
{
|
|
local inchain
|
|
local outchain
|
|
|
|
setup_one_ipsec() # $1 = gateway $2 = Tunnel Kind $3 = gateway zones
|
|
{
|
|
options="-m state --state NEW -j ACCEPT"
|
|
addrule $inchain -p 50 -s $1 -j ACCEPT
|
|
addrule $outchain -p 50 -d $1 -j ACCEPT
|
|
run_iptables -A $inchain -p 51 -s $1 -j ACCEPT
|
|
run_iptables -A $outchain -p 51 -d $1 -j ACCEPT
|
|
|
|
run_iptables -A $outchain -p udp -d $1 --dport 500 --sport 500 $options
|
|
|
|
if [ $2 = ipsec ]; then
|
|
run_iptables -A $inchain -p udp -s $1 --sport 500 --dport 500 $options
|
|
else
|
|
run_iptables -A $inchain -p udp -s $1 --dport 500 $options
|
|
fi
|
|
|
|
for z in `separate_list $3`; do
|
|
if validate_zone $z; then
|
|
addrule ${FW}2${z} -p udp --sport 500 --dport 500 $options
|
|
addrule ${z}2${FW} -p udp --sport 500 --dport 500 $options
|
|
else
|
|
error_message "Warning: Invalid gateway zone ($z)" \
|
|
" -- Tunnel \"$tunnel\" may encounter keying problems"
|
|
fi
|
|
done
|
|
|
|
echo " IPSEC tunnel to $gateway defined."
|
|
}
|
|
|
|
setup_one_other() # $1 = TYPE, $2 = gateway, $3 = protocol
|
|
{
|
|
addrule $inchain -p $3 -s $2 -j ACCEPT
|
|
addrule $outchain -p $3 -d $2 -j ACCEPT
|
|
|
|
echo " $1 tunnel to $2 defined."
|
|
}
|
|
|
|
setup_pptp_client() # $1 = gateway
|
|
{
|
|
addrule $outchain -p 47 -d $1 -j ACCEPT
|
|
addrule $outchain -p tcp --dport 1723 -d $1 -j ACCEPT
|
|
|
|
echo " PPTP tunnel to $1 defined."
|
|
}
|
|
|
|
setup_pptp_server()
|
|
{
|
|
addrule $inchain -p 47 -j ACCEPT
|
|
addrule $inchain -p tcp --dport 1723 -j ACCEPT
|
|
|
|
echo " PPTP server 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}
|
|
case $kind in
|
|
ipsec|IPSEC)
|
|
setup_one_ipsec $gateway ipsec $z1
|
|
;;
|
|
ipsecnat|IPSECNAT)
|
|
setup_one_ipsec $gateway ipsecnat $z1
|
|
;;
|
|
ipip|IPIP)
|
|
setup_one_other IPIP $gateway 4
|
|
;;
|
|
gre|GRE)
|
|
setup_one_other GRE $gateway 47
|
|
;;
|
|
pptpclient|PPTPCLIENT)
|
|
setup_pptp_client $gateway
|
|
;;
|
|
pptpserver|PPTPSERVER)
|
|
setup_pptp_server
|
|
;;
|
|
*)
|
|
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
|
|
}
|
|
|
|
#
|
|
# Setup Proxy ARP
|
|
#
|
|
setup_proxy_arp() {
|
|
|
|
print_error() {
|
|
error_message "Invalid value for HAVEROUTE - ($haveroute)"
|
|
error_message "Entry \"$address $interface $external $haveroute\" ignored"
|
|
}
|
|
|
|
setup_one_proxy_arp() {
|
|
case $haveroute in
|
|
[Nn][Oo])
|
|
haveroute=
|
|
;;
|
|
[Yy][Ee][Ss])
|
|
;;
|
|
*)
|
|
if [ -n "$haveroute" ]; then
|
|
print_error
|
|
return
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
[ -z "$haveroute" ] && run_ip route add $address dev $interface
|
|
|
|
run_arp -Ds $address $external pub
|
|
|
|
echo 1 > /proc/sys/net/ipv4/conf/$interface/proxy_arp
|
|
echo 0 > /proc/sys/net/ipv4/conf/$external/proxy_arp
|
|
|
|
echo $address $interface $external $haveroute >> ${STATEDIR}/proxyarp
|
|
|
|
echo " Host $address connected to $interface added to ARP on $external"
|
|
}
|
|
|
|
> ${STATEDIR}/proxyarp
|
|
|
|
strip_file proxyarp
|
|
|
|
while read address interface external haveroute; do
|
|
expandv address interface external haveroute
|
|
setup_one_proxy_arp
|
|
done < $TMP_DIR/proxyarp
|
|
|
|
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
|
|
echo " Enabled proxy ARP on $interface"
|
|
else
|
|
error_message "Warning: Unable to enable proxy ARP on $interface"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#
|
|
# Set up MAC Verification
|
|
#
|
|
setup_mac_lists() {
|
|
local interface
|
|
local mac
|
|
local addresses
|
|
local address
|
|
local chain
|
|
local logpart
|
|
local macpart
|
|
local blob
|
|
local hosts
|
|
#
|
|
# Generate the list of interfaces having MAC verification
|
|
#
|
|
maclist_interfaces=
|
|
|
|
for hosts in $maclist_hosts; do
|
|
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
|
|
|
|
echo "Setting up MAC Verification on $maclist_interfaces..."
|
|
#
|
|
# Be sure that they are all ethernet interfaces
|
|
#
|
|
for interface in $maclist_interfaces; do
|
|
case $interface in
|
|
eth*)
|
|
;;
|
|
*)
|
|
fatal_error "Error: MAC verification is only supported on ethernet devices: $interface"
|
|
;;
|
|
esac
|
|
|
|
createchain `mac_chain $interface` no
|
|
done
|
|
#
|
|
# Process the maclist file producing the verification rules
|
|
#
|
|
strip_file maclist
|
|
|
|
while read interface mac addresses; do
|
|
expandv interface mac addresses
|
|
|
|
chain=`mac_chain $interface`
|
|
|
|
if ! havechain $chain ; then
|
|
fatal_error "Error: No hosts on $interface have the maclist option specified"
|
|
fi
|
|
|
|
macpart=`mac_match $mac`
|
|
|
|
if [ -z "$addresses" ]; then
|
|
run_iptables -A $chain $macpart -j RETURN
|
|
else
|
|
for address in `separate_list $addresses` ; do
|
|
run_iptables -A $chain $macpart -s $address -j RETURN
|
|
done
|
|
fi
|
|
done < $TMP_DIR/maclist
|
|
#
|
|
# Setup Logging variables
|
|
#
|
|
if [ -n "$MACLIST_LOG_LEVEL" ]; then
|
|
logpart="-j LOG $LOGPARMS --log-level $MACLIST_LOG_LEVEL --log-prefix"
|
|
else
|
|
logpart=
|
|
fi
|
|
#
|
|
# Must take care of our own broadcasts and multicasts then terminate the verification
|
|
# chains
|
|
#
|
|
for interface in $maclist_interfaces; do
|
|
chain=`mac_chain $interface`
|
|
blob=`ip addr show $interface 2> /dev/null | grep inet | sed 's/inet //; s/brd //; s/scope.*//;'`
|
|
|
|
[ -z "$blob" ] && \
|
|
fatal_error "Error: Interface $interface must be up before Shorewall can start"
|
|
|
|
set -- $blob
|
|
|
|
while [ $# -gt 0 ]; do
|
|
address=${1%/*}
|
|
|
|
case $1 in
|
|
*/32)
|
|
;;
|
|
*)
|
|
run_iptables -A $chain -s $address -d $2 -j RETURN
|
|
shift
|
|
;;
|
|
esac
|
|
|
|
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
|
|
shift
|
|
done
|
|
|
|
[ -n "$logpart" ] && \
|
|
run_iptables -A $chain $logpart "Shorewall:$chain:$MACLIST_DISPOSITION:"
|
|
|
|
run_iptables -A $chain -j $maclist_target
|
|
done
|
|
#
|
|
# Generate jumps from the input and forward chains
|
|
#
|
|
for hosts in $maclist_hosts; do
|
|
interface=${hosts%:*}
|
|
hosts=${hosts#*:}
|
|
for chain in `input_chains $interface` ; do
|
|
run_iptables -A $chain -s $hosts -m state --state NEW \
|
|
-j `mac_chain $interface`
|
|
done
|
|
done
|
|
}
|
|
|
|
#
|
|
# Set up SYN flood protection
|
|
#
|
|
setup_syn_flood_chain ()
|
|
# $1 = policy chain
|
|
# $2 = synparams
|
|
{
|
|
local chain=$1
|
|
local limit=${2%:*}
|
|
local limit_burst=${2#*:}
|
|
|
|
run_iptables -N @$chain
|
|
run_iptables -A @$chain \
|
|
-m limit --limit $limit --limit-burst $limit_burst \
|
|
-j RETURN
|
|
run_iptables -A @$chain -j DROP
|
|
}
|
|
|
|
#
|
|
# Enable SYN flood protection on a chain
|
|
#
|
|
# Insert a jump rule to the protection chain from the first chain. Inserted
|
|
# as the second rule and restrict the jump to SYN packets
|
|
#
|
|
enable_syn_flood_protection() # $1 = chain, $2 = protection chain
|
|
{
|
|
run_iptables -I $1 2 -p tcp --syn -j @$2
|
|
echo " Enabled SYN flood protection"
|
|
}
|
|
|
|
#
|
|
# Delete existing Proxy ARP
|
|
#
|
|
delete_proxy_arp() {
|
|
if [ -f ${STATEDIR}/proxyarp ]; then
|
|
while read address interface external haveroute; do
|
|
qt arp -i $external -d $address pub
|
|
[ -z "$haveroute" ] && qt ip route del $address dev $interface
|
|
done < ${STATEDIR}/proxyarp
|
|
|
|
rm -f ${STATEDIR}/proxyarp
|
|
fi
|
|
|
|
[ -d ${STATEDIR} ] && touch ${STATEDIR}/proxyarp
|
|
|
|
for f in `ls /proc/sys/net/ipv4/conf/*/proxy_arp`; do
|
|
echo 0 > $f
|
|
done
|
|
}
|
|
|
|
#
|
|
# Setup Static Network Address Translation (NAT)
|
|
#
|
|
setup_nat() {
|
|
local allints
|
|
#
|
|
# At this point, we're just interested in the network translation
|
|
#
|
|
> ${STATEDIR}/nat
|
|
|
|
strip_file nat
|
|
|
|
echo "Setting up NAT..."
|
|
|
|
while read external interface internal allints localnat; do
|
|
expandv external interface internal allints localnat
|
|
if [ -n "$ADD_IP_ALIASES" ]; then
|
|
qt ip addr del $external dev $interface
|
|
fi
|
|
|
|
if [ -z "$allints" -o "$allints" = "Yes" \
|
|
-o "$allints" = "yes" ]
|
|
then
|
|
addnatrule nat_in -d $external -j DNAT --to-destination $internal
|
|
addnatrule nat_out -s $internal -j SNAT --to-source $external
|
|
|
|
if [ "$localnat" = "Yes" -o "$localnat" = "yes" ]; then
|
|
run_iptables -t nat -A OUTPUT -d $external \
|
|
-j DNAT --to-destination $internal
|
|
fi
|
|
else
|
|
addnatrule `input_chain $interface` \
|
|
-d $external -j DNAT --to-destination $internal
|
|
addnatrule `output_chain $interface` \
|
|
-s $internal -j SNAT --to-source $external
|
|
fi
|
|
|
|
if [ -n "$ADD_IP_ALIASES" ]; then
|
|
list_search $external $aliases_to_add || \
|
|
aliases_to_add="$aliases_to_add $external $interface"
|
|
fi
|
|
|
|
echo " Host $internal NAT $external on $interface"
|
|
done < $TMP_DIR/nat
|
|
}
|
|
|
|
#
|
|
# Delete existing Static NAT
|
|
#
|
|
delete_nat() {
|
|
run_iptables -t nat -F
|
|
run_iptables -t nat -X
|
|
|
|
if [ -f ${STATEDIR}/nat ]; then
|
|
while read external interface; do
|
|
qt ip addr del $external dev $interface
|
|
done < ${STATEDIR}/nat
|
|
|
|
rm -f {$STATEDIR}/nat
|
|
fi
|
|
|
|
[ -d ${STATEDIR} ] && touch ${STATEDIR}/nat
|
|
}
|
|
|
|
#
|
|
# Process a TC Rule
|
|
#
|
|
process_tc_rule()
|
|
{
|
|
add_a_tc_rule() {
|
|
r=
|
|
chain=tcpre
|
|
|
|
if [ "x$source" != "x-" ]; then
|
|
case $source in
|
|
*.*.*)
|
|
r="-s $source "
|
|
;;
|
|
~*)
|
|
r=`mac_match $source`
|
|
;;
|
|
$FW)
|
|
chain=tcout
|
|
;;
|
|
*)
|
|
if ! list_search $source $all_interfaces; then
|
|
fatal_error "Error: Unknown interface $source"
|
|
fi
|
|
|
|
r="-i $source "
|
|
;;
|
|
esac
|
|
fi
|
|
[ "x$dest" = "x-" ] || r="${r}-d $dest "
|
|
[ "$proto" = "all" ] || r="${r}-p $proto "
|
|
[ "x$port" = "x-" ] || r="${r}--dport $port "
|
|
[ "x$sport" = "x-" ] || r="${r}--sport $sport "
|
|
|
|
run_iptables -t mangle -A $chain $r -j MARK --set-mark $mark
|
|
|
|
}
|
|
|
|
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
|
|
|
|
echo " TC Rule \"$rule\" added"
|
|
}
|
|
|
|
#
|
|
# Setup queuing and classes
|
|
#
|
|
setup_tc() {
|
|
|
|
echo "Setting up Traffic Control Rules..."
|
|
|
|
#
|
|
# Create the TC mangle chains
|
|
#
|
|
run_iptables -t mangle -N tcpre
|
|
run_iptables -t mangle -N 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
|
|
#
|
|
# Link to the TC mangle chains from the main chains
|
|
#
|
|
run_iptables -t mangle -A PREROUTING -j tcpre
|
|
run_iptables -t mangle -A OUTPUT -j tcout
|
|
|
|
run_user_exit tcstart
|
|
|
|
}
|
|
|
|
#
|
|
# Clear Traffic Shaping
|
|
#
|
|
delete_tc()
|
|
{
|
|
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
|
|
}
|
|
|
|
#
|
|
# Add a NAT rule - Helper function for the rules file processor
|
|
#
|
|
# The caller has established the following variables:
|
|
# 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
|
|
#
|
|
add_nat_rule() {
|
|
local chain
|
|
|
|
# Be sure NAT is enabled
|
|
|
|
if [ -z "$NAT_ENABLED" ]; then
|
|
fatal_error \
|
|
"Error - Rule \"$rule\" requires NAT which is disabled"
|
|
fi
|
|
|
|
# Onle ACCEPT (plus DNAT and REDIRECT) may result in NAT
|
|
|
|
if [ "$target" != "ACCEPT" ]; then
|
|
fatal_error "Error - Only DNAT and REDIRECT rules may specify " \
|
|
"port mapping; rule \"$rule\""
|
|
fi
|
|
|
|
# Parse SNAT address if any
|
|
|
|
if [ "$addr" != "${addr%:*}" ]; then
|
|
snat="${addr#*:}"
|
|
addr="${addr%:*}"
|
|
else
|
|
snat=""
|
|
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="`find_interface_address $interface` $addr"
|
|
done
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
addr=${addr:-0.0.0.0/0}
|
|
|
|
# Select target
|
|
|
|
if [ -n "$serv" ]; then
|
|
servport="${servport:+:$servport}"
|
|
target1="DNAT --to-destination ${serv}${servport}"
|
|
else
|
|
target1="REDIRECT --to-port $servport"
|
|
fi
|
|
|
|
# Generate nat table rules
|
|
|
|
if [ "$source" = "$FW" ]; then
|
|
run_iptables -t nat -A OUTPUT $proto $sports -d $addr \
|
|
$multiport $dports -j $target1
|
|
else
|
|
chain=`dnat_chain $source`
|
|
|
|
if [ -n "$excludezones" ]; then
|
|
chain=nonat${nonat_seq}
|
|
nonat_seq=$(($nonat_seq + 1))
|
|
createnatchain $chain
|
|
addnatrule `dnat_chain $source` -j $chain
|
|
for z in $excludezones; do
|
|
eval hosts=\$${z}_hosts
|
|
for host in $hosts; do
|
|
for adr in $addr; do
|
|
addnatrule $chain $proto -s ${host#*:} \
|
|
$multiport $sports -d $adr $dports -j RETURN
|
|
done
|
|
done
|
|
done
|
|
fi
|
|
|
|
for adr in $addr; do
|
|
addnatrule $chain $proto $cli $sports \
|
|
-d $adr $multiport $dports -j $target1
|
|
done
|
|
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
|
|
|
|
# Handle SNAT
|
|
|
|
if [ -n "$snat" ]; then
|
|
if [ -n "$cli" ]; then
|
|
addnatrule `snat_chain $dest` $proto $cli $multiport \
|
|
$sports -d $serv $dports -j SNAT --to-source $snat
|
|
else
|
|
for source_host in $source_hosts; do
|
|
[ "x${source_host#*:}" = "x0.0.0.0/0" ] && \
|
|
error_message "Warning: SNAT will occur on all connections to this server and port - rule \"$rule\""
|
|
|
|
addnatrule `snat_chain $dest` \
|
|
-s ${source_host#*:} $proto $sports $multiport \
|
|
-d $serv $dports -j SNAT --to-source $snat
|
|
done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Add one Filter Rule -- Helper function for the rules file processor
|
|
#
|
|
# The caller has established the following variables:
|
|
# client = SOURCE IP or MAC
|
|
# server = DESTINATION IP or interface
|
|
# protocol = Protocol
|
|
# address = Original Destination Address
|
|
# port = Destination Port
|
|
# cport = Source Port
|
|
# multioption = String to invoke multiport match if appropriate
|
|
# servport = Port the server listens on
|
|
# chain = The canonical chain for this rule
|
|
#
|
|
add_a_rule()
|
|
{
|
|
# Set source variables
|
|
|
|
cli=
|
|
|
|
[ -n "$client" ] && case "$client" in
|
|
-)
|
|
;;
|
|
*:*)
|
|
cli="-i ${client%:*} -s ${client#*:}"
|
|
;;
|
|
*.*.*)
|
|
cli="-s $client"
|
|
;;
|
|
~*)
|
|
cli=`mac_match $client`
|
|
;;
|
|
*)
|
|
cli="-i $client"
|
|
;;
|
|
esac
|
|
|
|
# Set destination variables
|
|
|
|
dest_interface=
|
|
|
|
[ -n "$server" ] && case "$server" in
|
|
-)
|
|
serv=
|
|
;;
|
|
*.*.*)
|
|
serv=$server
|
|
;;
|
|
*)
|
|
dest_interface="-o $server"
|
|
serv=
|
|
;;
|
|
esac
|
|
|
|
# Setup protocol and port variables
|
|
|
|
sports=
|
|
dports=
|
|
state="-m state --state NEW"
|
|
proto=$protocol
|
|
addr=$address
|
|
servport=$serverport
|
|
multiport=
|
|
|
|
case $proto in
|
|
tcp|udp|TCP|UDP|6|17)
|
|
if [ -n "$port" -a "x${port}" != "x-" ]; then
|
|
dports="--dport"
|
|
if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then
|
|
multiport="$multioption"
|
|
dports="--dports"
|
|
fi
|
|
dports="$dports $port"
|
|
fi
|
|
|
|
if [ -n "$cport" -a "x${cport}" != "x-" ]; then
|
|
sports="--sport"
|
|
if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then
|
|
multiport="$multioption"
|
|
sports="--sports"
|
|
fi
|
|
sports="$sports $cport"
|
|
fi
|
|
;;
|
|
icmp|ICMP|1)
|
|
[ -n "$port" ] && [ "x${port}" != "x-" ] && \
|
|
dports="--icmp-type $port"
|
|
state=
|
|
;;
|
|
all|ALL)
|
|
[ -n "$port" ] && [ "x${port}" != "x-" ] && \
|
|
fatal_error "Port number not allowed with \"all\";" \
|
|
" rule: \"$rule\""
|
|
proto=
|
|
;;
|
|
related|RELATED)
|
|
proto=
|
|
state="-m state --state RELATED"
|
|
;;
|
|
*)
|
|
state=
|
|
[ -n "$port" ] && [ "x${port}" != "x-" ] && \
|
|
fatal_error "Port number not allowed with protocol " \
|
|
"\"$proto\"; rule: \"$rule\""
|
|
;;
|
|
esac
|
|
|
|
proto="${proto:+-p $proto}"
|
|
|
|
# Some misc. setup
|
|
|
|
case "$logtarget" in
|
|
REJECT)
|
|
target=reject
|
|
[ -n "$servport" ] && \
|
|
fatal_error "Error: server port may not be specified in a REJECT rule;"\
|
|
"rule: \"$rule\""
|
|
;;
|
|
REDIRECT)
|
|
[ -n "$serv" ] && startup_error "Error: REDIRECT rules cannot"\
|
|
" specify a server IP; rule: \"$rule\""
|
|
servport=${servport:=$port}
|
|
;;
|
|
DNAT)
|
|
[ -n "$serv" ] || fatal_error "Error: DNAT rules require a" \
|
|
" server address; rule: \"$rule\""
|
|
;;
|
|
esac
|
|
|
|
# Complain if the rule is really a policy
|
|
|
|
if [ -z "$proto" -a -z "$cli" -a -z "$serv" -a -z "$servport" ]; then
|
|
error_message "Warning -- Rule \"$rule\" is a POLICY"
|
|
error_message " -- and should be moved to the policy file"
|
|
fi
|
|
|
|
if [ -n "${serv}${servport}" ]; then
|
|
|
|
# A specific server or server port given
|
|
|
|
if [ -n "$addr" -a "$addr" != "$serv" ]; then
|
|
add_nat_rule
|
|
elif [ -n "$servport" -a "$servport" != "$port" ]; then
|
|
add_nat_rule
|
|
fi
|
|
|
|
serv="${serv:+-d $serv}"
|
|
|
|
[ -n "$loglevel" ] && run_iptables -A $chain $proto $multiport \
|
|
$state $cli $sports $serv $dports -j LOG $LOGPARMS \
|
|
--log-prefix "Shorewall:$chain:$logtarget:" \
|
|
--log-level $loglevel
|
|
run_iptables -A $chain $proto $multiport $state $cli $sports \
|
|
$serv $dports -j $target
|
|
else
|
|
|
|
# Destination is a simple zone
|
|
|
|
[ -n "$addr" ] && fatal_error \
|
|
"Error: An ADDRESS ($addr) is only allowed in" \
|
|
" a DNAT or REDIRECT: \"$rule\""
|
|
|
|
[ -n "$loglevel" ] && run_iptables -A $chain $proto $multiport \
|
|
$dest_interface $state $cli $sports $dports -j LOG \
|
|
$LOGPARMS --log-prefix "Shorewall:$chain:$logtarget:" \
|
|
--log-level $loglevel
|
|
|
|
run_iptables -A $chain $proto $multiport $dest_interface $state \
|
|
$cli $sports $dports -j $target
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Process a record from the rules file
|
|
#
|
|
# The caller has loaded the column contents from the record into the following
|
|
# variables:
|
|
#
|
|
# target clients servers protocol ports cports address
|
|
#
|
|
# and has loaded a space-separated list of their values in "rule".
|
|
#
|
|
# The 'multioption' variable has also been loaded appropriately to reflect
|
|
# the setting of the MULTIPORT option in /etc/shorewall/shorewall.conf
|
|
#
|
|
process_rule() {
|
|
|
|
# Function Body -- isolate log level
|
|
|
|
if [ "$target" = "${target%:*}" ]; then
|
|
loglevel=
|
|
else
|
|
loglevel="${target#*:}"
|
|
target="${target%:*}"
|
|
expandv loglevel
|
|
fi
|
|
|
|
logtarget="$target"
|
|
|
|
# Convert 1.3 Rule formats to 1.2 format
|
|
|
|
case $target in
|
|
DNAT)
|
|
target=ACCEPT
|
|
address=${address:=detect}
|
|
;;
|
|
REDIRECT)
|
|
target=ACCEPT
|
|
address=${address:=all}
|
|
if [ "x-" = "x$servers" ]; then
|
|
servers=$FW
|
|
else
|
|
servers="$FW::$servers"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Parse and validate source
|
|
|
|
if [ "$clients" = "${clients%:*}" ]; then
|
|
clientzone="$clients"
|
|
clients=
|
|
else
|
|
clientzone="${clients%%:*}"
|
|
clients="${clients#*:}"
|
|
[ -z "$clientzone" -o -z "$clients" ] && \
|
|
fatal_error "Error: Empty source zone or qualifier: rule \"$rule\""
|
|
fi
|
|
|
|
if [ "$clientzone" = "${clientzone%\!*}" ]; then
|
|
excludezones=
|
|
else
|
|
excludezones="${clientzone#*\!}"
|
|
clientzone="${clientzone%\!*}"
|
|
|
|
[ "$logtarget" = DNAT ] || [ "$logtarget" = REDIRECT ] ||\
|
|
fatal_error "Error: Exclude list only allowed with DNAT or REDIRECT"
|
|
fi
|
|
|
|
if ! validate_zone $clientzone; then
|
|
fatal_error "Error: Undefined Client Zone in rule \"$rule\""
|
|
fi
|
|
|
|
# Parse and validate destination
|
|
|
|
source=$clientzone
|
|
|
|
[ $source = $FW ] && source_hosts= || eval source_hosts=\"\$${source}_hosts\"
|
|
|
|
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 "Error: Empty destination zone or server port: rule \"$rule\""
|
|
else
|
|
serverport=
|
|
[ -z "$serverzone" -o -z "$servers" ] && \
|
|
startup_error "Error: Empty destination zone or qualifier: rule \"$rule\""
|
|
fi
|
|
fi
|
|
|
|
if ! validate_zone $serverzone; then
|
|
fatal_error "Error: Undefined Server Zone in rule \"$rule\""
|
|
fi
|
|
|
|
dest=$serverzone
|
|
|
|
# Create canonical chain if necessary
|
|
|
|
chain=${source}2${dest}
|
|
ensurechain $chain
|
|
|
|
# Generate Netfilter rule(s)
|
|
|
|
if [ -n "$MULTIPORT" -a \
|
|
"$ports" = "${ports%:*}" -a \
|
|
"$cports" = "${cports%:*}" -a \
|
|
`list_count $ports` -le 15 -a \
|
|
`list_count $cports` -le 15 ]
|
|
then
|
|
multioption="-m multiport"
|
|
for client in `separate_list ${clients:=-}`; do
|
|
for server in `separate_list ${servers:=-}`; do
|
|
port=${ports:=-}
|
|
cport=${cports:=-}
|
|
add_a_rule
|
|
done
|
|
done
|
|
else
|
|
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
|
|
|
|
echo " Rule \"$rule\" added."
|
|
}
|
|
|
|
#
|
|
# Process the rules file
|
|
#
|
|
process_rules() # $1 = name of rules file
|
|
{
|
|
strip_file rules
|
|
|
|
while read target clients servers protocol ports cports address; do
|
|
case "$target" in
|
|
|
|
ACCEPT*|DROP*|REJECT*|DNAT*|REDIRECT*)
|
|
expandv clients servers protocol ports cports address
|
|
rule="`echo $target $clients $servers $protocol $ports $cports $address`"
|
|
process_rule
|
|
;;
|
|
*)
|
|
rule="`echo $target $clients $servers $protocol $ports $cports $address`"
|
|
fatal_error "Error: Invalid Target in rule \"$rule\""
|
|
;;
|
|
esac
|
|
done < $TMP_DIR/rules
|
|
}
|
|
|
|
#
|
|
# Process a record from the tos file
|
|
#
|
|
# The caller has loaded the column contents from the record into the following
|
|
# variables:
|
|
#
|
|
# src dst protocol sport dport tos
|
|
#
|
|
# and has loaded a space-separated list of their values in "rule".
|
|
#
|
|
process_tos_rule() {
|
|
#
|
|
# Parse the contents of the 'src' variable
|
|
#
|
|
if [ "$src" = "${src%:*}" ]; then
|
|
srczone="$src"
|
|
src=
|
|
else
|
|
srczone="${src%:*}"
|
|
src="${src#*:}"
|
|
fi
|
|
|
|
source=
|
|
#
|
|
# Validate the source zone
|
|
#
|
|
if validate_zone $srczone; then
|
|
source=$srczone
|
|
elif [ "$srczone" = "all" ]; then
|
|
source="all"
|
|
else
|
|
error_message "Warning: Undefined Source Zone - rule \"$rule\" ignored"
|
|
return
|
|
fi
|
|
|
|
[ -n "$src" ] && case "$src" in
|
|
*.*.*)
|
|
#
|
|
# IP Address or subnet
|
|
#
|
|
src="-s $src"
|
|
;;
|
|
~*)
|
|
src=`mac_match $src`
|
|
;;
|
|
*)
|
|
#
|
|
# Assume that this is a device name
|
|
#
|
|
src="-i $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 subnet
|
|
#
|
|
;;
|
|
*)
|
|
#
|
|
# 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="-d $dest"
|
|
|
|
case $srczone in
|
|
$FW)
|
|
run_iptables -t mangle -A outtos \
|
|
$protocol $dest $dports $sports $tos
|
|
;;
|
|
all|ALL)
|
|
run_iptables -t mangle -A outtos \
|
|
$protocol $dest $dports $sports $tos
|
|
run_iptables -t mangle -A pretos \
|
|
$protocol $dest $dports $sports $tos
|
|
;;
|
|
*)
|
|
if [ -n "$src" ]; then
|
|
run_iptables -t mangle -A pretos $src \
|
|
$protocol $dest $dports $sports $tos
|
|
else
|
|
eval interfaces=\$${srczone}_interfaces
|
|
|
|
for interface in $interfaces; do
|
|
run_iptables -t mangle -A pretos -i $interface \
|
|
$protocol $dest $dports $sports $tos
|
|
done
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo " Rule \"$rule\" added."
|
|
}
|
|
|
|
#
|
|
# Process the tos file
|
|
#
|
|
process_tos() # $1 = name of tos file
|
|
{
|
|
echo "Processing $1..."
|
|
|
|
run_iptables -t mangle -N pretos
|
|
run_iptables -t mangle -N outtos
|
|
|
|
strip_file tos $1
|
|
|
|
while read src dst protocol sport dport tos; do
|
|
expandv src dst protocol sport dport tos
|
|
rule="`echo $src $dst $protocol $sport $dport $tos`"
|
|
process_tos_rule
|
|
done < $TMP_DIR/tos
|
|
|
|
run_iptables -t mangle -A PREROUTING -j pretos
|
|
run_iptables -t mangle -A OUTPUT -j outtos
|
|
}
|
|
|
|
#
|
|
# Load a Kernel Module
|
|
#
|
|
loadmodule() # $1 = module name, $2 - * arguments
|
|
{
|
|
local modulename=$1
|
|
local modulefile
|
|
|
|
if [ -z "`lsmod | grep $modulename`" ]; then
|
|
shift
|
|
modulefile=$MODULESDIR/${modulename}.o
|
|
|
|
if [ -f $modulefile ]; then
|
|
insmod $modulefile $*
|
|
return
|
|
fi
|
|
#
|
|
# If the modules directory contains compressed modules then we'll
|
|
# assume that insmod can load them
|
|
#
|
|
modulefile=${modulefile}.gz
|
|
|
|
if [ -f $modulefile ]; then
|
|
insmod $modulefile $*
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Display elements of a list with leading white space
|
|
#
|
|
display_list() # $1 = List Title, rest of $* = list to display
|
|
{
|
|
[ $# -gt 1 ] && echo " $*"
|
|
}
|
|
|
|
#
|
|
# Add rules to the "common" chain to silently drop packets addressed to any of
|
|
# the passed addresses
|
|
#
|
|
drop_broadcasts() # $* = broadcast addresses
|
|
{
|
|
while [ $# -gt 0 ]; do
|
|
run_iptables -A common -d $1 -j DROP
|
|
shift
|
|
done
|
|
}
|
|
|
|
#
|
|
# Add policy rule ( and possibly logging rule) to the passed chain
|
|
#
|
|
policy_rules() # $1 = chain to add rules to
|
|
# $2 = policy
|
|
# $3 = loglevel
|
|
{
|
|
local target="$2"
|
|
|
|
case "$target" in
|
|
ACCEPT)
|
|
;;
|
|
|
|
DROP)
|
|
run_iptables -A $1 -j common
|
|
;;
|
|
REJECT)
|
|
run_iptables -A $1 -j common
|
|
target=reject
|
|
;;
|
|
CONTINUE)
|
|
target=
|
|
;;
|
|
*)
|
|
fatal_error "Invalid policy ($policy) for $1"
|
|
;;
|
|
|
|
esac
|
|
|
|
[ $# -eq 3 ] && [ "x${3}" != "x-" ] && run_iptables -A $1 -j LOG $LOGPARMS \
|
|
--log-prefix "Shorewall:${1}:${2}:" --log-level $3
|
|
[ -n "$target" ] && run_iptables -A $1 -j $target
|
|
}
|
|
|
|
#
|
|
# Generate default policy & log level rules for the passed client & server
|
|
# zones
|
|
#
|
|
# This function is only called when the canonical chain for this client/server
|
|
# pair is known to exist. If the default policy for this pair specifies the
|
|
# same chain then we add the policy (and logging) rule to the canonical chain;
|
|
# otherwise add a rule to the canonical chain to jump to the appropriate
|
|
# policy chain.
|
|
#
|
|
default_policy() # $1 = client $2 = server
|
|
{
|
|
local chain="${1}2${2}"
|
|
local policy=
|
|
local loglevel=
|
|
local chain1
|
|
|
|
jump_to_policy_chain() {
|
|
#
|
|
# Add a jump to from the canonical chain to the policy chain. On return,
|
|
# $chain is set to the name of the policy chain
|
|
#
|
|
run_iptables -A $chain -j $chain1
|
|
chain=$chain1
|
|
}
|
|
|
|
apply_default()
|
|
{
|
|
#
|
|
# Add the appropriate rules to the canonical chain ($chain) to enforce
|
|
# the specified policy
|
|
#
|
|
# Construct policy chain name
|
|
#
|
|
chain1=${client}2${server}
|
|
|
|
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)
|
|
if [ -n "$synparams" ]; then
|
|
#
|
|
# To avoid double-counting SYN packets, enforce the policy
|
|
# in this chain.
|
|
#
|
|
enable_syn_flood_protection $chain $chain1
|
|
policy_rules $chain $policy $loglevel
|
|
else
|
|
#
|
|
# No problem with double-counting so just jump to the
|
|
# policy chain.
|
|
#
|
|
jump_to_policy_chain
|
|
fi
|
|
;;
|
|
CONTINUE)
|
|
#
|
|
# Silly to jump to the policy chain -- add any logging
|
|
# rules and enable SYN flood protection if requested
|
|
#
|
|
[ -n "$synparams" ] && \
|
|
enable_syn_flood_protection $chain $chain1
|
|
policy_rules $chain $policy $loglevel
|
|
;;
|
|
*)
|
|
#
|
|
# DROP or REJECT policy -- enforce in the policy chain and
|
|
# enable SYN flood protection if requested.
|
|
#
|
|
[ -n "$synparams" ] && \
|
|
enable_syn_flood_protection $chain $chain1
|
|
jump_to_policy_chain
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
echo " Policy $policy for $1 to $2 using chain $chain"
|
|
}
|
|
|
|
while read client server policy loglevel synparams; do
|
|
expandv client server policy loglevel synparams
|
|
case "$client" in
|
|
all|ALL)
|
|
if [ "$server" = "$2" -o "$server" = "all" ]; then
|
|
apply_default $1 $2
|
|
return
|
|
fi
|
|
;;
|
|
*)
|
|
if [ "$client" = "$1" ] && \
|
|
[ "$server" = "all" -o "$server" = "$2" ]
|
|
then
|
|
apply_default $1 $2
|
|
return
|
|
fi
|
|
;;
|
|
esac
|
|
done < $TMP_DIR/policy
|
|
|
|
fatal_error "Error: No default policy for zone $1 to zone $2"
|
|
}
|
|
|
|
#
|
|
# 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=
|
|
|
|
run_user_exit $1
|
|
|
|
while read client server policy loglevel synparams; do
|
|
expandv client server policy loglevel synparams
|
|
|
|
[ "x$loglevel" = "x-" ] && loglevel=
|
|
|
|
case "$client" in
|
|
all|ALL)
|
|
if [ "$server" = "$3" -o "$server" = "all" ]; then
|
|
policy_rules $1 $policy $loglevel
|
|
return
|
|
fi
|
|
;;
|
|
*)
|
|
if [ "$client" = "$2" ] && \
|
|
[ "$server" = "all" -o "$server" = "$3" ]
|
|
then
|
|
policy_rules $1 $policy $loglevel
|
|
return
|
|
fi
|
|
;;
|
|
esac
|
|
done < $TMP_DIR/policy
|
|
|
|
policy_rules $1 DROP INFO
|
|
}
|
|
|
|
#
|
|
# Find the appropriate chain to pass packets from a source zone to a
|
|
# destination zone
|
|
#
|
|
# If the canonical chain for this zone pair exists, echo it's name; otherwise
|
|
# locate and echo the name of the appropriate policy chain
|
|
#
|
|
rules_chain() # $1 = source zone, $2 = destination zone
|
|
{
|
|
local chain=${1}2${2}
|
|
|
|
havechain $chain && { echo $chain; return; }
|
|
|
|
while read client server policy loglevel ; do
|
|
expandv client server policy loglevel
|
|
case "$client" in
|
|
all|ALL)
|
|
if [ "$server" = "$2" -o "$server" = "all" ]; then
|
|
echo all2${server}
|
|
return
|
|
fi
|
|
;;
|
|
*)
|
|
if [ "$client" = "$1" -a "$server" = "all" ]; then
|
|
echo ${client}2${server}
|
|
return
|
|
fi
|
|
;;
|
|
esac
|
|
done < $TMP_DIR/policy
|
|
|
|
fatal_error "Error: No appropriate chain for zone $1 to zone $2"
|
|
}
|
|
|
|
#
|
|
# Set up Source NAT (including masquerading)
|
|
#
|
|
setup_masq()
|
|
{
|
|
setup_one() {
|
|
local using
|
|
|
|
if [ "$interface" = "${interface%:*}" ]; then
|
|
destnet="0.0.0.0/0"
|
|
else
|
|
destnet="${interface#*:}"
|
|
interface="${interface%:*}"
|
|
fi
|
|
|
|
if ! list_search $interface $all_interfaces; then
|
|
fatal_error "Error: Unknown interface $interface"
|
|
fi
|
|
|
|
if [ "$subnet" = "${subnet%!*}" ]; then
|
|
nomasq=
|
|
else
|
|
nomasq="${subnet#*!}"
|
|
subnet="${subnet%!*}"
|
|
fi
|
|
|
|
chain=`masq_chain $interface`
|
|
iface=
|
|
|
|
case $subnet in
|
|
*.*.*)
|
|
source="$subnet"
|
|
subnet="-s $subnet"
|
|
;;
|
|
-)
|
|
#
|
|
# Note: This only works if you have the LOCAL NAT patches in the
|
|
# kernel and in the iptables utility
|
|
#
|
|
chain=OUTPUT
|
|
subnet=
|
|
source=$FW
|
|
iface="-o $interface"
|
|
;;
|
|
*)
|
|
ipaddr="`ip addr show $subnet 2> /dev/null | grep 'inet '`"
|
|
source="$subnet"
|
|
if [ -z "$ipaddr" ]; then
|
|
fatal_error \
|
|
"Interface $subnet must be up before Shorewall starts"
|
|
fi
|
|
|
|
subnet="`echo $ipaddr | sed s/" "// | cut -d' ' -f2`"
|
|
[ -z "`echo "$subnet" | grep '/'`" ] && subnet="${subnet}/32"
|
|
subnet="-s $subnet"
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$address" -a -n "$ADD_SNAT_ALIASES" ]; then
|
|
list_search $address $aliases_to_add || \
|
|
aliases_to_add="$aliases_to_add $address $interface"
|
|
fi
|
|
|
|
destination=$destnet
|
|
|
|
if [ -n "$nomasq" ]; then
|
|
newchain=masq${masq_seq}
|
|
run_iptables -t nat -N $newchain
|
|
addnatrule $chain -d $destnet $iface $subnet -j $newchain
|
|
masq_seq=$(($masq_seq + 1))
|
|
chain=$newchain
|
|
subnet=
|
|
iface=
|
|
destnet=
|
|
|
|
for addr in `separate_list $nomasq`; do
|
|
addnatrule $chain -s $addr -j RETURN
|
|
done
|
|
else
|
|
destnet="-d $destnet"
|
|
fi
|
|
|
|
if [ -n "$address" ]; then
|
|
addnatrule $chain $subnet $destnet $iface \
|
|
-j SNAT --to-source $address
|
|
using=" using $address"
|
|
else
|
|
addnatrule $chain $subnet $destnet $iface -j MASQUERADE
|
|
using=
|
|
fi
|
|
|
|
[ -n "$nomasq" ] && source="$source except $nomasq"
|
|
echo " To $destination from $source through ${interface}${using}"
|
|
}
|
|
|
|
strip_file masq $1
|
|
|
|
[ -n "$NAT_ENABLED" ] && echo "Masqueraded Subnets and Hosts:"
|
|
|
|
while read interface subnet address; do
|
|
expandv interface subnet address
|
|
[ -n "$NAT_ENABLED" ] && setup_one || \
|
|
error_message "Warning: NAT disabled; masq rule ignored"
|
|
done < $TMP_DIR/masq
|
|
}
|
|
|
|
#
|
|
# Setup Intrazone chain if appropriate
|
|
#
|
|
setup_intrazone() # $1 = zone
|
|
{
|
|
eval hosts=\$${1}_hosts
|
|
|
|
if [ "$hosts" != "${hosts% *}" ] || \
|
|
have_interfaces_in_zone_with_option $1 multi
|
|
then
|
|
ensurechain ${1}2${1}
|
|
fi
|
|
}
|
|
#
|
|
# Add a record to the blacklst chain
|
|
#
|
|
# $source = address match
|
|
# $proto = protocol selector
|
|
# $dport = destination port selector
|
|
#
|
|
add_blacklist_rule() {
|
|
[ -n "$BLACKLIST_LOGLEVEL" ] && \
|
|
run_iptables -A blacklst $source $proto $dport -j \
|
|
LOG $LOGPARMS --log-prefix \
|
|
"Shorewall:blacklst:$BLACKLIST_DISPOSITION:" \
|
|
--log-level $BLACKLIST_LOGLEVEL
|
|
run_iptables -A blacklst $source $proto $dport -j $disposition
|
|
}
|
|
|
|
#
|
|
# Process a record from the blacklist file
|
|
#
|
|
# $subnet = address/subnet
|
|
# $protocol = Protocol Number/Name
|
|
# $port = Port Number/Name
|
|
#
|
|
process_blacklist_rec() {
|
|
local source
|
|
local addr
|
|
local proto
|
|
local dport
|
|
|
|
for addr in `separate_list $subnet`; do
|
|
case $addr in
|
|
~*)
|
|
addr=`echo $addr | sed 's/~//;s/-/:/g'`
|
|
source="--match mac --mac-source $addr"
|
|
;;
|
|
*)
|
|
source="-s $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
|
|
|
|
echo " $addr added to Black List"
|
|
done
|
|
}
|
|
|
|
#
|
|
# Setup the Black List
|
|
#
|
|
setup_blacklist() {
|
|
local interfaces=`find_interfaces_by_option blacklist`
|
|
local f=`find_file blacklist`
|
|
local disposition=$BLACKLIST_DISPOSITION
|
|
|
|
if [ -n "$interfaces" -a -f $f ]; then
|
|
echo "Setting up Blacklisting..."
|
|
|
|
strip_file blacklist $f
|
|
|
|
createchain blacklst no
|
|
|
|
for interface in $interfaces; do
|
|
for chain in `first_chains $interface`; do
|
|
run_iptables -A $chain -j blacklst
|
|
done
|
|
|
|
echo " Blacklisting enabled on $interface"
|
|
done
|
|
|
|
[ "$disposition" = REJECT ] && disposition=reject
|
|
|
|
while read subnet protocol ports; do
|
|
expandv subnet protocol ports
|
|
process_blacklist_rec
|
|
done < $TMP_DIR/blacklist
|
|
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Refresh the Black List
|
|
#
|
|
refresh_blacklist() {
|
|
local f=`find_file blacklist`
|
|
local disposition=$BLACKLIST_DISPOSITION
|
|
|
|
if qt iptables -L blacklst -n ; then
|
|
echo "Refreshing Black List..."
|
|
|
|
strip_file blacklist $f
|
|
|
|
[ "$disposition" = REJECT ] && disposition=reject
|
|
|
|
run_iptables -F blacklst
|
|
|
|
while read subnet protocol ports; do
|
|
expandv subnet protocol ports
|
|
process_blacklist_rec
|
|
done < $TMP_DIR/blacklist
|
|
fi
|
|
}
|
|
|
|
#
|
|
# Verify that kernel has netfilter support
|
|
#
|
|
verify_os_version() {
|
|
|
|
osversion=`uname -r`
|
|
|
|
case $osversion in
|
|
2.4.*|2.5.*)
|
|
;;
|
|
*)
|
|
startup_error "Shorewall version $version does not work with kernel version $osversion"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
#
|
|
# Add IP Aliases
|
|
#
|
|
add_ip_aliases()
|
|
{
|
|
local external
|
|
local interface
|
|
local primary
|
|
|
|
do_one()
|
|
{
|
|
#
|
|
# 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 the primary address
|
|
#
|
|
# Get all of the lines that contain inet addresses with broadcast
|
|
#
|
|
val=`ip addr show $interface | grep 'inet.*brd '` 2> /dev/null
|
|
|
|
if [ -n "$val" ] ; then
|
|
#
|
|
# Hack off the leading 'inet <ip addr>' (actually cut off the
|
|
# "/" as well but add it back in).
|
|
#
|
|
val="/${val#*/}"
|
|
#
|
|
# Now get the VLSM, "brd" and the broadcast address
|
|
#
|
|
val=${val%% scope*}
|
|
fi
|
|
|
|
run_ip addr add ${external}${val} dev $interface
|
|
echo "$external $interface" >> ${STATEDIR}/nat
|
|
echo " IP Address $external added to interface $interface"
|
|
}
|
|
|
|
set -- $aliases_to_add
|
|
|
|
while [ $# -gt 0 ]; do
|
|
external=$1
|
|
interface=$2
|
|
primary=`find_interface_address $interface`
|
|
shift;shift
|
|
[ "x${primary}" = "x${external}" ] || do_one
|
|
done
|
|
}
|
|
|
|
#
|
|
# Load kernel modules required for Shorewall
|
|
#
|
|
load_kernel_modules() {
|
|
|
|
[ -z "$MODULESDIR" ] && \
|
|
MODULESDIR=/lib/modules/$osversion/kernel/net/ipv4/netfilter
|
|
|
|
modules=`find_file modules`
|
|
|
|
if [ -f $modules -a -d $MODULESDIR ]; then
|
|
echo "Loading Modules..."
|
|
. $modules
|
|
fi
|
|
}
|
|
|
|
#
|
|
# 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 () {
|
|
|
|
echo "Determining Zones..."
|
|
|
|
determine_zones
|
|
|
|
[ -z "$zones" ] && startup_error "ERROR: No Zones Defined"
|
|
|
|
display_list "Zones:" $zones
|
|
|
|
echo "Validating interfaces file..."
|
|
|
|
validate_interfaces_file
|
|
|
|
echo "Validating hosts file..."
|
|
|
|
validate_hosts_file
|
|
|
|
echo "Validating Policy file..."
|
|
|
|
validate_policy
|
|
|
|
echo "Determining Hosts in Zones..."
|
|
|
|
determine_interfaces
|
|
determine_hosts
|
|
|
|
deletechain shorewall
|
|
|
|
[ -n "$NAT_ENABLED" ] && delete_nat
|
|
|
|
delete_proxy_arp
|
|
|
|
[ -n "$MANGLE_ENABLED" ] && \
|
|
run_iptables -t mangle -F && \
|
|
run_iptables -t mangle -X
|
|
|
|
[ -n "$TC_ENABLED" ] && delete_tc
|
|
|
|
run_user_exit init
|
|
|
|
echo "Deleting user chains..."
|
|
|
|
setpolicy INPUT DROP
|
|
setpolicy OUTPUT DROP
|
|
setpolicy FORWARD DROP
|
|
|
|
deleteallchains
|
|
|
|
setcontinue FORWARD
|
|
setcontinue INPUT
|
|
setcontinue OUTPUT
|
|
#
|
|
# Allow DNS lookups during startup for FQDNs
|
|
#
|
|
run_iptables -A INPUT -p udp --dport 53 -j ACCEPT # I suppose that there
|
|
# is an idiot somewhere
|
|
# who needs this
|
|
run_iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
run_iptables -A FORWARD -p udp --dport 53 -j ACCEPT
|
|
|
|
[ -n "$CLAMPMSS" ] && \
|
|
run_iptables -A FORWARD -p tcp \
|
|
--tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
|
|
|
|
|
|
if [ -z "$NEWNOTSYN" ]; then
|
|
createchain newnotsyn no
|
|
run_user_exit newnotsyn
|
|
[ -n "$LOGNEWNOTSYN" ] && \
|
|
run_iptables -A newnotsyn -j LOG \
|
|
--log-prefix "Shorewall:newnotsyn:DROP:" --log-level $LOGNEWNOTSYN
|
|
run_iptables -A newnotsyn -j DROP
|
|
fi
|
|
|
|
createchain icmpdef no
|
|
createchain common no
|
|
createchain reject no
|
|
createchain dynamic no
|
|
|
|
if [ -f /var/lib/shorewall/save ]; then
|
|
echo "Restoring dynamic rules..."
|
|
|
|
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
|
|
|
|
echo "Creating input Chains..."
|
|
|
|
for interface in $all_interfaces; do
|
|
createchain `forward_chain $interface` no
|
|
run_iptables -A `forward_chain $interface` -j dynamic
|
|
createchain `input_chain $interface` no
|
|
run_iptables -A `input_chain $interface` -j dynamic
|
|
done
|
|
}
|
|
|
|
#
|
|
# Build the common chain -- called during [re]start and refresh
|
|
#
|
|
build_common_chain() {
|
|
#
|
|
# PING
|
|
#
|
|
[ -n "$FORWARDPING" ] && \
|
|
run_iptables -A icmpdef -p icmp --icmp-type echo-request -j ACCEPT
|
|
#
|
|
# Common ICMP rules
|
|
#
|
|
run_user_exit icmpdef
|
|
#
|
|
# Common rules in each chain
|
|
#
|
|
common=`find_file common`
|
|
|
|
if [ -f $common ]; then
|
|
. $common
|
|
else
|
|
. `find_file common.def`
|
|
fi
|
|
#
|
|
# New Not Syn Stuff
|
|
#
|
|
if [ -n "$NEWNOTSYN" ]; then
|
|
run_iptables -A common -p tcp --tcp-flags ACK ACK -j ACCEPT
|
|
run_iptables -A common -p tcp --tcp-flags RST RST -j ACCEPT
|
|
fi
|
|
#
|
|
# BROADCASTS
|
|
#
|
|
drop_broadcasts `find_broadcasts`
|
|
}
|
|
|
|
#
|
|
# Construct zone-independent rules
|
|
#
|
|
add_common_rules() {
|
|
logdisp() # $1 = Chain Name
|
|
{
|
|
echo "LOG --log-prefix "Shorewall:${1}:DROP:" --log-level info"
|
|
}
|
|
#
|
|
# Reject Rules
|
|
#
|
|
run_iptables -A reject -p tcp -j REJECT --reject-with tcp-reset
|
|
run_iptables -A reject -j REJECT
|
|
#
|
|
# dropunclean rules
|
|
#
|
|
interfaces="`find_interfaces_by_option dropunclean`"
|
|
|
|
if [ -n "$interfaces" ]; then
|
|
createchain badpkt no
|
|
|
|
if [ -n "$LOGUNCLEAN" ]; then
|
|
logoptions="$LOGPARAMS --log-prefix Shorewall:badpkt:DROP:"
|
|
logoptions="$logoptions --log-level $LOGUNCLEAN --log-ip-options"
|
|
run_iptables -A badpkt -p tcp -j LOG $logoptions --log-tcp-options
|
|
run_iptables -A badpkt -p ! tcp -j LOG $logoptions
|
|
fi
|
|
|
|
run_iptables -A badpkt -j DROP
|
|
echo "Mangled/Invalid Packet filtering enabled on:"
|
|
|
|
for interface in $interfaces; do
|
|
for chain in `first_chains $interface`; do
|
|
run_iptables -A $chain --match unclean -j badpkt
|
|
done
|
|
echo " $interface"
|
|
done
|
|
fi
|
|
#
|
|
# logunclean rules
|
|
#
|
|
interfaces="`find_interfaces_by_option logunclean`"
|
|
|
|
if [ -n "$interfaces" ]; then
|
|
createchain logpkt no
|
|
|
|
[ -z"$LOGUNCLEAN" ] && LOGUNCLEAN=info
|
|
logoptions="$LOGPARAMS --log-prefix Shorewall:logpkt:LOG:"
|
|
logoptions="$logoptions --log-level $LOGUNCLEAN --log-ip-options"
|
|
run_iptables -A logpkt -p tcp -j LOG $logoptions --log-tcp-options
|
|
run_iptables -A logpkt -p ! tcp -j LOG $logoptions
|
|
|
|
echo "Mangled/Invalid Packet Logging enabled on:"
|
|
|
|
for interface in $interfaces; do
|
|
for chain in `first_chains $interface`; do
|
|
run_iptables -A $chain --match unclean -j logpkt
|
|
done
|
|
echo " $interface"
|
|
done
|
|
fi
|
|
|
|
build_common_chain
|
|
|
|
#
|
|
# DHCP
|
|
#
|
|
echo "Adding rules for DHCP"
|
|
|
|
for interface in `find_interfaces_by_option dhcp`; do
|
|
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
|
|
|
|
#
|
|
# RFC 1918
|
|
#
|
|
norfc1918_interfaces="`find_interfaces_by_option norfc1918`"
|
|
|
|
if [ -n "$norfc1918_interfaces" ]; then
|
|
echo "Enabling RFC1918 Filtering"
|
|
|
|
strip_file rfc1918
|
|
|
|
createchain rfc1918 no
|
|
|
|
createchain logdrop no
|
|
run_iptables -A logdrop -j `logdisp rfc1918`
|
|
run_iptables -A logdrop -j DROP
|
|
|
|
if [ -n "$MANGLE_ENABLED" ]; then
|
|
#
|
|
# Mangling is enabled -- 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 logdrop
|
|
run_iptables -t mangle -A logdrop -j `logdisp man1918`
|
|
run_iptables -t mangle -A logdrop -j DROP
|
|
fi
|
|
|
|
while read subnet target; do
|
|
case $target in
|
|
logdrop|DROP|RETURN)
|
|
;;
|
|
*)
|
|
fatal_error " Error:Illegal target ($target) for $subnet"
|
|
;;
|
|
esac
|
|
|
|
run_iptables -A rfc1918 -s $subnet -j $target
|
|
#
|
|
# If packet mangling is enabled, trap packets with an
|
|
# RFC1918 destination
|
|
#
|
|
if [ -n "$MANGLE_ENABLED" ]; then
|
|
run_iptables -t mangle -A man1918 -d $subnet -j $target
|
|
fi
|
|
done < $TMP_DIR/rfc1918
|
|
|
|
for interface in $norfc1918_interfaces; do
|
|
for chain in `first_chains $interface`; do
|
|
run_iptables -A $chain -j rfc1918
|
|
done
|
|
|
|
[ -n "$MANGLE_ENABLED" ] && \
|
|
run_iptables -t mangle -A PREROUTING -i $interface -j man1918
|
|
done
|
|
|
|
fi
|
|
#
|
|
# Process Black List
|
|
#
|
|
setup_blacklist
|
|
|
|
#
|
|
# Enable the Loopback interface
|
|
#
|
|
run_iptables -A INPUT -i lo -j ACCEPT
|
|
run_iptables -A OUTPUT -o lo -j ACCEPT
|
|
|
|
#
|
|
# Enable icmp output
|
|
#
|
|
run_iptables -A OUTPUT -m state --state ! INVALID -p icmp -j ACCEPT
|
|
#
|
|
# Route Filtering
|
|
#
|
|
for f in /proc/sys/net/ipv4/conf/*/rp_filter; do
|
|
echo 0 > $f
|
|
done
|
|
|
|
interfaces="`find_interfaces_by_option routefilter`"
|
|
|
|
if [ -n "$interfaces" -o -n "$ROUTE_FILTER" ]; then
|
|
echo "Setting up Kernel Route Filtering..."
|
|
|
|
if [ -n "$ROUTE_FILTER" ]; then
|
|
echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
|
|
else
|
|
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
|
|
|
|
for interface in $interfaces; do
|
|
file=/proc/sys/net/ipv4/conf/$interface/rp_filter
|
|
if [ -f $file ]; then
|
|
echo 1 > $file
|
|
else
|
|
error_message \
|
|
"Warning: Cannot set route filtering on $interface"
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
#
|
|
# IP Forwarding
|
|
#
|
|
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
|
|
}
|
|
|
|
#
|
|
# 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
|
|
#
|
|
while read client server policy loglevel synparams; do
|
|
expandv client server policy loglevel synparams
|
|
|
|
chain=${client}2${server}
|
|
|
|
[ -n "$synparams" ] && setup_syn_flood_chain $chain $synparams
|
|
|
|
if havechain $chain; then
|
|
[ -n "$synparams" ] && \
|
|
run_iptables -I $chain 2 -p tcp --syn -j @$chain
|
|
else
|
|
#
|
|
# A wild-card rule. Create the chain and add policy
|
|
# rules
|
|
#
|
|
# We must include the ESTABLISHED and RELATED state
|
|
# rule here to account for replys and reverse
|
|
# related sessions associated with sessions going
|
|
# in the other direction
|
|
#
|
|
createchain $chain
|
|
|
|
[ "$client" = "all" -o "$server" = "all" ] && \
|
|
policy_rules $chain $policy $loglevel
|
|
|
|
[ -n "$synparams" ] && \
|
|
[ $policy = ACCEPT -o $policy = CONTINUE ] && \
|
|
run_iptables -I $chain 2 -p tcp --syn -j @$chain
|
|
fi
|
|
|
|
done < $TMP_DIR/policy
|
|
#
|
|
# Add policy rules to canonical chains
|
|
#
|
|
for zone in $FW $zones; do
|
|
setup_intrazone $zone
|
|
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
|
|
|
|
havenatchain $destchain && \
|
|
run_iptables -t nat -A $sourcechain $@ -j $destchain
|
|
}
|
|
|
|
#
|
|
# Jump to a RULES chain from one of the builtin nat chains
|
|
#
|
|
# If NAT_BEFORE_RULES then append the rule to the chain; otherwise, insert
|
|
# the jump near the front of the builtin chain
|
|
#
|
|
addrulejump() # $1 = BUILTIN chain, $2 = user chain, $3 - * other arguments
|
|
{
|
|
local sourcechain=$1 destchain=$2
|
|
shift
|
|
shift
|
|
|
|
if havenatchain $destchain; then
|
|
if [ -n "$NAT_BEFORE_RULES" ]; then
|
|
run_iptables -t nat -A $sourcechain $@ -j $destchain
|
|
else
|
|
eval run_iptables -t nat -I $sourcechain \
|
|
\$${sourcechain}_rule $@ -j $destchain
|
|
eval ${sourcechain}_rule=\$\(\(\$${sourcechain}_rule + 1\)\)
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#
|
|
# 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
|
|
|
|
multi_interfaces=`find_interfaces_by_option multi`
|
|
|
|
> ${STATEDIR}/chains
|
|
> ${STATEDIR}/zones
|
|
|
|
for zone in $zones; do
|
|
eval source_hosts=\$${zone}_hosts
|
|
|
|
echo $zone $source_hosts >> ${STATEDIR}/zones
|
|
|
|
chain1=`rules_chain $FW $zone`
|
|
chain2=`rules_chain $zone $FW`
|
|
|
|
echo "$FW $zone $chain1" >> ${STATEDIR}/chains
|
|
echo "$zone $FW $chain2" >> ${STATEDIR}/chains
|
|
|
|
for host in $source_hosts; do
|
|
interface=${host%:*}
|
|
subnet=${host#*:}
|
|
|
|
run_iptables -A OUTPUT -o $interface -d $subnet -j $chain1
|
|
|
|
#
|
|
# Add jumps from the builtin chains for DNAT and SNAT rules
|
|
#
|
|
addrulejump PREROUTING `dnat_chain $zone` -i $interface -s $subnet
|
|
addrulejump POSTROUTING `snat_chain $zone` -o $interface -d $subnet
|
|
|
|
run_iptables -A `input_chain $interface` -s $subnet -j $chain2
|
|
|
|
done
|
|
|
|
for zone1 in $zones; do
|
|
eval dest_hosts=\$${zone1}_hosts
|
|
|
|
chain="`rules_chain $zone $zone1`"
|
|
|
|
echo "$zone $zone1 $chain" >> ${STATEDIR}/chains
|
|
|
|
if havechain ${zone}2${zone1} || havechain ${zone1}2${zone}; then
|
|
have_canonical=Yes
|
|
else
|
|
have_canonical=
|
|
fi
|
|
|
|
for host in $source_hosts; do
|
|
interface=${host%:*}
|
|
subnet=${host#*:}
|
|
chain1=`forward_chain $interface`
|
|
|
|
if [ -n "$have_canonical" ]; then
|
|
multi=yes
|
|
else
|
|
case $interface in
|
|
*+*)
|
|
multi=yes
|
|
;;
|
|
*)
|
|
list_search $interface $multi_interfaces && multi=yes || multi=
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
for host1 in $dest_hosts; do
|
|
interface1=${host1%:*}
|
|
subnet1=${host1#*:}
|
|
|
|
if [ $interface != $interface1 -o -n "$multi" ]; then
|
|
run_iptables -A $chain1 -s $subnet \
|
|
-o $interface1 -d $subnet1 -j $chain
|
|
fi
|
|
done
|
|
done
|
|
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
|
|
done
|
|
|
|
complete_standard_chain INPUT all $FW
|
|
complete_standard_chain OUTPUT $FW all
|
|
complete_standard_chain FORWARD all all
|
|
|
|
run_iptables -D INPUT -m state --state ESTABLISHED -j ACCEPT
|
|
run_iptables -D OUTPUT -m state --state ESTABLISHED -j ACCEPT
|
|
run_iptables -D FORWARD -m state --state ESTABLISHED -j ACCEPT
|
|
|
|
run_iptables -D INPUT -p udp --dport 53 -j ACCEPT
|
|
run_iptables -D OUTPUT -p udp --dport 53 -j ACCEPT
|
|
run_iptables -D FORWARD -p udp --dport 53 -j ACCEPT
|
|
}
|
|
|
|
#
|
|
# Start/Restart the Firewall
|
|
#
|
|
define_firewall() # $1 = Command (Start or Restart)
|
|
{
|
|
if [ -f /etc/shorewall/startup_disabled ]; then
|
|
echo " Shorewall Startup is disabled -- to enable startup"
|
|
echo " after you have completed Shorewall configuration,"
|
|
echo " remove the file /etc/shorewall/startup_disabled"
|
|
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 2
|
|
fi
|
|
|
|
echo "${1}ing Shorewall..."
|
|
|
|
verify_os_version
|
|
|
|
load_kernel_modules
|
|
|
|
echo "Initializing..."
|
|
|
|
initialize_netfilter
|
|
|
|
echo "Configuring Proxy ARP"
|
|
|
|
setup_proxy_arp
|
|
|
|
setup_nat
|
|
|
|
echo "Adding Common Rules"
|
|
|
|
add_common_rules
|
|
|
|
tunnels=`find_file tunnels`
|
|
|
|
[ -f $tunnels ] && \
|
|
echo "Processing $tunnels..." && setup_tunnels $tunnels
|
|
|
|
maclist_hosts=`find_hosts_by_option maclist`
|
|
|
|
if [ -n "$maclist_hosts" ] ; then
|
|
setup_mac_lists
|
|
fi
|
|
|
|
rules=`find_file rules`
|
|
|
|
echo "Processing $rules..."
|
|
|
|
process_rules $rules
|
|
|
|
echo "Setting up ICMP Echo handling..."
|
|
|
|
filterping_interfaces="`find_interfaces_by_option filterping`"
|
|
noping_interfaces="`find_interfaces_by_option noping`"
|
|
|
|
for interface in $all_interfaces; do
|
|
if ! list_search $interface $filterping_interfaces; then
|
|
if list_search $interface $noping_interfaces; then
|
|
target=DROP
|
|
else
|
|
target=ACCEPT
|
|
fi
|
|
|
|
run_iptables -A `input_chain $interface` \
|
|
-p icmp --icmp-type echo-request -j $target
|
|
fi
|
|
done
|
|
|
|
policy=`find_file policy`
|
|
|
|
echo "Processing $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
|
|
|
|
[ -n "$TC_ENABLED" ] && setup_tc
|
|
|
|
echo "Activating Rules..."
|
|
|
|
activate_rules
|
|
|
|
[ -n "$aliases_to_add" ] && \
|
|
echo "Adding IP Addresses..." && \
|
|
add_ip_aliases
|
|
|
|
run_user_exit start
|
|
|
|
createchain shorewall no
|
|
|
|
date > $STATEDIR/restarted
|
|
|
|
report "Shorewall ${1}ed"
|
|
|
|
rm -rf $TMP_DIR
|
|
}
|
|
|
|
#
|
|
# Check the configuration
|
|
#
|
|
check_config() {
|
|
echo "Verifying Configuration..."
|
|
|
|
verify_os_version
|
|
|
|
load_kernel_modules
|
|
|
|
echo "Determining Zones..."
|
|
|
|
determine_zones
|
|
|
|
[ -z "$zones" ] && startup_error "ERROR: No Zones Defined"
|
|
|
|
display_list "Zones:" $zones
|
|
|
|
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 rules file..."
|
|
|
|
validate_rules
|
|
|
|
echo "Validating policy file..."
|
|
|
|
validate_policy
|
|
|
|
rm -rf $TMP_DIR
|
|
|
|
echo "Configuration Validated"
|
|
}
|
|
|
|
#
|
|
# Rebuild the common chain
|
|
#
|
|
refresh_firewall()
|
|
{
|
|
echo "Refreshing Shorewall..."
|
|
|
|
echo "Determining Zones and Interfaces..."
|
|
|
|
determine_zones
|
|
|
|
[ -z "$zones" ] && startup_error "ERROR: No Zones Defined"
|
|
|
|
determine_interfaces
|
|
|
|
run_user_exit refresh
|
|
|
|
run_iptables -F common
|
|
|
|
echo "Adding Common Rules"
|
|
|
|
build_common_chain
|
|
|
|
#
|
|
# Blacklist
|
|
#
|
|
refresh_blacklist
|
|
|
|
report "Shorewall Refreshed"
|
|
|
|
rm -rf $TMP_DIR
|
|
}
|
|
|
|
#
|
|
# Query NetFilter about the existence of a filter chain
|
|
#
|
|
chain_exists() # $1 = chain name
|
|
{
|
|
qt iptables -L $1 -n
|
|
}
|
|
|
|
#
|
|
# Add a host or subnet to a zone
|
|
#
|
|
add_to_zone() # $1 = <interface>[:<hosts>] $2 = zone
|
|
{
|
|
local base
|
|
|
|
nat_chain_exists() # $1 = chain name
|
|
{
|
|
qt iptables -t nat -L $1 -n
|
|
}
|
|
|
|
do_iptables() # $@ = command
|
|
{
|
|
if ! iptables $@ ; then
|
|
startup_error "Error: can't add $1 to zone $2"
|
|
fi
|
|
}
|
|
|
|
output_rule_num() {
|
|
local num=`iptables -L OUTPUT -n --line-numbers | grep icmp | cut -d' ' -f1 | head -n1`
|
|
|
|
[ -n "$num" ] && echo $(($num+1))
|
|
}
|
|
#
|
|
# Isolate interface and host parts
|
|
#
|
|
interface=${1%:*}
|
|
host=${1#*:}
|
|
|
|
[ -z "$host" ] && host="0.0.0.0/0"
|
|
#
|
|
# Load $zones
|
|
#
|
|
determine_zones
|
|
#
|
|
# Validate Zone
|
|
#
|
|
zone=$2
|
|
|
|
validate_zone $zone || startup_error "Error: Unknown zone: $zone"
|
|
|
|
[ "$zone" = $FW ] && startup_error "Error: Can't add $1 to firewall zone"
|
|
#
|
|
# Be sure that Shorewall has been restarted using a DZ-aware version of the code
|
|
#
|
|
[ -f ${STATEDIR}/chains ] || startup_error "Error: ${STATEDIR}/chains -- file not found"
|
|
[ -f ${STATEDIR}/zones ] || startup_error "Error: ${STATEDIR}/zones -- file not found"
|
|
#
|
|
# Be sure that the interface was present at last [re]start
|
|
#
|
|
if ! chain_exists `input_chain $interface` ; then
|
|
startup_error "Error: Unknown interface $interface"
|
|
fi
|
|
#
|
|
# Build lists of interfaces with special rules
|
|
#
|
|
dhcp_interfaces=`find_interfaces_by_option dhcp`
|
|
blacklist_interfaces=`find_interfaces_by_option blacklist`
|
|
filterping_interfaces=`find_interfaces_by_option filterping`
|
|
maclist_interfaces=`find_interfaces_by_maclist`
|
|
#
|
|
# Normalize the first argument to this function
|
|
#
|
|
newhost="$interface:$host"
|
|
#
|
|
# Create a new Zone state file
|
|
#
|
|
> ${STATEDIR}/zones_$$
|
|
#
|
|
# Add $1 to the Zone state file
|
|
#
|
|
while read z hosts; do
|
|
if [ "$z" = "$zone" ]; then
|
|
for h in $hosts; do
|
|
if [ "$h" = "$newhost" ]; then
|
|
rm -f ${STATEDIR}/zones_$$
|
|
startup_error "Error: $1 already in zone $zone"
|
|
fi
|
|
done
|
|
|
|
[ -z "$hosts" ] && hosts=$newhost || hosts="$hosts $newhost"
|
|
fi
|
|
|
|
eval ${z}_hosts=\"$hosts\"
|
|
|
|
echo "$z $hosts" >> ${STATEDIR}/zones_$$
|
|
done < ${STATEDIR}/zones
|
|
|
|
mv -f ${STATEDIR}/zones_$$ ${STATEDIR}/zones
|
|
#
|
|
# 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 -I PREROUTING -i $interface -s $host -j $chain
|
|
fi
|
|
#
|
|
# Insert new rules into the input chains for the passed interface
|
|
#
|
|
while read z1 z2 chain; do
|
|
if [ "$z1" = "$zone" ]; then
|
|
if [ "$z2" = "$FW" ]; then
|
|
#
|
|
# We will insert the rule right after the DHCP, 'ping' and
|
|
# MAC rules (if any)
|
|
#
|
|
if list_search $interface $dhcp_interfaces; then
|
|
rulenum=3
|
|
else
|
|
rulenum=2
|
|
fi
|
|
|
|
if ! list_search $interface $filterping_interfaces; then
|
|
rulenum=$(($rulenum + 1))
|
|
fi
|
|
|
|
if ! list_search $interface $maclist_interfaces; then
|
|
rulenum=$(($rulenum + 1))
|
|
fi
|
|
|
|
do_iptables -I `input_chain $interface` $rulenum -s $host -j $chain
|
|
else
|
|
#
|
|
# Insert rules into the passed interface's forward chain
|
|
#
|
|
# We insert them after any blacklist/MAC verification rules
|
|
#
|
|
source_chain=`forward_chain $interface`
|
|
eval dest_hosts=\"\$${z2}_hosts\"
|
|
|
|
base=`chain_base $interface`
|
|
|
|
eval rulenum=\$${base}_rulenum
|
|
|
|
if [ -z "$rulenum" ]; then
|
|
if list_search $interface $blacklist_interfaces; then
|
|
rulenum=3
|
|
else
|
|
rulenum=2
|
|
fi
|
|
|
|
if ! list_search $interface $maclist_interfaces; then
|
|
rulenum=$(($rulenum + 1))
|
|
fi
|
|
fi
|
|
|
|
for h in $dest_hosts; do
|
|
iface=${h%:*}
|
|
hosts=${h#*:}
|
|
|
|
if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
|
|
do_iptables -I $source_chain $rulenum -s $host -o $iface -d $hosts -j $chain
|
|
rulenum=$(($rulenum + 1))
|
|
fi
|
|
done
|
|
|
|
eval ${base}_rulenum=$rulenum
|
|
|
|
fi
|
|
elif [ "$z2" = "$zone" ]; then
|
|
if [ "$z1" = "$FW" ]; then
|
|
#
|
|
# Add a rule to the OUTPUT chain -- always after the icmp * ACCEPT rule
|
|
#
|
|
do_iptables -I OUTPUT `output_rule_num` -o $interface -d $host -j $chain
|
|
else
|
|
#
|
|
# Insert rules into the source interface's forward chain
|
|
#
|
|
# We insert them after any blacklist rules
|
|
#
|
|
eval source_hosts=\"\$${z1}_hosts\"
|
|
|
|
for h in $source_hosts; do
|
|
iface=${h%:*}
|
|
hosts=${h#*:}
|
|
|
|
base=`chain_base $iface`
|
|
|
|
eval rulenum=\$${base}_rulenum
|
|
|
|
if [ -z "$rulenum" ]; then
|
|
if list_search $iface $blacklist_interfaces; then
|
|
rulenum=3
|
|
else
|
|
rulenum=2
|
|
fi
|
|
fi
|
|
|
|
if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
|
|
do_iptables -I `forward_chain $iface` $rulenum -s $hosts -o $interface -d $host -j $chain
|
|
rulenum=$(($rulenum + 1))
|
|
fi
|
|
|
|
eval ${base}_rulenum=$rulenum
|
|
done
|
|
fi
|
|
fi
|
|
done < ${STATEDIR}/chains
|
|
|
|
echo "$1 added to zone $2"
|
|
}
|
|
|
|
#
|
|
# Delete a host or subnet from a zone
|
|
#
|
|
delete_from_zone() # $1 = <interface>[:<hosts>] $2 = zone
|
|
{
|
|
#
|
|
# Delete the subnect host(s) from the zone state file
|
|
#
|
|
delete_from_zones_file()
|
|
{
|
|
> ${STATEDIR}/zones_$$
|
|
|
|
while read z hosts; do
|
|
if [ "$z" = "$zone" ]; then
|
|
temp=$hosts
|
|
hosts=
|
|
|
|
for h in $temp; do
|
|
if [ "$h" = "$delhost" ]; then
|
|
echo Yes
|
|
else
|
|
hosts="$hosts $h"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo "$z $hosts" >> ${STATEDIR}/zones_$$
|
|
done < ${STATEDIR}/zones
|
|
|
|
mv -f ${STATEDIR}/zones_$$ ${STATEDIR}/zones
|
|
}
|
|
#
|
|
# Isolate interface and host parts
|
|
#
|
|
interface=${1%:*}
|
|
host=${1#*:}
|
|
|
|
[ -z "$host" ] && host="0.0.0.0/0"
|
|
#
|
|
# Load $zones
|
|
#
|
|
determine_zones
|
|
|
|
zone=$2
|
|
|
|
validate_zone $zone || startup_error "Error: Unknown zone: $zone"
|
|
|
|
[ "$zone" = $FW ] && startup_error "Error: Can't remove $1 from firewall zone"
|
|
#
|
|
# Be sure that Shorewall has been restarted using a DZ-aware version of the code
|
|
#
|
|
[ -f ${STATEDIR}/chains ] || startup_error "Error: ${STATEDIR}/chains -- file not found"
|
|
[ -f ${STATEDIR}/zones ] || startup_error "Error: ${STATEDIR}/zones -- file not found"
|
|
#
|
|
# Be sure that the interface was present at last [re]start
|
|
#
|
|
if ! chain_exists `input_chain $interface` ; then
|
|
startup_error "Error: Unknown interface $interface"
|
|
fi
|
|
#
|
|
# Normalize the first argument to this function
|
|
#
|
|
delhost="$interface:$host"
|
|
#
|
|
# Delete the passed hosts from the zone state file
|
|
#
|
|
[ -z "`delete_from_zones_file`" ] && \
|
|
error_message "Warning: $1 does not appear to be in zone $2"
|
|
#
|
|
# Construct the zone host maps
|
|
#
|
|
while read z hosts; do
|
|
eval ${z}_hosts=\"$hosts\"
|
|
done < ${STATEDIR}/zones
|
|
#
|
|
# Delete any nat table entries for the host(s)
|
|
#
|
|
qt iptables -t nat -D PREROUTING -i $interface -s $host -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 `input_chain $interface` -i $interface -s $host -j $chain
|
|
else
|
|
source_chain=`forward_chain $interface`
|
|
eval dest_hosts=\"\$${z2}_hosts\"
|
|
|
|
for h in $dest_hosts $delhost; do
|
|
iface=${h%:*}
|
|
hosts=${h#*:}
|
|
|
|
if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
|
|
qt iptables -D $source_chain -s $host -o $iface -d $hosts -j $chain
|
|
fi
|
|
done
|
|
fi
|
|
elif [ "$z2" = "$zone" ]; then
|
|
if [ "$z1" = "$FW" ]; then
|
|
qt iptables -D OUTPUT -o $interface -d $host -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
|
|
qt iptables -D `forward_chain $iface` -s $hosts -o $interface -d $host -j $chain
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
done < ${STATEDIR}/chains
|
|
|
|
echo "$1 removed from zone $2"
|
|
}
|
|
|
|
#
|
|
# 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
|
|
|
|
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
|
|
#
|
|
# Clear all configuration variables
|
|
#
|
|
version=
|
|
FW=
|
|
SUBSYSLOCK=
|
|
STATEDIR=
|
|
ALLOWRELATED=
|
|
LOGRATE=
|
|
LOGBURST=
|
|
LOGPARMS=
|
|
NAT_ENABLED=
|
|
MANGLE_ENABLED=
|
|
ADD_IP_ALIASES=
|
|
ADD_SNAT_ALIASES=
|
|
TC_ENABLED=
|
|
LOGUNCLEAN=
|
|
BLACKLIST_DISPOSITION=
|
|
BLACKLIST_LOGLEVEL=
|
|
CLAMPMSS=
|
|
ROUTE_FILTER=
|
|
NAT_BEFORE_RULES=
|
|
MULTIPORT=
|
|
DETECT_DNAT_IPADDRS=
|
|
MERGE_HOSTS=
|
|
MUTEX_TIMEOUT=
|
|
NEWNOTSYN=
|
|
LOGNEWNOTSYN=
|
|
FORWARDPING=
|
|
MACLIST_DISPOSITION=
|
|
MACLIST_LOG_LEVEL=
|
|
stopping=
|
|
have_mutex=
|
|
masq_seq=1
|
|
nonat_seq=1
|
|
aliases_to_add=
|
|
|
|
TMP_DIR=/tmp/shorewall-$$
|
|
rm -rf $TMP_DIR
|
|
mkdir -p $TMP_DIR && chmod 700 $TMP_DIR || \
|
|
startup_error "Can't create $TMP_DIR"
|
|
|
|
trap "rm -rf $TMP_DIR; my_mutex_off; exit 2" 1 2 3 4 5 6 9
|
|
|
|
functions=/usr/lib/shorewall/functions
|
|
|
|
if [ -f $functions ]; then
|
|
. $functions
|
|
else
|
|
startup_error "$functions does not exist!"
|
|
fi
|
|
|
|
version_file=/usr/lib/shorewall/version
|
|
|
|
[ -f $version_file ] && version=`cat $version_file`
|
|
#
|
|
# Strip the files that we use often
|
|
#
|
|
strip_file interfaces
|
|
strip_file hosts
|
|
|
|
run_user_exit shorewall.conf
|
|
run_user_exit params
|
|
|
|
[ -z "${STATEDIR}" ] && STATEDIR=/var/state/shorewall
|
|
|
|
[ -d $STATEDIR ] || mkdir -p $STATEDIR
|
|
|
|
|
|
[ -z "$FW" ] && FW=fw
|
|
|
|
ALLOWRELATED="`added_param_value_yes ALLOWRELATED $ALLOWRELATED`"
|
|
NAT_ENABLED="`added_param_value_yes NAT_ENABLED $NAT_ENABLED`"
|
|
MANGLE_ENABLED="`added_param_value_yes MANGLE_ENABLED $MANGLE_ENABLED`"
|
|
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
|
|
LOGPARMS="--match limit"
|
|
[ -n "$LOGRATE" ] && LOGPARMS="$LOGPARMS --limit $LOGRATE"
|
|
[ -n "$LOGBURST" ] && LOGPARMS="$LOGPARMS --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
|
|
|
|
CLAMPMSS=`added_param_value_no CLAMPMSS $CLAMPMSS`
|
|
ADD_SNAT_ALIASES=`added_param_value_no ADD_SNAT_ALIASES $ADD_SNAT_ALIASES`
|
|
ROUTE_FILTER=`added_param_value_no ROUTE_FILTER $ROUTE_FILTER`
|
|
NAT_BEFORE_RULES=`added_param_value_yes NAT_BEFORE_RULES $NAT_BEFORE_RULES`
|
|
MULTIPORT=`added_param_value_no MULTIPORT $MULTIPORT`
|
|
DETECT_DNAT_IPADDRS=`added_param_value_no DETECT_DNAT_IPADDRS $DETECT_DNAT_IPADDRS`
|
|
MERGE_HOSTS=`added_param_value_no MERGE_HOSTS $MERGE_HOSTS`
|
|
FORWARDPING=`added_param_value_no FORWARDPING $FORWARDPING`
|
|
NEWNOTSYN=`added_param_value_yes NEWNOTSYN $NEWNOTSYN`
|
|
|
|
maclist_target=reject
|
|
|
|
if [ -n "$MACLIST_DISPOSITION" ] ; then
|
|
case $MACLIST_DISPOSITION in
|
|
REJECT)
|
|
;;
|
|
ACCEPT|DROP)
|
|
maclist_target=$MACLIST_DISPOSITION
|
|
;;
|
|
*)
|
|
startup_error "Invalid value ($MACLIST_DISPOSITION) for MACLIST_DISPOSITION"
|
|
;;
|
|
esac
|
|
else
|
|
MACLIST_DISPOSITION=REJECT
|
|
fi
|
|
|
|
}
|
|
|
|
#
|
|
# Give Usage Information
|
|
#
|
|
usage() {
|
|
echo "Usage: $0 [debug] {start|stop|reset|restart|status|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
|
|
echo -n "Stopping Shorewall..."
|
|
determine_zones
|
|
stop_firewall
|
|
[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
|
|
echo "done."
|
|
my_mutex_off
|
|
;;
|
|
|
|
start)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if qt iptables -L shorewall -n ; then
|
|
[ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
|
|
echo "Shorewall Already Started"
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 0;
|
|
fi
|
|
define_firewall "Start" && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
|
|
my_mutex_off
|
|
;;
|
|
|
|
restart)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if qt iptables -L shorewall -n ; then
|
|
define_firewall "Restart"
|
|
else
|
|
echo "Shorewall Not Currently Running"
|
|
define_firewall "Start"
|
|
fi
|
|
|
|
[ $? -eq 0 ] && [ -n "$SUBSYSLOCK" ] && touch $SUBSYSLOCK
|
|
my_mutex_off
|
|
;;
|
|
|
|
status)
|
|
[ $# -ne 1 ] && usage
|
|
echo -e "Shorewall-$version Status at $HOSTNAME - `date`\\n"
|
|
iptables -L -n -v
|
|
;;
|
|
|
|
reset)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if ! qt iptables -L shorewall -n ; then
|
|
echo "Shorewall Not Started"
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 2;
|
|
fi
|
|
iptables -Z
|
|
iptables -t nat -Z
|
|
iptables -t mangle -Z
|
|
report "Shorewall Counters Reset"
|
|
date > $STATEDIR/restarted
|
|
my_mutex_off
|
|
;;
|
|
|
|
refresh)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if ! qt iptables -L shorewall -n ; then
|
|
echo "Shorewall Not Started"
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 2;
|
|
fi
|
|
refresh_firewall;
|
|
my_mutex_off
|
|
;;
|
|
|
|
clear)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
echo -n "Clearing Shorewall..."
|
|
determine_zones
|
|
clear_firewall
|
|
[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
|
|
echo "done."
|
|
my_mutex_off
|
|
;;
|
|
|
|
check)
|
|
[ $# -ne 1 ] && usage
|
|
do_initialize
|
|
check_config
|
|
;;
|
|
|
|
add)
|
|
[ $# -ne 3 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if ! qt iptables -L shorewall -n ; then
|
|
echo "Shorewall Not Started"
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 2;
|
|
fi
|
|
add_to_zone $2 $3
|
|
my_mutex_off
|
|
;;
|
|
|
|
delete)
|
|
[ $# -ne 3 ] && usage
|
|
do_initialize
|
|
my_mutex_on
|
|
if ! qt iptables -L shorewall -n ; then
|
|
echo "Shorewall Not Started"
|
|
[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
|
|
my_mutex_off
|
|
exit 2;
|
|
fi
|
|
delete_from_zone $2 $3
|
|
my_mutex_off
|
|
;;
|
|
|
|
*)
|
|
usage
|
|
;;
|
|
|
|
esac
|