diff --git a/Shorewall/lib.actions b/Shorewall/lib.actions new file mode 100644 index 000000000..a1ab355ec --- /dev/null +++ b/Shorewall/lib.actions @@ -0,0 +1,828 @@ +#!/bin/sh +# +# Shorewall 3.2 -- /usr/share/shorewall/lib.actions +# +# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] +# +# (c) 1999,2000,2001,2002,2003,2004,2005,2006 - Tom Eastep (teastep@shorewall.net) +# +# Complete documentation is available at http://shorewall.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of Version 2 of the GNU General Public License +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA + +# +# Add one Filter Rule from an action -- Helper function for the action file processor +# +# The caller has established the following variables: +# COMMAND = current command. +# client = SOURCE IP or MAC +# server = DESTINATION IP or interface +# protocol = Protocol +# address = Original Destination Address +# port = Destination Port +# cport = Source Port +# multioption = String to invoke multiport match if appropriate +# action = The chain for this rule +# ratelimit = Optional rate limiting clause +# userandgroup = owner match clause +# logtag = Log tag +# +add_an_action() +{ + local chain1 + + do_ports() { + if [ -n "$port" ]; then + dports="--dport" + if [ -n "$multioption" -a "$port" != "${port%,*}" ]; then + multiport="$multioption" + dports="--dports" + fi + dports="$dports $port" + fi + + if [ -n "$cport" ]; then + sports="--sport" + if [ -n "$multioption" -a "$cport" != "${cport%,*}" ]; then + multiport="$multioption" + sports="--sports" + fi + sports="$sports $cport" + fi + } + + interface_error() + { + fatal_error "Unknown interface $1 in rule: \"$rule\"" + } + + action_interface_verify() + { + verify_interface $1 || interface_error $1 + } + + handle_exclusion() + { + build_exclusion_chain chain1 filter "$excludesource" "$excludedest" + + run_iptables -A $chain $(fix_bang $cli $proto $sports $multiport $dports) $user -j $chain1 + + cli= + proto= + sports= + multiport= + dports= + user= + } + + do_ipp2p() { + [ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. Rule: \"$rule\"" + + dports="-m ipp2p --${port:-ipp2p}" + + case $proto in + ipp2p|IPP2P) + proto=tcp + port= + do_ports + ;; + ipp2p:udpIPP2P:UDP) + proto=udp + port= + do_ports + ;; + ipp2p:all|IPP2P:ALL) + proto=all + ;; + esac + } + + # Set source variables. The 'cli' variable will hold the client match predicate(s). + + cli= + + case "$client" in + -) + ;; + *:*) + action_interface_verify ${client%:*} + cli="$(match_source_dev ${client%:*}) $(source_ip_range ${client#*:})" + ;; + *.*.*|+*|!+*) + cli="$(source_ip_range $client)" + ;; + ~*|!~*) + cli=$(mac_match $client) + ;; + *) + if [ -n "$client" ]; then + action_interface_verify $client + cli="$(match_source_dev $client)" + fi + ;; + esac + + # Set destination variables - 'serv' and 'dest_interface' hold the server match predicate(s). + + dest_interface= + serv= + + case "$server" in + -) + ;; + *.*.*|+*|!+*) + serv=$server + ;; + ~*|!~*) + fatal_error "Rule \"$rule\" - Destination may not be specified by MAC Address" + ;; + *) + if [ -n "$server" ]; then + action_interface_verify $server + dest_interface="$(match_dest_dev $server)" + fi + ;; + esac + + # Setup protocol and port variables + + sports= + dports= + proto=$protocol + servport=$serverport + multiport= + chain1=$chain + user="$userandgroup" + + [ x$port = x- ] && port= + [ x$cport = x- ] && cport= + + case $proto in + tcp|TCP|6) + do_ports + ;; + tcp:syn) + proto="$proto --syn" + do_ports + ;; + udp|UDP|17) + do_ports + ;; + icmp|ICMP|1) + [ -n "$port" ] && dports="--icmp-type $port" + ;; + ipp2p|IPP2P|ipp2p:*|IPP2P:*) + do_ipp2p + ;; + *) + [ -n "$port" ] && \ + fatal_error "Port number not allowed with protocol \"$proto\"; rule: \"$rule\"" + ;; + esac + + proto="${proto:+-p $proto}" + + # Some misc. setup + + case "$logtarget" in + LOG) + [ -z "$loglevel" ] && fatal_error "LOG requires log level" + ;; + esac + + if [ -n "${excludesource}${excludedest}" ]; then + handle_exclusion + fi + + if [ -n "${serv}" ]; then + for serv1 in $(separate_list $serv); do + for srv in $(firewall_ip_range $serv1); do + if [ -n "$loglevel" ]; then + log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \ + $(fix_bang $proto $sports $multiport $cli $(dest_ip_range $srv) $dports) + fi + + run_iptables2 -A $chain1 $proto $multiport $cli $sports \ + $(dest_ip_range $srv) $dports $ratelimit $user -j $target + done + done + else + if [ -n "$loglevel" ]; then + log_rule_limit $loglevel $chain1 $action $logtarget "$ratelimit" "$logtag" -A $user \ + $(fix_bang $proto $sports $multiport $cli $dest_interface $dports) + fi + + run_iptables2 -A $chain1 $proto $multiport $cli $dest_interface $sports \ + $dports $ratelimit $user -j $target + fi +} + +# +# Process a record from an action file +# +process_action() # $1 = chain (Chain to add the rules to) + # $2 = action (The action name for logging purposes) + # $3 = target (The (possibly modified) contents of the TARGET column) + # $4 = clients + # $5 = servers + # $6 = protocol + # $7 = ports + # $8 = cports + # $9 = ratelimit + # $10 = userspec +{ + local chain="$1" + local action="$2" + local target="$3" + local clients="$4" + local servers="$5" + local protocol="$6" + local ports="$7" + local cports="$8" + local ratelimit="$9" + local userspec="${10}" + local userandgroup= + local logtag= + + if [ -n "$ratelimit" ]; then + case $ratelimit in + -) + ratelimit= + ;; + *:*) + ratelimit="-m limit --limit ${ratelimit%:*} --limit-burst ${ratelimit#*:}" + ;; + *) + ratelimit="-m limit --limit $ratelimit" + ;; + esac + fi + + [ "x$userspec" = "x-" ] && userspec= + + if [ -n "$userspec" ]; then + userandgroup="-m owner" + + case "$userspec" in + !*+*) + if [ -n "${userspec#*+}" ]; then + userandgroup="$userandgroup ! --cmd-owner ${userspec#*+}" + fi + userspec=${userspec%+*} + ;; + *+*) + if [ -n "${userspec#*+}" ]; then + userandgroup="$userandgroup --cmd-owner ${userspec#*+}" + fi + userspec=${userspec%+*} + ;; + esac + + case "$userspec" in + !*:*) + if [ "$userspec" != "!:" ]; then + temp="${userspec#!}" + temp="${temp%:*}" + [ -n "$temp" ] && userandgroup="$userandgroup ! --uid-owner $temp" + temp="${userspec#*:}" + [ -n "$temp" ] && userandgroup="$userandgroup ! --gid-owner $temp" + fi + ;; + *:*) + if [ "$userspec" != ":" ]; then + temp="${userspec%:*}" + [ -n "$temp" ] && userandgroup="$userandgroup --uid-owner $temp" + temp="${userspec#*:}" + [ -n "$temp" ] && userandgroup="$userandgroup --gid-owner $temp" + fi + ;; + !*) + [ "$userspec" != "!" ] && userandgroup="$userandgroup ! --uid-owner ${userspec#!}" + ;; + *) + [ -n "$userspec" ] && userandgroup="$userandgroup --uid-owner $userspec" + ;; + esac + + [ "$userandgroup" = "-m owner" ] && userandgroup= + fi + + # Isolate log level + + if [ "$target" = "${target%:*}" ]; then + loglevel= + else + loglevel="${target#*:}" + target="${target%%:*}" + expandv loglevel + if [ "$loglevel" != "${loglevel%:*}" ]; then + logtag="${loglevel#*:}" + loglevel="${loglevel%:*}" + expandv logtag + fi + + case $loglevel in + none*) + loglevel= + [ $target = LOG ] && return + ;; + esac + + loglevel=${loglevel%\!} + fi + + logtarget="$target" + + case $target in + REJECT) + target=reject + ;; + CONTINUE) + target=RETURN + ;; + *) + ;; + esac + + excludesource= + + case ${clients:=-} in + *!*!*) + fatal_error "Invalid SOURCE in rule \"$rule\"" + ;; + !*) + if [ $(list_count $clients) -gt 1 ]; then + excludesource=${clients#!} + clients= + fi + ;; + *!*) + excludesource=${clients#*!} + clients=${clients%!*} + ;; + esac + + excludedest= + + case ${servers:=-} in + *!*!*) + fatal_error "Invalid DEST in rule \"$rule\"" + ;; + !*) + if [ $(list_count $servers) -gt 1 ]; then + excludedest=${servers#*!} + servers= + fi + ;; + *!*) + excludedest=${servers#*!} + servers=${servers%!*} + ;; + esac + + # Generate Netfilter rule(s) + + [ "x$protocol" = "x-" ] && protocol=all || protocol=${protocol:=all} + + if [ -n "$XMULTIPORT" ] && \ + ! list_search $protocol "icmp" "ICMP" "1" && \ + [ $(( $(list_count $ports) + $(list_count1 $(split $ports ) ) )) -le 16 -a \ + $(( $(list_count $cports) + $(list_count1 $(split $cports ) ) )) -le 16 ] + then + # + # Extended MULTIPORT is enabled, and less than + # 16 ports are listed (port ranges count as two ports) - use multiport match. + # + multioption="-m multiport" + for client in $(separate_list $clients); do + for server in $(separate_list $servers); do + # + # add_an_action() modifies these so we must set their values each time + # + port=${ports:=-} + cport=${cports:=-} + add_an_action + done + done + elif [ -n "$MULTIPORT" ] && \ + ! list_search $protocol "icmp" "ICMP" "1" && \ + [ "$ports" = "${ports%:*}" -a \ + "$cports" = "${cports%:*}" -a \ + $(list_count $ports) -le 15 -a \ + $(list_count $cports) -le 15 ] + then + # + # MULTIPORT is enabled, there are no port ranges in the rule and less than + # 16 ports are listed - use multiport match. + # + multioption="-m multiport" + for client in $(separate_list $clients); do + for server in $(separate_list $servers); do + # + # add_an_action() modifies these so we must set their values each time + # + port=${ports:=-} + cport=${cports:=-} + add_an_action + done + done + else + # + # MULTIPORT is disabled or the rule isn't compatible with multiport match + # + multioption= + for client in $(separate_list $clients); do + for server in $(separate_list $servers); do + for port in $(separate_list ${ports:=-}); do + for cport in $(separate_list ${cports:=-}); do + add_an_action + done + done + done + done + fi + # + # Report Result + # + progress_message " Rule \"$rule\" $DONE." + save_progress_message_short " Rule \\\"$rule\\\" added." +} + +# +# This function determines the logging for a subordinate action or a rule within a subordinate action +# +merge_levels() # $1=level at which superior action is called, $2=level at which the subordinate rule is called +{ + local superior=$1 subordinate=$2 + + set -- $(split $1) + + case $superior in + *:*:*) + case $2 in + 'none!') + echo ${subordinate%%:*}:'none!':$3 + return + ;; + *'!') + echo ${subordinate%%:*}:$2:$3 + return + ;; + *) + case $subordinate in + *:*:*) + echo $subordinate + return + ;; + *:*) + echo $subordinate:$3 + return + ;; + *) + echo ${subordinate%%:*}:$2:$3 + return + ;; + esac + ;; + esac + ;; + *:*) + case $2 in + 'none!') + echo ${subordinate%%:*}:'none!' + return + ;; + *'!') + echo ${subordinate%%:*}:$2 + return + ;; + *) + case $subordinate in + *:*) + echo $subordinate + return + ;; + *) + echo ${subordinate%%:*}:$2 + return + ;; + esac + ;; + esac + ;; + *) + echo $subordinate + ;; + esac +} + +# +# The next three functions implement the three phases of action processing. +# +# The first phase (process_actions1) occurs before the rules file is processed. ${SHAREDIR}/actions.std +# and ${CONFDIR}/actions are scanned (in that order) and for each action: +# +# a) The related action definition file is located and scanned. +# b) Forward and unresolved action references are trapped as errors. +# c) A dependency graph is created. For each , the variable 'requiredby_' lists the +# action[:level[:tag]] of each action invoked by . +# d) All actions are listed in the global variable ACTIONS. +# e) Common actions are recorded (in variables of the name _common) and are added to the global +# USEDACTIONS +# +# As the rules file is scanned, each action[:level[:tag]] is merged onto the USEDACTIONS list. When an +# is merged onto this list, its action chain is created. Where logging is specified, a chain with the name +# %n is used where the name is truncated on the right where necessary to ensure that the total +# length of the chain name does not exceed 30 characters. +# +# The second phase (process_actions2) occurs after the rules file is scanned. The transitive closure of +# USEDACTIONS is generated; again, as new actions are merged onto this list, their action chains are created. +# +# The final phase (process_actions3) is to traverse the USEDACTIONS list populating each chain appropriately +# by reading the action definition files and creating rules. Note that a given action definition file is +# processed once for each unique [:level[:tag]] applied to an invocation of the action. +# +process_actions1() { + + strip_file actions + + strip_file actions.std ${SHAREDIR}/actions.std + + for inputfile in actions.std actions; do + while read xaction rest; do + [ "x$rest" = x ] || fatal_error "Invalid Action: $xaction $rest" + + case $xaction in + *:*) + error_message "WARNING: Default Actions are now specified in /etc/shorewall/shorewall.conf" + xaction=${xaction%:*} + ;; + esac + + [ -z "$xaction" ] && continue + + [ "$xaction" = "$(chain_base $xaction)" ] || fatal_error "Invalid Action Name: $xaction" + + if ! list_search $xaction $ACTIONS; then + f=action.$xaction + fn=$(find_file $f) + + eval requiredby_${action}= + + if [ -f $fn ]; then + progress_message2 " Pre-processing $fn..." + strip_file $f $fn + while read xtarget xclients xservers xprotocol xports xcports xratelimit $xuserspec; do + expandv xtarget + temp="${xtarget%%:*}" + case "$temp" in + ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) + ;; + *) + if list_search $temp $ACTIONS; then + eval requiredby=\"\$requiredby_${xaction}\" + list_search $xtarget $requiredby || eval requiredby_${xaction}=\"$requiredby $xtarget\" + else + temp=$(map_old_action $temp) + + case $temp in + */*) + param=${temp#*/} + case $param in + ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) + ;; + *) + rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" + fatal_error "Invalid Macro Parameter in rule \"$rule\"" + ;; + esac + temp=${temp%%/*} + ;; + esac + + f1=macro.${temp} + fn=$(find_file $f1) + + if [ ! -f $TMP_DIR/$f1 ]; then + # + # We must only verify macros once to ensure that they don't invoke any non-standard actions + # + if [ -f $fn ]; then + strip_file $f1 $fn + + progress_message " ..Expanding Macro $fn..." + + while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do + expandv mtarget + temp="${mtarget%%:*}" + case "$temp" in + ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE|PARAM) + ;; + *) + rule="$mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec" + fatal_error "Invalid TARGET in rule \"$rule\"" + esac + done < $TMP_DIR/$f1 + + progress_message " ..End Macro" + else + rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" + fatal_error "Invalid TARGET in rule \"$rule\"" + fi + fi + fi + ;; + + esac + done < $TMP_DIR/$f + else + fatal_error "Missing Action File: $f" + fi + + ACTIONS="$ACTIONS $xaction" + fi + done < $TMP_DIR/$inputfile + done + + for action in $DROP_DEFAULT $REJECT_DEFAULT; do + case $action in + none) + ;; + *) + if list_search $action $ACTIONS; then + list_search $action $USEDACTIONS || USEDACTIONS="$USEDACTIONS $action" + fi + ;; + esac + done +} + +process_actions2() { + + local interfaces="$(find_interfaces_by_option upnp)" + + if [ -n "$interfaces" ]; then + if ! list_search forwardUPnP $USEDACTIONS; then + error_message "WARNING:Missing forwardUPnP rule (required by 'upnp' interface option on $interfaces)" + fi + fi + + progress_message " Generating Transitive Closure of Used-action List..." + + changed=Yes + + while [ -n "$changed" ]; do + changed= + for xaction in $USEDACTIONS; do + + eval required=\"\$requiredby_${xaction%%:*}\" + + for xaction1 in $required; do + # + # Generate the action that will be passed to process_action by merging the + # logging specified when the action was invoked with the logging in the + # invocation of the subordinate action (usually no logging) + # + xaction2=$(merge_levels $xaction $xaction1) + + if ! list_search $xaction2 $USEDACTIONS; then + # + # We haven't seen this one before -- create and record a chain to handle it + # + USEDACTIONS="$USEDACTIONS $xaction2" + createactionchain $xaction2 + changed=Yes + fi + done + done + done +} + +# +# process_action3() is in the compiler. What follows is called from that function when the action +# being processed is not a builtin. + +process_actions3a() { + + local f=action.$xaction1 + + progress_message2 "$DOING $(find_file $f) for Chain $xchain..." + + while read xtarget xclients xservers xprotocol xports xcports xratelimit xuserspec; do + expandv xtarget + # + # Generate the target:level:tag to pass to process_action() + # + xaction2=$(merge_levels $xaction $xtarget) + + is_macro= + param= + + xtarget1=${xaction2%%:*} + + case $xtarget1 in + ACCEPT|DROP|REJECT|LOG|QUEUE|CONTINUE) + # + # Builtin target -- Nothing to do + # + ;; + *) + if list_search $xtarget1 $ACTIONS ; then + # + # An Action -- Replace the target from the file + # -- with the one generated above + xtarget=$xaction2 + # + # And locate the chain for that action:level:tag + # + xaction2=$(find_logactionchain $xtarget) + else + is_macro=yes + fi + ;; + esac + + expandv xclients xservers xprotocol xports xcports xratelimit xuserspec + + if [ -n "$is_macro" ]; then + + xtarget1=$(map_old_action $xtarget1) + + case $xtarget1 in + */*) + param=${xtarget1#*/} + xtarget1=${xtarget1%%/*} + ;; + esac + + progress_message "..Expanding Macro $(find_file macro.$xtarget1)..." + while read mtarget mclients mservers mprotocol mports mcports mratelimit muserspec; do + expandv mtarget mclients mservers mprotocol mports mcports mratelimit muserspec + + mtarget=$(merge_levels $xaction2 $mtarget) + + case $mtarget in + PARAM|PARAM:*) + [ -n "$param" ] && mtarget=$(substitute_action $param $mtarget) || fatal_error "PARAM requires that a parameter be supplied in macro invocation" + ;; + esac + + if [ -n "$mclients" ]; then + case $mclients in + -|SOURCE) + mclients=${xclients} + ;; + DEST) + mclients=${xservers} + ;; + *) + mclients=$(merge_macro_source_dest $mclients $xclients) + ;; + esac + else + mclients=${xclients} + fi + + if [ -n "$mservers" ]; then + case $mservers in + -|DEST) + mservers=${xservers} + ;; + SOURCE) + mservers=${xclients} + ;; + *) + mservers=$(merge_macro_source_dest $mservers $xservers) + ;; + esac + else + mservers=${xserverss} + fi + + [ -n "$xprotocol" ] && [ "x${xprotocol}" != x- ] && mprotocol=$xprotocol + [ -n "$xports" ] && [ "x${xports}" != x- ] && mports=$xports + [ -n "$xcports" ] && [ "x${xcports}" != x- ] && mcports=$xcports + [ -n "$xratelimit" ] && [ "x${xratelimit}" != x- ] && mratelimit=$xratelimit + [ -n "$xuserspec" ] && [ "x${xuserspec}" != x- ] && muserspec=$xuserspec + + rule="$mtarget ${mclients:=-} ${mservers:=-} ${mprotocol:=-} ${mports:=-} ${mcports:=-} ${mratelimit:-} ${muserspec:=-}" + process_action $xchain $xaction1 $mtarget $mclients $mservers $mprotocol $mports $mcports $mratelimit $muserspec + done < $TMP_DIR/macro.$xtarget1 + progress_message "..End Macro" + else + rule="$xtarget $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec" + process_action $xchain $xaction1 $xaction2 $xclients $xservers $xprotocol $xports $xcports $xratelimit $xuserspec + fi + done < $TMP_DIR/$f +} diff --git a/Shorewall/shorewall.spec b/Shorewall/shorewall.spec index 3e093dd91..d1ae22aa5 100644 --- a/Shorewall/shorewall.spec +++ b/Shorewall/shorewall.spec @@ -114,6 +114,7 @@ fi %attr(0555,root,root) /usr/share/shorewall/firewall %attr(0555,root,root) /usr/share/shorewall/help %attr(0444,root,root) /usr/share/shorewall/lib.accounting +%attr(0444,root,root) /usr/share/shorewall/lib.actions %attr(0444,root,root) /usr/share/shorewall/lib.dynamiczones %attr(0444,root,root) /usr/share/shorewall/lib.maclist %attr(0444,root,root) /usr/share/shorewall/lib.nat