#---------------------------------------------------------------------------- # /etc/rc.d/fwrules-helper # helper functions for packet filter rules # # Creation: 2003-07-05 jw5 # Last Update: $Id$ #---------------------------------------------------------------------------- : ${IPTABLES:=sbin_iptables} : ${fwh_state:=/var/run/fwrules-helper.state} : ${tmpl_dir:=/etc/fwrules.tmpl} : ${ext_dir:=/etc/rc.d} fw_tmp=/tmp/fwrules-helper.$$ iptables_rules_added=0 reset_rules_added () { iptables_rules_added=0 } get_rules_added () { eval $1=$iptables_rules_added } set_rules_added () { iptables_rules_added=$1 } sbin_iptables () { if ! /sbin/iptables "$@" > $fw_tmp 2>&1; then log_error "Error executing iptables $@" log_error < $fw_tmp rm -f $fw_tmp return 1 fi rm -f $fw_tmp iptables_rules_added=`expr $iptables_rules_added + 1` } [ "$IP_NET_N" ] || . /var/run/ip_net.conf # read extensions for i in $ext_dir/fwrules-*.ext do if [ -f $i ] then if sh -c ". $i" > $fw_tmp 2>&1 then . $i else log_error "Error parsing extension $i, rules with this extension in them will fail." log_error < $fw_tmp fi rm -f $fw_tmp fi done assert_empty () { case x$2 in x) ;; *) set_error "$1: to many subexpressions after translation" ;; esac } get_limit() { limit= case x"$1" in x | xnone) ;; *) set -- `echo $1 | sed -e 's/:/ /'` case x$2 in x) limit="-m limit --limit $1" ;; *) limit="-m limit --limit $1 --limit-burst $2" ;; esac ;; esac } normalize_network () { res=$1 case $res in 0.0.0.0/0) ;; */*) res="`netcalc network $res`/`netcalc netmaskbits $res`" ;; esac } # mangle_params param option # - replace ':' with ' ', '-' with ':' mangle_params () { echo $1 | sed -e "s/:/ /g;s/\([0-9]\+\)-\([0-9]\+\)/\1:\2/g;" } recent_p=yes do_recent_param () { eval `echo $recent_param | sed -e 's/^\([a-z]*\)=\(.*\)/recent_tok=\1;recent_val=\2/'` recent_opt="$recent_opt --$recent_tok $recent_val" } do_recent () { recent_opt= set -- `echo $1 | sed -e 's/,/ /g'` while [ "$1" ]; do case $1 in \!*) recent_param=`echo $1 | sed -e 's/^!//'` recent_neg='!' ;; *) recent_param="$1" recent_neg= ;; esac case $recent_param in name=* | seconds=* | hitcount=*) do_recent_param $recent_param ;; set | update | rcheck | remove) recent_opt="$recent_opt $recent_neg --$recent_param" ;; rttl | rsource | rdest) recent_opt="$recent_opt --$recent_param" ;; *) set_error "recent: unknown option $recent_param" ;; esac shift done match_opt="$match_opt -m recent $recent_opt" } # mangle_ip_params param option # in: ip:port, ip or port[-range] # out: ip, ip_neg_opt; port, port_neg_opt, tcp_udp_needed # replaces '-' with ':' if no option given mangle_ip_params () { param=$1 opt=$2 case $param in *:*) set -- `echo $param | sed -e 's/:/ /'` ip=$1 port=$2 tcp_udp_needed='yes' ;; *) if echo $param | grep -q '^[-,0-9]\+$' then ip=0.0.0.0/0 port=$param tcp_udp_needed='yes' else ip=$param port='' fi ;; esac get_negation $ip ip=$param ip_neg_opt="$neg_opt" get_negation $port port=$param port_neg_opt="$neg_opt" translate_ip_net $ip case $res in dhcp) set_error "You can not use an interface ip provided by dhcp in a packet filter rule ($ip='$res')" return ;; esac normalize_network $res ip=$res [ "$port" -a ! "$opt" ] && port="`echo $port | sed -e 's/-/:/'`" } # get_negation param # - removes '!' from param and sets neg_opt accordingly get_negation () { case $1 in !*) param=`echo $1 | sed -e 's/^!//g'` neg_opt='! ' ;; *) param=$1 neg_opt='' ;; esac } # # default matches # prot_p=yes state_p=yes length_p=yes limit_p=yes mark_p=yes if_p=yes mac_p=yes # do_something handle "something:" statments # # do_prot param opt # - handles "prot:" statements do_prot () { set -- `mangle_params $1` proto=$1 opt=$2 case $proto in icmp) assert_empty "do_prot $*" "$3" ;; *) assert_empty "do_prot $*" "$2" ;; esac get_negation $proto case $param in any) ;; icmp) prot_opt="-p $neg_opt icmp" case x$opt in x) ;; *) get_negation $opt prot_opt="$prot_opt --icmp-type $neg_opt $param" ;; esac ;; *) prot_opt="-p $neg_opt $param" ;; esac } do_mark () { get_negation $1 match_opt="$match_opt -m mark $neg_opt --mark $param" } do_state () { get_negation $1 match_opt="$match_opt -m state $neg_opt --state $param" } do_length () { param=`mangle_params $1` get_negation $param match_opt="$match_opt -m length $neg_opt --length $param" } do_limit () { get_limit $1 match_opt="$match_opt $limit" } do_mac () { get_negation $1 match_opt="$match_opt -m mac --mac-source $neg_opt $param" } do_if_opt () { translate_ip_dev $3 real_if if is_error; then real_if=lo log_error "setting interfaces to 'lo'" fi [ "$real_if" != "any" ] && if_opt="$if_opt $1 $2 $real_if" } do_if () { set -- `mangle_params $1` assert_empty "do_if $*" "$3" get_negation $1 if_in=$param if_in_negopt="$neg_opt" get_negation $2 if_out=$param if_out_negopt="$neg_opt" do_if_opt -i "$if_in_negopt" $if_in do_if_opt -o "$if_out_negopt" $if_out } do_default () { rule="$*" case "$rule" in *BIDIRECTIONAL*) rule=`echo $rule | sed -e 's#\(.*\)BIDIRECTIONAL\(.*\)#\1\2#'` bidirectional='yes' ;; esac case "$rule" in *DROP*|*REJECT*|*ACCEPT*|*SNAT*|*DNAT*|*NETMAP*|*MASQUERADE*|*REDIRECT*) case "$rule" in *NOLOG*) rule=`echo $rule | sed -e 's#\(.*\)NOLOG[^[:space:]]*\(.*\)#\1\2#'` log_opt='no' ;; *LOG*) log_prefix="`echo $rule | sed -e 's#.*LOG:\?\([^[:space:]]*\).*#\1#'`" log_opt='yes' rule=`echo $rule | sed -e 's#\(.*\)LOG[^[:space:]]*\(.*\)#\1\2#'` ;; esac ;; esac set -- $rule # rewrite arguments case "$#" in 3) src=$1 dst=$2 action=$3 ;; 2) case $1 in *:* | dynamic*) dst=$1 action=$2 ;; *.*.*.* | any | IP_* | ip_* | @* ) src=$1 action=$2 ;; *) dst=$1 action=$2 ;; esac ;; 1) action=$1 ;; *) set_error "*** Error in rule $rule ***" ;; esac mangle_ip_params $src if is_error then set_error return 1 fi src="$ip_neg_opt$ip" case "x$port" in x) ;; *,*) src_port_opt="-m multiport --source-ports $port_neg_opt $port" ;; *) src_port_opt="--source-port $port_neg_opt $port" ;; esac mangle_ip_params $dst if is_error then set_error return 1 fi dst="$ip_neg_opt$ip" dport=$port case "x$port" in x) ;; *,*) dst_port_opt="-m multiport --destination-ports $port_neg_opt $port" ;; *) dst_port_opt="--destination-port $port_neg_opt $port" ;; esac action_opt= case $action in NONE) ;; DROP) case x$log_prefix in x) action_opt="$drop";; esac ;; REJECT) case x$log_prefix in x) action_opt="$reject";; esac ;; MARK:*) val=`echo $action | sed -e 's/MARK://'` action_opt="MARK --set-mark $val" ;; MASQUERADE:*) mangle_ip_params `echo $action | sed -e 's/MASQUERADE://'` keep_slash action_opt="MASQUERADE" [ "$port" ] && action_opt="$action_opt --to-ports $port" ;; SNAT:*) mangle_ip_params `echo $action | sed -e 's/SNAT://'` keep_slash action_opt="SNAT --to-source ${ip}" [ "$port" ] && action_opt="$action_opt:$port" ;; NETMAP:*) mangle_ip_params `echo $action | sed -e 's/NETMAP://'` keep_slash action_opt="NETMAP --to ${ip}" ;; DNAT:*) mangle_ip_params `echo $action | sed -e 's/DNAT://'` keep_slash action_opt="DNAT --to-destination ${ip}" [ "$port" ] && action_opt="$action_opt:$port" ;; REDIRECT:*) mangle_ip_params `echo $action | sed -e 's/REDIRECT://'` keep_slash : ${port:=$dport} action_opt="REDIRECT --to-ports $port" ;; LOG:*) log_prefix="`echo $action | sed -e 's/LOG://'`" action_opt=LOG ;; *) action_opt="$action" ;; esac } execute_log_statement () { case x$log_prefix in x) $IPTABLES $1 -m comment --comment "$2" -j LOG $log_level ;; *) $IPTABLES $1 -m comment --comment "$2" -j LOG --log-prefix "$log_prefix " $log_level ;; esac } # prot:(tcp|udp|gre|[0-9]{1,3}) if:in:out state:RELATED,ESTABLISHED,NEW,INVALID src[:port-port] dest[:port-port] accept|reject|drop really_execute_iptables () { do_action=$2 do_log='' case $do_action in *drp-log | *rej-log) if [ "$log_opt" = 'no' ] then do_action=`echo $do_action | sed -e 's#^\(.*\)-log#\1#'` fi ;; *drp | *rej) if [ "$log_opt" = 'yes' ] then do_action=${do_action}-log fi ;; *) do_log="$log_opt" ;; esac case "$do_log" in yes) case $op in I) $IPTABLES $1 -m comment --comment "$comment" -j $do_action execute_log_statement "$1" "$comment" ;; *) execute_log_statement "$1" "$comment" $IPTABLES $1 -m comment --comment "$comment" -j $do_action ;; esac ;; *) case $do_action in LOG) execute_log_statement "$1" "$comment" ;; *) $IPTABLES $1 -m comment --comment "$comment" -j $do_action ;; esac esac } exec_iptables () { table=$1 chain=$2 case $op in I) opts="-t $table -$op $chain $position" ;; *) opts="-t $table -$op $chain" ;; esac opts="$opts $if_opt $match_opt -s $src $src_port_opt -d $dst $dst_port_opt" if [ -z "$fw_rule_error" ] then if [ -z "$tcp_udp_needed" -o "$prot_opt" ] then really_execute_iptables "$prot_opt $opts" "$action_opt" else really_execute_iptables "-p tcp $opts" "$action_opt" really_execute_iptables "-p udp $opts" "$action_opt" fi fi } init_options () { table=$1 chain=$2 op=$3 if_opt='' match_opt='' prot_opt='' src='any' src_port_opt='' dst='any' dst_port_opt='' action_opt='' tcp_udp_needed='' bidirectional='' log_opt='' log_prefix='' log_target='LOG' fw_rule_error='' if [ -f $fwh_state.$chain ] then . $fwh_state.$chain else drop=DROP reject=REJECT fi } parse_rule () { parse_rule_param="$1" while true do set -- $parse_rule_param eval `echo $1 | sed -e 's/^[[:space:]]*/#/;s/^#\([a-z][[:alnum:]]\+\):\([^[:space:]]*\).*/present=\$\1_p;ext=\1;val=\2/;s/^#.*/ext=;/;s/.*ext=any.*/ext=/'` case x$ext in x|xdynamic) do_default $* break ;; *) shift parse_rule_param="$*" case $present in yes) eval do_$ext $val ;; *) log_error "invalid match $ext:" ;; esac ;; esac done } exec_rule () { $pre_exec if [ "$bidirectional" = 'yes' ] then exec_iptables $table $chain swp=$src src=$dst dst=$swp if_opt=`echo $if_opt | sed -e 's#-i #-x #' -e 's#-o #-i #' -e 's#-x #-o #'` fi exec_iptables $table $chain } read_tmpl () { { if [ -e $tmpl_dir/$tmpl_name ] then cat $tmpl_dir/$tmpl_name else grep -i "^$tmpl_name[[:space:]]" $tmpl_dir/templates fi } | sed -e "s/^$tmpl_name //;s/#.*//;/^[[:space:]]*$/d" } # do_rule table chain operation rule position method comment do_rule () { case x$FWRULES_DO_DEBUG in x) set +x ;; esac orig_table=$1 orig_chain=$2 orig_op=$3 orig_rule="$4" position=$5 method="$6" comment="$7" : ${method:=exec_rule} : ${comment:="$orig_rule"} init_options $orig_table $orig_chain $orig_op reset_rules_added case "$orig_rule" in *tmpl:*) eval set -- `echo "$orig_rule" | sed -e "s#\(.*\)[[:space:]:]*tmpl:\([^[:space:]]*\)\(.*\)#'\1' '\2' '\3'#"` rule_head="$1" rule_tail="$3" orig_rule="$rule_head $rule_tail" orig_tmpl_name=$2 orig_position=$position : ${orig_position:=1} tmpl_name=`echo $2 | tr A-Z a-z` tmpl_rules="`read_tmpl`" if [ -n "$tmpl_rules" ] then echo "$tmpl_rules" | while read tmpl_rule do parse_rule "$tmpl_rule DROP" parse_rule "$orig_rule" is_error || $method init_options $orig_table $orig_chain $orig_op get_rules_added rules_added position=`expr $orig_position + $rules_added` done else set_error "can't find packetfilter template '$orig_tmpl_name'" fi ;; *) parse_rule "$orig_rule" is_error || $method esac case x$tmp_debug in xyes) set -x; ;; esac case x$debug_active in xyes) set -x; ;; esac } # add_rule table chain rule comment add_rule () { do_rule $1 $2 A "$3" '' '' "$4" } # del_rule table chain rule comment del_rule () { do_rule $1 $2 D "$3" '' '' "$4" } # ins_rule table chain rule position comment ins_rule () { do_rule $1 $2 I "$3" "$4" '' "$5" } do_pre_exec () { chain=test-chain } do_check_rule () { dcr_res=0 pre_exec=do_pre_exec base_colecho=true base_log_file=/tmp/check_rule.$$ cons_boot=yes rm -f $base_log_file if ! iptables -t $1 -L test-chain > /dev/null 2>&1; then do_chain $1 test-chain N add-test-chain fi do_rule $1 $2 A "$3" '' "$4" "$3" if [ -f $base_log_file ]; then echo "Invalid rule '$3'" cat $base_log_file rm -f $base_log_file dcr_res=1 else do_rule $1 $2 D "$3" '' "$4" "$3" fi unset pre_exec unset base_colecho unset base_log_file return $dcr_res } check_rule () { : ${SCRIPT:=check_rule} do_check_rule filter foo "$1" '' } # setup_logging # setup_logging () { enabled=$1 chain=$2 prefix="$3" log_limit="$4" rej_limit="$5" udp_rej_limit="$6" log_level=$7 get_limit "$log_limit" log_limit="$limit" get_limit "$rej_limit" rej_limit="$limit" get_limit "$udp_rej_limit" udp_rej_limit="$limit" case $chain in FORWARD) drop="fw-drp" reject="fw-rej" ;; INPUT) drop="in-drp" reject="in-rej" ;; *) log_error "setup_loggig: unknown chain $chain" drop="un-drp" reject="un-rej" ;; esac cat <<-EOF > $fwh_state.$chain drop_name=$drop reject_name=$reject EOF case x$log_level in x) ;; *) log_level="--log-level $log_level" echo "log_level='$log_level'" >> $fwh_state.$chain ;; esac $IPTABLES -N ${drop} $IPTABLES -N ${drop}-log $IPTABLES -N ${reject} $IPTABLES -N ${reject}-log $IPTABLES -N ${reject}-fin $IPTABLES -A ${drop}-log $log_limit -j LOG --log-prefix "${prefix}-drop " $log_level $IPTABLES -A ${drop}-log -j DROP $IPTABLES -A ${drop} -j DROP $IPTABLES -A ${reject}-log $log_limit -j LOG --log-prefix "${prefix}-reject " $log_level $IPTABLES -A ${reject}-log -j ${reject} $IPTABLES -A ${reject} -p udp $udp_rej_limit -j ${reject}-fin $IPTABLES -A ${reject} -p ! udp $rej_limit -j ${reject}-fin $IPTABLES -A ${reject} -j DROP case $reject in *in*) $IPTABLES -A ${reject}-fin -p tcp -j REJECT --reject-with tcp-reset $IPTABLES -A ${reject}-fin -p udp -j REJECT --reject-with port-unreach $IPTABLES -A ${reject}-fin -p ! icmp -j REJECT --reject-with proto-unreach $IPTABLES -A ${reject}-fin -j DROP ;; *) $IPTABLES -A ${reject}-fin -p ! icmp -j REJECT --reject-with admin-prohib $IPTABLES -A ${reject}-fin -j DROP ;; esac case $enabled in yes) drop="${drop}-log" reject="${reject}-log" ;; *) drop=DROP ;; esac cat <<-EOF >> $fwh_state.$chain drop=$drop reject=$reject EOF } # get log chain names and default drop/reject actions get_defaults () { . $fwh_state.$1 } # close_chain close_chain () { . $fwh_state.$1 case "$2" in DROP) $IPTABLES -A $1 -j $drop ;; REJECT) $IPTABLES -A $1 -j $reject ;; ACCEPT) $IPTABLES -P $1 ACCEPT ;; esac } do_chain () { local table=$1 local chain=$2 local op=$3 local name=$4 if ! $IPTABLES -t $table -$op $chain then log_error "${name}_chain $chain failed..." fi } chain_present () { local table=$2 : ${table:=filter} iptables -t $table -L $1 > /dev/null 2>&1 } nat_chain_present () { chain_present $1 nat } add_chain () { do_chain filter $1 N add } add_nat_chain () { do_chain nat $1 N add } flush_chain () { do_chain filter $1 F flush } flush_nat_chain () { do_chain nat $1 F flush } del_chain () { do_chain filter $1 X del } del_nat_chain () { do_chain nat $1 X del } dmz_green_dev() { mode=$1 dev=$2 if ! iptables -t nat -L POSTROUTING | grep "mark.*SNAT"; then ins_rule nat POSTROUTING "mark:55 if:any:${dmz_orange}_DEV SNAT:${dmz_orange}_IPADDR" fi case $mode in add | ins | del) ${mode}_rule filter dmz-fwd "if:$dev:${dmz_orange}_DEV ACCEPT" ${mode}_rule mangle PREROUTING "if:$dev:any MARK:55" ;; *) log_error "dmz_green_dev: unknown mode '$mode'" ;; esac } set_count () { echo 1 > /var/run/$1_def_end /sbin/iptables -nL $1 --line-numbers | \ while read num tail do case $num in [0-9]*) echo `expr $num + 1` > /var/run/$1_def_end ;; esac done } get_count () { res=`cat /var/run/$1_def_end` }