#!/bin/sh
#
# Shorewall 3.4 -- /usr/share/shorewall/lib.dynamiczones
#
#     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/firewall when processing
# the 'add' and 'delete' commands.
#

#
# Add a host or networks to a zone
#
add_to_zone() # $1...${n-1} = <interface>[:<hosts>] $n = zone
{
    local interface host zone z h z1 z2 chain
    local dhcp_interfaces blacklist_interfaces maclist_interfaces
    local tcpflags_interfaces newhostlist=
    local rulenum source_chain dest_hosts iface hosts hostlist=

    nat_chain_exists() # $1 = chain name
    {
	qt $IPTABLES -t nat -L $1 -n
    }

    do_iptables() # $@ = command
    {
	[ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange

	if ! $IPTABLES $@ ; then
	    error_message "ERROR: Can't add $newhost to zone $zone"
	fi
    }

    DOING=Processing
    DONE=Processed
    #
    # Load $zones
    #
    determine_zones
    #
    # Validate Interfaces File
    #
    validate_interfaces_file
    #
    # Validate Hosts File
    #
    validate_hosts_file
    #
    # Validate IPSec File
    #
    f=$(find_file $IPSECFILE)

    [ -f $f ] && setup_ipsec $f
    #
    # Normalize host list
    #
    while [ $# -gt 1 ]; do
	interface=${1%%:*}
	host=${1#*:}
        #
        # Be sure that the interface was dynamic at last [re]start
        #
	if ! chain_exists $(input_chain $interface) ; then
	    startup_error "Unknown interface $interface"
	fi

	if ! chain_exists $(dynamic_in $interface) ; then
	    startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf"
	fi

	if [ -z "$host" ]; then
	    hostlist="$hostlist $interface:0.0.0.0/0"
	else
	    for h in $(separate_list $host); do
		hostlist="$hostlist $interface:$h"
	    done
	fi

	shift
    done
    #
    # Validate Zone
    #
    zone=$1

    validate_zone $zone || startup_error "Unknown zone: $zone"

    [ "$zone" = $FW ] && startup_error "Can't add $1 to firewall zone"

    #
    # Be sure that Shorewall has been restarted using a DZ-aware version of the code
    #
    [ -f ${VARDIR}/chains ] || startup_error "${VARDIR}/chains -- file not found"
    [ -f ${VARDIR}/zones ]  || startup_error "${VARDIR}/zones -- file not found"
    #
    # Check for duplicates and create a new zone state file
    #
    > ${VARDIR}/zones_$$

    while read z type hosts; do
	if [ "$z" = "$zone" ]; then
	    case "$hosts" in
		*exclude*)
		    rm -f ${VARDIR}/zones_$$
		    startup_error "Modifying a zone that has an exclude list is not supported"
		    ;;
		*)
		    for h in $hostlist; do
			list_search $h $hosts
			if [ "$?" -gt 0 ]; then
			    newhostlist="$newhostlist $h"
			else
			    error_message "$h already in zone $zone"
			fi
		    done

		    [ -z "$hosts" ] && hosts=$newhostlist || hosts="$hosts $newhostlist"
		    ;;
	    esac
	fi

	eval ${z}_hosts=\"$hosts\"

	echo "$z $type $hosts" >> ${VARDIR}/zones_$$
    done < ${VARDIR}/zones

    mv -f ${VARDIR}/zones_$$ ${VARDIR}/zones

    TERMINATOR=fatal_error
    #
    # Create a new Zone state file
    #
    for newhost in $newhostlist; do
        #
        # Isolate interface and host parts
        #
	interface=${newhost%%:*}
	host=${newhost#*:}
        #
        # If the zone passed in the command has a dnat chain then insert a rule in
        # the nat table PREROUTING chain to jump to that chain when the source
        # matches the new host(s)#
        #
	chain=${zone}_dnat

	if nat_chain_exists $chain; then
	    do_iptables -t nat -A $(dynamic_in $interface) $(source_ip_range $host) $(match_ipsec_in $zone $newhost) -j $chain
	fi
        #
        # Insert new rules into the filter table for the passed interface
        #
	while read z1 z2 chain; do
	    [ "$z1" = "$z2" ] && op="-I" || op="-A"
	    if [ "$z1" = "$zone" ]; then
		if [ "$z2" = "$FW" ]; then
		    do_iptables $op $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j $chain
		else
		    source_chain=$(dynamic_fwd $interface)
		    if is_ipsec_host $z1 $newhost ; then
			do_iptables $op $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd
		    else
			eval dest_hosts=\"\$${z2}_hosts\"

			for h in $dest_hosts; do
			    iface=${h%%:*}
			    hosts=${h#*:}

			    if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				do_iptables $op $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain
			    fi
			done
		    fi
		fi
	    elif [ "$z2" = "$zone" ]; then
		if [ "$z1" = "$FW" ]; then
		    #
		    # Add a rule to the dynamic out chain for the interface
		    #
		    do_iptables $op $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
		else
		    eval source_hosts=\"\$${z1}_hosts\"

		    for h in $source_hosts; do
			iface=${h%%:*}
			hosts=${h#*:}

			if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
			    if is_ipsec_host $z1 $h; then
				do_iptables $op ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
			    else
				do_iptables $op $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $newhost) -j $chain
			    fi
			fi
		    done
		fi
	    fi
	done < ${VARDIR}/chains

	progress_message "$newhost added to zone $zone"

    done

    rm -rf $TMP_DIR
}

#
# Delete a host or networks from a zone
#
delete_from_zone() # $1 = <interface>[:<hosts>] $2 = zone
{
    local interface host zone z h z1 z2 chain delhost
    local dhcp_interfaces blacklist_interfaces maclist_interfaces tcpflags_interfaces
    local rulenum source_chain dest_hosts iface hosts hostlist=

    DOING=Processing
    DONE=Processed
    #
    # Load $zones
    #
    determine_zones
    #
    # Validate Interfaces File
    #
    validate_interfaces_file
    #
    # Validate Hosts File
    #
    validate_hosts_file
    #
    # Validate IPSec File
    #
    f=$(find_file ipsec)

    [ -f $f ] && setup_ipsec $f

    #
    # Normalize host list
    #
    while [ $# -gt 1 ]; do
	interface=${1%%:*}
	host=${1#*:}
        #
        # Be sure that the interface was dynamic at last [re]start
        #
	if ! chain_exists $(input_chain $interface) ; then
	    startup_error "Unknown interface $interface"
	fi

	if ! chain_exists $(dynamic_in $interface) ; then
	    startup_error "At last Shorewall [re]start, DYNAMIC_ZONES=No in shorewall.conf"
	fi

	if [ -z "$host" ]; then
	    hostlist="$hostlist $interface:0.0.0.0/0"
	else
	    for h in $(separate_list $host); do
		hostlist="$hostlist $interface:$h"
	    done
	fi

	shift
    done
    #
    # Validate Zone
    #
    zone=$1

    validate_zone $zone || startup_error "Unknown zone: $zone"

    [ "$zone" = $FW ] && startup_error "Can't delete from the firewall zone"

    #
    # Be sure that Shorewall has been restarted using a DZ-aware version of the code
    #
    [ -f ${VARDIR}/chains ] || startup_error "${VARDIR}/chains -- file not found"
    [ -f ${VARDIR}/zones ]  || startup_error "${VARDIR}/zones -- file not found"
    #
    # Delete the passed hosts from the zone state file
    #
    > ${VARDIR}/zones_$$

    while read z hosts; do
	if [ "$z" = "$zone" ]; then
	    temp=$hosts
	    hosts=

	    for host in $hostlist; do
		found=
		for h in $temp; do
		    if [ "$h" = "$host" ]; then
			found=Yes
			break
		    fi
		done

		[ -n "$found" ] || error_message "WARNING: $host does not appear to be in zone $zone"
	    done

	    for h in $temp; do
		found=
		for host in $hostlist; do
		    if [ "$h" = "$host" ]; then
			found=Yes
			break
		    fi
		done

		[ -n "$found" ] || hosts="$hosts $h"
	    done
	fi

	eval ${z}_hosts=\"$hosts\"

	echo "$z $hosts" >> ${VARDIR}/zones_$$
    done < ${VARDIR}/zones

    mv -f ${VARDIR}/zones_$$ ${VARDIR}/zones

    TERMINATOR=fatal_error

    for delhost in $hostlist; do
	interface=${delhost%%:*}
	host=${delhost#*:}
        #
        # Delete any nat table entries for the host(s)
        #
	qt_iptables -t nat -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $zone $delhost) -j ${zone}_dnat
        #
        # Delete rules rules the input chains for the passed interface
        #
	while read z1 z2 chain; do
	    if [ "$z1" = "$zone" ]; then
		if [ "$z2" = "$FW" ]; then
		    qt_iptables -D $(dynamic_in $interface) $(match_source_hosts $host) $(match_ipsec_in $z1 $delhost) -j $chain
		else
		    source_chain=$(dynamic_fwd $interface)
		    if is_ipsec_host $z1 $delhost ; then
			qt_iptables -D $source_chain $(match_source_hosts $host) $(match_ipsec_in $z1 $newhost) -j ${z1}_frwd
		    else
			eval dest_hosts=\"\$${z2}_hosts\"

			[ "$z2" = "$zone" ] && dest_hosts="$dest_hosts $hostlist"

			for h in $dest_hosts; do
			    iface=${h%%:*}
			    hosts=${h#*:}

			    if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				qt_iptables -D $source_chain $(match_source_hosts $host) -o $iface $(match_dest_hosts $hosts) $(match_ipsec_out $z2 $h) -j $chain
			    fi
			done
		    fi
		fi
	    elif [ "$z2" = "$zone" ]; then
		if [ "$z1" = "$FW" ]; then
		    qt_iptables -D $(dynamic_out $interface) $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain
		else
		    eval source_hosts=\"\$${z1}_hosts\"

		    for h in $source_hosts; do
			iface=${h%%:*}
			hosts=${h#*:}

			if [ "$iface" != "$interface" -o "$hosts" != "$host" ]; then
				if is_ipsec_host $z1 $h; then
				    qt_iptables -D ${z1}_dyn -o $interface $(match_dest_hosts $host) $(match_ipsec_out $z2 $delhost) -j $chain
				else
				    qt_iptables -D $(dynamic_fwd $iface) $(match_source_hosts $hosts) -o $interface $(match_dest_hosts $host)  $(match_ipsec_out $z2 $delhost) -j $chain
				fi
			fi
		    done
		fi
	    fi
	done < ${VARDIR}/chains

	progress_message "$delhost removed from zone $zone"

    done

    rm -rf $TMP_DIR
}
