#!/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 maclist option
# is specified in an entry in the interfaces file.
#

#
# Set up MAC Verification
#
setup_mac_lists() # $1 = Phase Number
{
    local interface
    local mac
    local addresses
    local address
    local chain
    local chain1
    local macpart
    local blob
    local hosts
    local ipsec
    local policy=

    create_mac_chain()
    {
	case $MACLIST_TABLE in
	    filter)
		createchain $1 no
		;;
	    *)
		createmanglechain $1
		;;
	esac
    }

    have_mac_chain()
    {
	local result

	case $MACLIST_TABLE in
	    filter)
		havechain $1 && result=0 || result=1
		;;
	    *)
		havemanglechain $1 && result=0 || result=1
		;;
	esac

	return $result
    }
    #
    # Generate the list of interfaces having MAC verification
    #
    maclist_interfaces=

    for hosts in $maclist_hosts; do
	hosts=${hosts#*^}
	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

    progress_message "$DOING MAC Verification on $maclist_interfaces -- Phase $1..."
    #
    # Create chains.
    #
    if [ $1 -eq 1 ]; then
	for interface in $maclist_interfaces; do
	    chain=$(mac_chain $interface)
	    create_mac_chain $chain
	    #
	    # If we're using the mangle table and the interface is DHCP-enabled then we need to accept DHCP broadcasts from 0.0.0.0
	    #
	    if [ $MACLIST_TABLE = mangle ] && interface_has_option $interface dhcp; then
		run_iptables -t mangle -A $chain -s 0.0.0.0 -d 255.255.255.255 -p udp --dport 67:68 -j RETURN
	    fi

	    if [ -n "$MACLIST_TTL" ]; then
		chain1=$(macrecent_target $interface)
		create_mac_chain $chain1
		run_iptables -A $chain  -t $MACLIST_TABLE -m recent --rcheck --seconds $MACLIST_TTL --name $chain -j RETURN
		run_iptables -A $chain  -t $MACLIST_TABLE                                                         -j $chain1
		run_iptables -A $chain  -t $MACLIST_TABLE -m recent --update                        --name $chain -j RETURN
		run_iptables -A $chain  -t $MACLIST_TABLE -m recent --set                           --name $chain
	    fi
	done
        #
        # Process the maclist file producing the verification rules
        #
	while read disposition interface mac addresses; do

	    level=

	    case $disposition in
		ACCEPT:*)
		    level=${disposition#*:}
		    disposition=ACCEPT
		    target=RETURN
		    ;;
		ACCEPT)
		    target=RETURN
		    ;;
		REJECT:*)
		    [ $MACLIST_TABLE = mangle ] && fatal_error "DISPOSITION = REJECT is incompatible with MACLIST_TABLE=mangle"
		    target=reject
		    disposition=REJECT
		    ;;
		REJECT)
		    [ $MACLIST_TABLE = mangle ] && fatal_error "DISPOSITION = REJECT is incompatible with MACLIST_TABLE=mangle"
		    target=reject
		    ;;
		DROP:*)
		    level=${disposition#*:}
		    disposition=DROP
		    target=DROP
		    ;;
		DROP)
		    target=DROP
		    ;;
		*)
		    case "$interface" in
			*:*:*|~*-*-*)
			    #
			    # Pre-3.2 record format
			    #
			    addresses="$mac"
			    mac="$interface"
			    interface="$disposition"
			    disposition=ACCEPT
			    target=RETURN
			    ;;
			*)
			    fatal_error "Invalid DISPOSITION ($disposition) in rule \"$disposition $interface $mac $addresses\""
			    ;;
		    esac
		    ;;
	    esac

	    physdev_part=

	    if [ -n "$BRIDGING" ]; then
		case $interface in
		    *:*)
			physdev_part="-m physdev --physdev-in ${interface#*:}"
			interface=${interface%:*}
		    ;;
		esac
	    fi

	    [ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface)

	    if ! have_mac_chain $chain ; then
		fatal_error "No hosts on $interface have the maclist option specified"
	    fi

	    if [ x${mac:=-} = x- ]; then
		if [ -z "$addresses" ]; then
		    fatal_error "You must specify a MAC address or an IP address"
		else
		    macpart=
		fi
	    else
		macpart=$(mac_match $mac)
	    fi
	    
	    if [ -z "$addresses" ]; then
		[ -n "$level" ] && \
		    log_rule_limit $level $chain $(mac_chain $interface) $disposition "$LOGLIMIT" "" -A -t $MACLIST_TABLE $macpart $physdev_part
		    run_iptables -A $chain -t $MACLIST_TABLE $macpart $physdev_part -j $target
	    else
		for address in $(separate_list $addresses) ; do
		    [ -n "$level" ] && \
			log_rule_limit $level $chain $(mac_chain $interface) $disposition "$LOGLIMIT" "" -A -t $MACLIST_TABLE $macpart -s $address $physdev_part
			run_iptables2 -A $chain -t $MACLIST_TABLE $macpart -s $address $physdev_part -j $target
		done
	    fi
	done < $TMP_DIR/maclist
        #
        # Generate jumps from the input and forward chains
        #
        for hosts in $maclist_hosts; do
	    ipsec=${hosts%^*}
	    [ -n "$POLICY_MATCH" ] && policy="-m policy --pol $ipsec --dir in" || policy=
	    hosts=${hosts#*^}
	    interface=${hosts%%:*}
	    hosts=${hosts#*:}
	    case $MACLIST_TABLE in
		filter)
		    for chain in $(first_chains $interface) ; do
			run_iptables -A $chain $(match_source_hosts $hosts) -m state --state NEW \
			    $policy -j $(mac_chain $interface)
		    done
		    ;;
		*)
		    run_iptables -t mangle -A PREROUTING -i $interface $(match_source_hosts $hosts) -m state --state NEW \
			$policy -j $(mac_chain $interface)
		    ;;
	    esac
	done
    else
        #
        # Must take care of our own broadcasts and multicasts then terminate the verification
        # chains
        #
	for interface in $maclist_interfaces; do

	    [ -n "$MACLIST_TTL" ] && chain=$(macrecent_target $interface) || chain=$(mac_chain $interface)

		if [ -n "$MACLIST_LOG_LEVEL" -o $MACLIST_DISPOSITION != ACCEPT ]; then
		    indent >&3 << __EOF__

if interface_is_usable $interface; then
    ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet //; s/brd //; s/scope.*//;' | while read address broadcast; do
        address=\${address%/*}
        if [ -n "\$broadcast" ]; then
            run_iptables -t $MACLIST_TABLE -A $chain -s \$address -d \$broadcast -j RETURN
        fi

        run_iptables -t $MACLIST_TABLE -A $chain -s \$address -d 255.255.255.255 -j RETURN
        run_iptables -t $MACLIST_TABLE -A $chain -s \$address -d 224.0.0.0/4     -j RETURN
    done
else
    fatal_error "Interface $interface must be up before Shorewall can start"
fi

__EOF__
		fi

		CHAIN=$chain

		append_file maclog

		if [ -n "$MACLIST_LOG_LEVEL" ]; then
		    log_rule_limit $MACLIST_LOG_LEVEL $chain $(mac_chain $interface) $MACLIST_DISPOSITION "$LOGLIMIT" "" -A -t $MACLIST_TABLE
		fi

		if [ $MACLIST_DISPOSITION != ACCEPT ]; then
		    run_iptables -A $chain -t $MACLIST_TABLE -j $MACLIST_TARGET
		fi
	done
    fi
}

