#!/bin/sh
#
#     The Shoreline Firewall (Shorewall) Packet Filtering Firewall - V3.4
#
#     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)
#
#     tcstart from tc4shorewall       Version 0.5
#     (c) 2005 Arne Bernin <arne@ucbering.de>
#     Modified by Tom Eastep for integration into the Shorewall distribution
#     published under GPL Version 2#
#
#	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
#
#	If an error occurs while starting or restarting the firewall, the
#	firewall is automatically stopped.
#
#	Commands are:
#
#	   shorewall restart			  Restarts the firewall
#	   shorewall stop			  Stops the firewall
#	   shorewall reset			  Resets iptables packet and
#						  byte counts
#	   shorewall clear			  Remove all Shorewall chains
#						  and rules/policies.
#          shorewall add <iface>[:<host>] zone    Adds a host or subnet to a zone
#          shorewall delete <iface>[:<host>] zone Deletes a host or subnet from a zone
#
# Mutual exclusion -- These functions are jackets for the mutual exclusion
#		      routines in $FUNCTIONS. They invoke
#		      the corresponding function in that file if the user did
#		      not specify "nolock" on the runline.
#
my_mutex_on() {
    [ -n "$NOLOCK" ] || { mutex_on; HAVE_MUTEX=Yes; }
}

my_mutex_off() {
    [ -n "$HAVE_MUTEX" ] && { mutex_off; HAVE_MUTEX=; }
}

#
# Fatal error -- stops the firewall after issuing the error message
#
fatal_error() # $* = Error Message
{
    echo "   ERROR: $@" >&2
    stop_firewall
    exit 2
}

#
# Fatal error during startup -- generate an error message and abend without
#				altering the state of the firewall
#
startup_error() # $* = Error Message
{
    echo "   ERROR: $@" >&2
    my_mutex_off
    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE
    kill $$
    exit 2
}

#
# Send a message to STDOUT and the System Log
#
report () { # $* = message
    progress_message3 "$@"
    logger -p kern.info "$@"
}

#
# Run iptables and if an error occurs, stop the firewall and quit
#
run_iptables() {
    if [ -z "$KLUDGEFREE" ]; then
	#
	# Purge the temporary files that we use to prevent duplicate '-m' specifications
	#
	[ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange
    fi

   if ! $IPTABLES $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"$IPTABLES $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

#
# Version of 'run_iptables' that inserts white space after "!" in the arg list
#
run_iptables2() {

    case "$@" in
	*!*)
	    run_iptables $(fix_bang $@)
	    ;;
	*)
	    run_iptables $@
	    ;;
    esac

}

#
# Quietly run iptables
#
qt_iptables() {
    if [ -z "$KLUDGEFREE" ]; then
	#
	# Purge the temporary files that we use to prevent duplicate '-m' specifications
	#
	[ -n "$PHYSDEV_MATCH" ] && [ -f $TMP_DIR/physdev ] && rm -f $TMP_DIR/physdev
	[ -n "$IPRANGE_MATCH" ] && [ -f $TMP_DIR/iprange ] && rm -f $TMP_DIR/iprange
    fi

    qt $IPTABLES $@
}

#
# Run ip and if an error occurs, stop the firewall and quit
#
run_ip() {
    if ! ip $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"ip $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

#
# Run tc and if an error occurs, stop the firewall and quit
#
run_tc() {
    if ! tc $@ ; then
	if [ -z "$STOPPING" ]; then
	    error_message "ERROR: Command \"tc $@\" Failed"
	    stop_firewall
	    exit 2
	fi
    fi
}

#
# Delete a chain if it exists
#
deletechain() # $1 = name of chain
{
    qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1
}

#
# Find broadcast addresses -- if we are compiling a script and 'detect' is specified for an interface
#                             the function returns nothing for that interface
#
find_broadcasts() {
    for interface in $ALL_INTERFACES; do
	eval bcast=\$$(chain_base $interface)_broadcast
	if [ "x$bcast" = "xdetect" ]; then
	    ip -f inet addr show $interface 2> /dev/null | grep 'inet.*brd' | sed 's/inet.*brd //; s/scope.*//;' | sort -u
	elif [ "x${bcast}" != "x-" ]; then
	    echo $(separate_list $bcast)
	fi
    done
}

#
# For each entry in the CRITICALHOSTS global list, add INPUT and OUTPUT rules to
# enable traffic to/from those hosts.
#
enable_critical_hosts()
{
    for host in $CRITICALHOSTS; do
	interface=${host%:*}
	networks=${host#*:}
	$IPTABLES -A INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	$IPTABLES -A OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
    done
}

#
# For each entry in the CRITICALHOSTS global list, delete the INPUT and OUTPUT rules that
# enable traffic to/from those hosts.
#
disable_critical_hosts()
{
    for host in $CRITICALHOSTS; do
	interface=${host%:*}
	networks=${host#*:}
	$IPTABLES -D INPUT  -i $interface $(source_ip_range $networks) -j ACCEPT
	$IPTABLES -D OUTPUT -o $interface $(dest_ip_range $networks) -j ACCEPT
    done
}

#
# Stop the Firewall
#
stop_firewall() {
    #
    # Turn off trace unless we were tracing "stop" or "clear"
    #

    [ -n "$RESTOREBASE" ] && rm -f $RESTOREBASE

    case $COMMAND in
	stop|clear)
	    ;;
	*)
	    set +x

	    [ -n "${RESTOREFILE:=restore}" ]

	    RESTOREPATH=${VARDIR}/$RESTOREFILE

	    if [ -x $RESTOREPATH ]; then

		if [ -x ${RESTOREPATH}-ipsets ]; then
		    progress_message2 Restoring Ipsets...
		    #
		    # We must purge iptables to be sure that there are no
		    # references to ipsets
		    #
		    for table in mangle nat filter; do
			iptables -t $table -F
			iptables -t $table -X
		    done

		    ${RESTOREPATH}-ipsets
		fi

		echo Restoring Shorewall...

		if $RESTOREPATH restore; then
		    echo "Shorewall restored from $RESTOREPATH"
		    set_state "Started"
		else
		    set_state "Unknown"
		fi

		my_mutex_off
		kill $$
		exit 2
	    fi
	    ;;
    esac

    set_state "Stopping"

    STOPPING="Yes"

    TERMINATOR=

    deletechain shorewall

    run_user_exit stop

    if [ -n "$MANGLE_ENABLED" ]; then
	run_iptables -t mangle -F
	run_iptables -t mangle -X
	for chain in PREROUTING INPUT FORWARD POSTROUTING; do
	    qt $IPTABLES -t mangle -P $chain ACCEPT
	done
    fi

    if [ -n "$RAW_TABLE" ]; then
	run_iptables -t raw -F
	run_iptables -t raw -X
	for chain in PREROUTING OUTPUT; do
	    qt $IPTABLES -t raw -P $chain ACCEPT
	done
    fi

    if [ -n "$NAT_ENABLED" ]; then
	delete_nat
	for chain in PREROUTING POSTROUTING OUTPUT; do
	    qt $IPTABLES -t nat -P $chain ACCEPT
	done
    fi

    delete_proxy_arp
    [ -n "$CLEAR_TC" ] && delete_tc1

    [ -n "$DISABLE_IPV6" ] && disable_ipv6

    undo_routing
    restore_default_route

    process_criticalhosts

    if [ -n "$CRITICALHOSTS" ]; then
	if [ -z "$ADMINISABSENTMINDED" ]; then
	    for chain in INPUT OUTPUT; do
		setpolicy $chain ACCEPT
	    done

	    setpolicy FORWARD DROP

	    deleteallchains

	    enable_critical_hosts

	    for chain in INPUT OUTPUT; do
		setpolicy $chain DROP
	    done
	else
	    for chain in INPUT OUTPUT; do
		setpolicy $chain ACCEPT
	    done

	    setpolicy FORWARD DROP

	    deleteallchains

	    enable_critical_hosts

	    setpolicy INPUT DROP

	    for chain in INPUT FORWARD; do
		setcontinue $chain
	    done
	fi
    elif [ -z "$ADMINISABSENTMINDED" ]; then
	for chain in INPUT OUTPUT FORWARD; do
	    setpolicy $chain DROP
	done

	deleteallchains
    else
	for chain in INPUT FORWARD; do
	    setpolicy $chain DROP
	done

	setpolicy OUTPUT ACCEPT

	deleteallchains

	for chain in INPUT FORWARD; do
	    setcontinue $chain
	done
    fi

    process_routestopped -A

    $IPTABLES -A INPUT  -i lo -j ACCEPT
    [ -z "$ADMINISABSENTMINDED" ] && \
	$IPTABLES -A OUTPUT -o lo -j ACCEPT

    for interface in $(find_interfaces_by_option dhcp); do
	$IPTABLES -A INPUT  -p udp -i $interface --dport 67:68 -j ACCEPT
	[ -z "$ADMINISABSENTMINDED" ] && \
	    $IPTABLES -A OUTPUT -p udp -o $interface --dport 67:68 -j ACCEPT
	#
	# This might be a bridge
	#
	$IPTABLES -A FORWARD -p udp -i $interface -o $interface --dport 67:68 -j ACCEPT
    done

    case "$IP_FORWARDING" in
    [Oo][Nn])
	echo 1 > /proc/sys/net/ipv4/ip_forward
	progress_message2 "IP Forwarding Enabled"
	;;
    [Oo][Ff][Ff])
	echo 0 > /proc/sys/net/ipv4/ip_forward
	progress_message2 "IP Forwarding Disabled!"
	;;
    esac

    run_user_exit stopped

    set_state "Stopped"

    logger -p kern.info "Shorewall Stopped"

    rm -rf $TMP_DIR

    case $COMMAND in
    stop|clear)
	;;
    *)
	#
	# The firewall is being stopped when we were trying to do something
	# else. Remove the lock file and Kill the shell in case we're in a
	# subshell
	#
	my_mutex_off
	kill $$
	;;
    esac
}

#
# Remove all rules and remove all user-defined chains
#
clear_firewall() {
    stop_firewall

    setpolicy INPUT ACCEPT
    setpolicy FORWARD ACCEPT
    setpolicy OUTPUT ACCEPT

    run_iptables -F

    echo 1 > /proc/sys/net/ipv4/ip_forward

    if [ -n "$DISABLE_IPV6" ] && qt mywhich ip6tables; then
	ip6tables -P INPUT   ACCEPT 2> /dev/null
	ip6tables -P OUTPUT  ACCEPT 2> /dev/null
	ip6tables -P FORWARD ACCEPT 2> /dev/null
    fi

    run_user_exit clear

    set_state "Cleared"

    logger -p kern.info "Shorewall Cleared"
}

#
# Delete existing Proxy ARP
#
delete_proxy_arp() {
    if [ -f ${VARDIR}/proxyarp ]; then
	while read address interface external haveroute; do
	    case $COMMAND in
		stop|clear)
		    qt arp -i $external -d $address pub
		    [ -z "${haveroute}${NOROUTES}" ] && qt ip route del $address dev $interface
		    ;;
		*)
		    if [ -n "$STOPPING" ]; then
			qt arp -i $external -d $address pub
			qt arp -i $external -d $address pub
			[ -z "${haveroute}${NOROUTES}" ] && qt ip route del $address dev $interface
		    else
			qt arp -i $external -d $address pub
			if [ -z "$haveroute" ];then
			    [ -n "$NOROUTE" ] || qt ip route del $address dev $interface
			fi
		    fi
		    ;;
	    esac
	done < ${VARDIR}/proxyarp

	rm -f ${VARDIR}/proxyarp
    fi

    [ -d ${VARDIR} ] && touch ${VARDIR}/proxyarp

    case $COMMAND in
	stop|clear)
	    for f in /proc/sys/net/ipv4/conf/*; do
		[ -f $f/proxy_arp ] && echo 0 > $f/proxy_arp
	    done
	    ;;
	*)
	    if [ -n "$STOPPING" ]; then
		for f in /proc/sys/net/ipv4/conf/*; do
		    [ -f $f/proxy_arp ] && echo 0 > $f/proxy_arp
		done
	    else
		for f in /proc/sys/net/ipv4/conf/*; do
		    [ -f $f/proxy_arp ] && echo 0 > $f/proxy_arp
		done
	    fi
	    ;;
    esac
}

#
# Delete existing Static NAT
#
delete_nat() {
    run_iptables -t nat -F
    run_iptables -t nat -X

    if [ -f ${VARDIR}/nat ]; then
	while read external interface; do
	    qt ip addr del $external dev $interface
	done < ${VARDIR}/nat

	rm -f ${VARDIR}/nat
    fi

    [ -d ${VARDIR} ] && touch ${VARDIR}/nat
}

#
# Check for disabled startup
#
check_disabled_startup() {
    if [ -z "$STARTUP_ENABLED" ]; then
	echo "   Shorewall Startup is disabled -- to enable startup"
	echo "   after you have completed Shorewall configuration,"
	echo "   change the setting of STARTUP_ENABLED to Yes in"
        echo "   ${CONFDIR}/shorewall.conf"

	[ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	my_mutex_off
	exit 2
    fi
}

#
# Give Usage Information
#
usage() {
    echo "Usage: $0 [debug] {start|stop|reset|restart|clear}"
    exit 1
}

#
# E X E C U T I O N    B E G I N S   H E R E
#
#
# Start trace if first arg is "debug"
#
[ $# -gt 1 ] && [ "$1" = "debug" ] && { set -x ; shift ; }

NOLOCK=

[ $# -gt 1 ] && [ "$1" = "nolock" ] && { NOLOCK=Yes; shift ; }

trap "my_mutex_off; exit 2" 1 2 3 4 5 6 9

SHAREDIR=/usr/share/shorewall
VARDIR=/var/lib/shorewall
CONFDIR=/etc/shorewall

for library in lib.base lib.config; do
    FUNCTIONS=${SHAREDIR}/${library}

    if [ -f $FUNCTIONS ]; then
	[ $VERBOSE -ge 2 ] && echo "Loading $FUNCTIONS..."
	. $FUNCTIONS
    else
	fatal_error "Installation error: $FUNCTIONS does not exist!"
    fi
done

PROGRAM=firewall

COMMAND="$1"

case "$COMMAND" in
    stop)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	#
	# Don't want to do a 'stop' when startup is disabled
	#
	check_disabled_startup
	progress_message3 "Stopping Shorewall..."
	stop_firewall
	[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
	progress_message3 "done."
	my_mutex_off
	;;

    reset)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	$IPTABLES -Z
	$IPTABLES -t nat -Z
	$IPTABLES -t mangle -Z
	report "Shorewall Counters Reset"
	date > ${VARDIR}/restarted
	my_mutex_off
	;;

    clear)
	[ $# -ne 1 ] && usage
	do_initialize
	my_mutex_on
	progress_message3 "Clearing Shorewall..."
	clear_firewall
	[ -n "$SUBSYSLOCK" ] && rm -f $SUBSYSLOCK
	progress_message3 "done."
	my_mutex_off
	;;

     add)
	[ $# -lt 3 ] && usage
	do_initialize
	lib_load dynamiczones "The add command"
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	shift
	add_to_zone $@
	my_mutex_off
	;;

    delete)
	[ $# -lt 3 ] && usage
	lib_load dynamiczones "The delete command"
	do_initialize
	my_mutex_on
	if ! shorewall_is_started ; then
	    echo "Shorewall Not Started"
	    [ -n "$TMP_DIR" ] && rm -rf $TMP_DIR
	    my_mutex_off
	    exit 2;
	fi
	shift
	delete_from_zone $@
	my_mutex_off
	;;

   call)
	#
	# Undocumented way to call functions in ${SHAREDIR}/firewall directly
	#
	shift
	do_initialize
	EMPTY=
	$@
	;;

    *)
	usage
	;;

esac
