#!/bin/sh
#
# Shorewall 3.4 -- /usr/share/shorewall/lib.tcrules
#
#     This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
#
#     (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - 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
#
# This library is loaded by /usr/share/shorewall/compiler when the tcrules file is
# non-empty. It is also loaded by the compiled firewall script under the same
# condition when the script is processing the 'refresh' command.
#

#
# Process a TC Rule - $MARKING_CHAIN is assumed to contain the name of the
#                     default marking chain
#
# The caller has established values for the following variables:
#
# mark     - MARK column
# sources  - SOURCE column
# dests    - DEST column
# proto    - PROTO column
# ports    - PORT(S) column
# sports   - CLIENT PORT(S) column
# user     - USER column
# testval  - TEST column
# length   - LENGTH column
# tos      - TOS column
#
process_tc_rule()
{
    local did_connmark= multiport= classid=

    chain=$MARKING_CHAIN  target="MARK --set-mark" marktest=

    handle_designator() {
	chain=$1
	mark="${mark%:*}"
    }

    do_ipp2p()
    {
	[ -n "$IPP2P_MATCH" ] || fatal_error "Your kernel and/or iptables does not have IPP2P match support. TC Rule: \"$rule\""
	[ "x$port" = "x-" ] && port="ipp2p"

	case $proto in
	    *:*)
		proto=${proto#*:}
		;;
	    *)
		proto=tcp
		;;
	esac

	r="${r}-p $proto -m ipp2p --${port} "
    }

    verify_small_mark()
    {
	verify_mark $1
	[ $(($1)) -lt 256 ] || fatal_error "Mark Value ($1) too larg, rule \"$rule\""
    }

    do_connmark()
    {
	target="CONNMARK --set-mark"
	mark=$mark/0xff
	did_connmark=Yes
    }

    validate_mark()
    {
	case $1 in
	    */*)
		verify_mark ${1%/*}
		verify_mark ${1#*/}
		;;
	    *)
		verify_mark $1
		;;
	esac
    }

    add_a_tc_rule() {
	r=

	if [ "x$source" != "x-"	 ]; then
	    case $source in
		$FW:*)
		    r="$(source_ip_range ${source#*:}) "
		    ;;
		*:*)
		    interface=${source%:*}
		    verify_interface $interface || fatal_error "Unknown interface $interface in rule \"$rule\""
		    r="$(match_source_dev $interface) $(source_ip_range ${source#*:}) "
		    ;;
		*.*.*|+*|!+*)
		    r="$(source_ip_range $source) "
		    ;;
		~*|!~*)
		    r="$(mac_match $source) "
		    ;;
		$FW)
		    ;;
		*)
		    verify_interface $source || fatal_error "Unknown interface $source in rule \"$rule\""
		    r="$(match_source_dev $source) "
		    ;;
	    esac
	fi

	if [ "x${user:--}" != "x-" ]; then

	    [ "$chain" != tcout ] && \
		fatal_error "Invalid use of a user/group: rule \"$rule\""

	    r="$r-m owner"

	    case "$user" in
		*+*)
		    r="$r --cmd-owner ${user#*+} "
		    user=${user%+*}
		    ;;
	    esac

	    case "$user" in
		*:*)
		    temp="${user%:*}"
		    [ -n "$temp" ] && r="$r --uid-owner $temp "
		    temp="${user#*:}"
		    [ -n "$temp" ] && r="$r --gid-owner $temp "
		    ;;
		*)
		    [ -n "$user" ] && r="$r --uid-owner $user "
		    ;;
	    esac
	fi

	[ -n "$marktest" ] && r="${r}-m ${marktest}--mark $testval "

	if [ "x$dest" != "x-"  ]; then
	    case $dest in
		*:*)
		    [ "$chain" = tcpre ] && fatal_error "Destination interface is not allowed in the PREROUTING chain - rule \"$rule\""
		    interface=${dest%:*}
		    verify_interface $interface || fatal_error "Unknown interface $interface in rule \"$rule\""
		    r="$(match_dest_dev $interface) $(dest_ip_range ${dest#*:}) "
		    ;;
		*.*.*|+*|!+*)
		    r="${r}$(dest_ip_range $dest) "
		    ;;
		*)
		    [ "$chain" = tcpre ] && fatal_error "Destination interface is not allowed in the PREROUTING chain - rule \"$rule\""
		    verify_interface $dest || fatal_error "Unknown interface $dest in rule \"$rule\""
		    r="${r}$(match_dest_dev $dest) "
		    ;;
	    esac
	fi

	if [ "x${length:=-}" != "x-" ]; then
	    [ -n "$LENGTH_MATCH" ] || fatal_error "Your kernel and/or iptables does not have length match support. Rule: \"$rule\""
	    r="${r}-m length --length ${length} "
	fi

	if [ "x${tos:=-}" != "x-" ]; then
	    r="${r}-m tos --tos ${tos} "
	fi

	case $proto in
	    ipp2p|IPP2P|ipp2p:*|IPP2P:*)
		do_ipp2p
		;;
	    icmp|ICMP|1)
		r="${r}-p icmp "
		[ "x$port"  = "x-" ] || r="${r}--icmp-type $port"
		;;
	    *)
		[ "x$proto" = "x-"  ] && proto=all
		[ "x$proto" = "x"   ] && proto=all
		[ "$proto"  = "all" ] || r="${r}-p $proto "
		[ "x$port"  = "x-"  ] || r="${r}$multiport $port "
		;;
	esac

	[ "x$sport" = "x-"  ] || r="${r}--sport $sport "

	if [ -n "${excludesources}${excludedests}" ]; then

	    [ $target = RETURN ] && \
		fatal_error "Exclusion is currently not supported with CONTINUE"

	    build_exclusion_chain chain1 mangle "$excludesources" "$excludedests"

	    run_iptables2 -t mangle -A $chain $r -j $chain1

	    run_iptables -t mangle -A $chain1 -j $target $mark
	else
	    run_iptables2 -t mangle -A $chain $r -j $target $mark
	fi

    }
    #
    # E x e c u t i o n  B e g i n s  H e r e
    #
    case $sources in
	$FW|$FW:*)
	    chain=tcout

	    if [ "x$mark" != "x${mark%:*}" ]; then
		case "${mark#*:}" in
		    t|T)
			handle_designator tcpost
			;;
		    ct|CT)
			handle_designator tcpost
			do_connmark
			;;
		    c|C)
			mark=${mark%:*}
			do_connmark
			;;
		    p|P|cp|CP|f|F|cf|CF)
			fatal_error "Invalid chain designator for source \$FW; rule \"$rule\""
			;;
		    *)
			chain=tcpost
			target="CLASSIFY --set-class"
			classid=Yes
			;;
		esac
	    fi
	    ;;
	*)
	    if [ "x$mark" != "x${mark%:*}" ]; then
		case "${mark#*:}" in
		    p|P)
			handle_designator tcpre
			;;
		    cp|CP)
			handle_designator tcpre
			do_connmark
			;;
		    f|F)
			handle_designator tcfor
			;;
		    cf|CF)
			handle_designator tcfor
			do_connmark
			;;
		    t|T)
			handle_designator tcpost
			;;
		    ct|CT)
			handle_designator tcpost
			do_connmark
			;;
		    c|C)
			mark=${mark%:*}
			do_connmark
			;;
		    *)
			chain=tcpost
			classid=Yes
			target="CLASSIFY --set-class"
			;;
		esac
	    fi
	    ;;
    esac

    mask=0xffff

    case $mark in
	SAVE)
	    [ -n "$did_connmark" ] && fatal_error "SAVE not valid with :C[FP]"
	    target="CONNMARK --save-mark --mask 0xFF"
	    mark=
	    ;;
	SAVE/*)
	    [ -n "$did_connmark" ] && fatal_error "SAVE not valid with :C[FP]"
	    target="CONNMARK --save-mark --mask"
	    mark=${mark#*/}
	    verify_small_mark $mark
	    ;;
	RESTORE)
	    [ -n "$did_connmark" ] && fatal_error "RESTORE not valid with :C[FP]"
	    target="CONNMARK --restore-mark --mask 0xFF"
	    mark=
	    ;;
	RESTORE/*)
	    [ -n "$did_connmark" ] && fatal_error "RESTORE not valid with :C[FP]"
	    target="CONNMARK --restore-mark --mask"
	    mark=${mark#*/}
	    verify_small_mark $mark
	    ;;
	CONTINUE)
	    [ -n "$did_connmark" ] && fatal_error "CONTINUE not valid with :C[FP]"
	    target=RETURN
	    mark=
	    ;;
	\|*)
	    [ -n "$classid" ] && fatal_error "Invalid class ID: $mark"
	    [ -n "$did_connmark" ] && fatal_error "Logical OR not valid with :C[FP]"
	    target="MARK --or-mark"
	    mark=${mark#|}
	    validate_mark $mark
	    if [ $((${mark%/*})) -lt 256 -a $((${mark%/*})) -ne 0 -a -n "$HIGH_ROUTE_MARKS" -a $chain = tcpre ]; then
		fatal_error "Marks < 256 may not be set in the PREROUTING chain when HIGH_ROUTE_MARKS=Yes"
	    fi
	    ;;
	\&*)
	    [ -n "$classid" ] && fatal_error "Invalid class ID: $mark"
	    [ -n "$did_connmark" ] && fatal_error "Logical AND not valid with :C[FP]"
	    target="MARK --and-mark"
	    mark=${mark#&}
	    validate_mark $mark
	    if [ $((${mark%/*})) -lt 256 -a $((${mark%/*})) -ne 0 -a -n "$HIGH_ROUTE_MARKS" -a $chain = tcpre ]; then
		fatal_error "Marks < 256 may not be set in the PREROUTING chain when HIGH_ROUTE_MARKS=Yes"
	    fi
	    ;;
	*)
	    if [ -z "$classid" ]; then
		validate_mark $mark
		if [ $((${mark%/*})) -gt 255 ]; then
		    case $chain in
			tcpre|tcout)
			    target="MARK --or-mark"
			    ;;
			*)
			    fatal_error "Invalid mark value ($mark) in rule \"$rule\""
			    ;;
		    esac
		elif [ $((${mark%/*})) -ne 0 -a -n "$HIGH_ROUTE_MARKS" -a $chain = tcpre ]; then
		    fatal_error "Marks < 256 may not be set in the PREROUTING chain when HIGH_ROUTE_MARKS=Yes"
		fi
	    fi
	    ;;
    esac

    case $testval in
	-)
	    ;;
	!*:C)
	    marktest="connmark ! "
	    testval=${testval%:*}
	    testval=${testval#!}
	    ;;
	*:C)
	    marktest="connmark "
	    testval=${testval%:*}
	    ;;
	!*)
	    marktest="mark ! "
	    testval=${testval#!}
	    ;;
	*)
	    [ -n "$testval" ] && marktest="mark "
	    ;;
    esac

    if [ -n "$marktest" ] ; then
	case $testval in
	    */*)
		verify_mark ${testval%/*}
		verify_mark ${testval#*/}
		;;
	    *)
		verify_mark $testval
		testval=$testval/$mask
		;;
	esac
    fi

    excludesources=

    case ${sources:=-} in
	*!*!*)
	    fatal_error "Invalid SOURCE in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $sources) -gt 1 ]; then
		excludesources=${sources#!}
		sources=-
	    fi
	    ;;
	*!*)
	    excludesources=${sources#*!}
	    sources=${sources%!*}
	    ;;
    esac

    excludedests=

    case ${dests:=-} in
	*!*!*)
	    fatal_error "Invalid DEST in rule \"$rule\""
	    ;;
	!*)
	    if [ $(list_count $dests) -gt 1 ]; then
		excludedests=${dests#*!}
		dests=-
	    fi
	    ;;
	*!*)
	    excludedests=${dests#*!}
	    dests=${dests%!*}
	    ;;
    esac
    #
    # This isn't as fancy as the code in the rules file processor. It only uses multiport match
    # if the extended variety is available and then only on the destination port(s). 
    #
    if [ -n "$XMULTIPORT" ]; then
	#
	# Extended Multiport match can handle port ranges 
	#
	multiport="-m multiport --dports"
	for source in $(separate_list $sources); do
	    for dest in $(separate_list $dests); do
		port=${ports:=-}
		for sport in $(separate_list ${sports:=-}); do
		    add_a_tc_rule
		done
	    done
	done
    else
	multiport=--dport
	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
    fi

    progress_message "   TC Rule \"$rule\" $DONE"
    save_progress_message_short "   TC Rule \\\"$rule\\\" Added"
}

#
# Process the tcrules file
#
process_tc_rules()
{
    cat >&3 << __EOF__

#
# Create Marking Rules from the tcrules file
#
setup_tc_rules()
{
__EOF__
    INDENT="    "
    
    while read mark sources dests proto ports sports user testval length tos; do
	if [ "x$mark" = xCOMMENT ]; then
	    if [ -n "$COMMENTS" ]; then
		    comment=$(echo $sources $dests $proto $ports $sports $user $testval $length $tos)
		    save_command COMMENT=\"$comment\"
	    else
		error_message "COMMENT ignored --  requires comment support in iptables/Netfilter"
	    fi
	else
	    rule=$(echo "$mark $sources $dests $proto $ports $sports $user $testval $length $tos")
	    process_tc_rule
	fi
    done < $TMP_DIR/tcrules

    INDENT=""
    save_command "}"
    save_command
}
