#----------------------------------------------------------------------------
# /etc/rc.d/fwrules-helper 
# helper functions for packet filter rules
#
# Creation:     2003-07-05 jw5 
# Last Update:  $Id: fwrules-helper 11044 2006-02-21 19:07:24Z hh $
#----------------------------------------------------------------------------

: ${I:=sbin_iptables}
: ${fwh_state:=/var/run/fwrules-helper.state}
: ${tmpl_dir:=/etc/fwrules.tmpl}
: ${ext_dir:=/etc/rc.d}

fw_tmp=/tmp/fwrules-helper.$$
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
}

[ "$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) ;;
	*) log_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;"
}

# 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
    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
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
    assert_empty "do_prot $*" "$3"
    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_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 ()
{
    if=$3
    case $if in 
	IP_NET_*_DEV | ip_net_*_dev)
	    translate_ip_net $if
	    if=$res
	    ;;
	circuit-*)
	    if [ -f /var/run/$if ]
 	    then
		if=`cat /var/run//$if`
	    else
		set_error "*** Error: unknown circuit $if, ignoring interface restriction ***"
		if=any
	    fi
	    ;;
	pppoe)
	    if [ -f /var/run/pppoe-device ]
 	    then
		if=`cat /var/run/pppoe-device`
	    else
		set_error "*** Error: unknown circuit pppoe, ignoring interface restriction ***"
		if=any
	    fi

	    ;;
    esac
    if [ "$if" != "any" ]
    then
	if_opt="$if_opt $1 $2 $if"
    fi
}

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
    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
    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="$action"
    case $action in
	DROP)
	    case x$log_prefix in
		x) action_opt="$drop";;
	    esac
	    ;;
	REJECT)
	    case x$log_prefix in
		x) action_opt="$reject";;
	    esac
	    ;;
	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 --log-prefix $log_prefix"
	    ;;
    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
	log=
	case "$do_log" in 
	    yes) 
		case x$log_prefix in
		    x) log="-j $log_target" ;;
		    *) log="-j $log_target --log-prefix $log_prefix" ;;
		esac
		case $op in 
		    I)
			$I $1 -m comment --comment "$comment" -j $do_action
			$I $1 -m comment --comment "$comment" $log
			;;
		    *)
			$I $1 -m comment --comment "$comment" $log
			$I $1 -m comment --comment "$comment" -j $do_action
			;;
		esac
		;;
	    *)
		$I $1 -m comment --comment "$comment" -j $do_action
		;;
	esac
}

exec_iptables ()
{
    table=$1
    chain=$2
    opts="-t $table -$op $chain $position $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 ()
{
    set $1
#    echo "parse_rules: $*"

    while true
    do
	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
		;;
	    *)
		case $present in
		    yes)
			eval do_$ext $val
			;;
		    *)
			log_error "invalid match $ext:"
			;;
		esac
		;;
	esac
	shift
    done    
}

exec_rule ()
{
    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
}

exec_portfw_rule ()
{
    case $pf_type in 
	dynamic)
#	    echo "dynamic dst='$dst'"
	    case $dst in
		dynamic)
		    dst=$pf_ip
		    ;;
		*)
		    continue
		    ;;
	    esac
	    ;;
	static)
#	    echo "static dst='$dst'"
	    case $dst in
		dynamic)
		    continue
		    ;;
	    esac
#	    echo "static pf_ip='$pf_ip'"
	    case "$pf_ip" in
		'')
		    ;;
		$dst)
		    ;;
		*)
		    continue
		    ;;
	    esac
    esac
    exec_rule

    case $action_opt in
        *DNAT*)
            set `echo $action_opt | sed -e 's/DNAT --to-destination//' -e 's/:/ /' -e 's/-/:/'`
            dst=" $1 "
            [ "$2" ] && dst_port_opt="--destination-port $2"
            action_opt=ACCEPT
            table=filter
            chain=PORTFWACCESS
            exec_rule
	;;
	*REDIRECT*) 
	    set $action_opt
	    case x$3 in
		x) ;;
		*) dst_port_opt="--destination-port $3" ;;
	    esac
            action_opt=ACCEPT
            table=filter
	    chain=PORTREDIRACCESS
	    exec_rule
	;;
    esac
}

# do_rule table chain operation rule position method
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

    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
	    tmpl_name=`echo $2 | tr A-Z a-z`

            if [ -e $tmpl_dir/$tmpl_name ]
            then
                tmpl_rules=`cat $tmpl_dir/$tmpl_name`
            else
                tmpl_rules=`grep -i "^$tmpl_name[[:space:]]" $tmpl_dir/templates | sed -e "s/^$tmpl_name //"`
            fi

            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
                done
            else
                log_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 ()
{
    do_rule $1 $2 A "$3" '' '' "$4"
}

del_rule ()
{
    do_rule $1 $2 D "$3" '' '' "$4"
}

ins_rule ()
{
    do_rule $1 $2 I "$3" "$4" '' "$5" 
}

# setup_logging <yes|no> <chain prefix> <log prefix> <log limits> 
#               <rej limits> <udp rej limits>
setup_logging ()
{
    enabled=$1
    chain=$2
    prefix="$3"
    log_limit="$4"
    rej_limit="$5"
    udp_rej_limit="$6"
    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
	    
    $I -N ${drop}
    $I -N ${drop}-log
    $I -N ${reject}
    $I -N ${reject}-log
    $I -N ${reject}-fin
    
    $I -A ${drop}-log            $log_limit     -j LOG --log-prefix "${prefix}-drop "
    $I -A ${drop}-log                           -j DROP
    $I -A ${drop}                               -j DROP

    $I -A ${reject}-log          $log_limit     -j LOG --log-prefix "${prefix}-reject "
    $I -A ${reject}-log                         -j ${reject}
    $I -A ${reject}     -p   udp $udp_rej_limit -j ${reject}-fin
    $I -A ${reject}     -p ! udp $rej_limit     -j ${reject}-fin
    $I -A ${reject}                             -j DROP

    case $reject in
    *in*)
	$I -A ${reject}-fin -p   tcp  -j REJECT --reject-with tcp-reset
	$I -A ${reject}-fin -p   udp  -j REJECT --reject-with port-unreach
	$I -A ${reject}-fin -p ! icmp -j REJECT --reject-with proto-unreach
	$I -A ${reject}-fin           -j DROP
	;;
    *)
	$I -A ${reject}-fin -p ! icmp -j REJECT --reject-with admin-prohib
	$I -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 <chain name> <chain policy>
close_chain ()
{
    . $fwh_state.$1

    case "$2" in
	DROP)
	    $I -A $1 -j $drop
	    ;;
	REJECT)
	    $I -A $1 -j $reject
	    ;;
	ACCEPT)
	    $I -P $1 ACCEPT
	;;
    esac
}

do_chain ()
{
    table=$1
    chain=$2
    op=$3
    name=$4
    if ! $I -t $table -$op $chain
    then
	log_error "${name}_chain $chain failed..."
    fi
}

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 
}

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`
}
