#
# Shorewall 5.2 -- /usr/share/shorewall/lib.cli-std
#
#     (c) 1999-2018 - Tom Eastep (teastep@shorewall.net)
#
#	Complete documentation is available at https://shorewall.org
#
#       This program is part of Shorewall.
#
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by the
#       Free Software Foundation, either version 2 of the license or, at your
#       option, any later version.
#
#	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, see <http://www.gnu.org/licenses/>.
#
# This library contains the command processing code common to /sbin/shorewall and /sbin/shorewall6.
#
####################################################################################################
# Set the configuration variables from the .conf file
#
#     $1 = Yes: read the params file
#     $2 = Yes: check for STARTUP_ENABLED
#     $3 = Yes: Check for LOGFILE
#
get_config() {
    local prog
    local lib

    ensure_config_path

    if [ "$1" = Yes ]; then
	if [ "$(id -u)" -eq 0 ]; then 
	    params=$(find_file params)
	else
	    params="$g_shorewalldir/params"
	fi

	if [ -f $params ]; then
	    . $params
	fi
    fi

    if [ -n "$g_shorewalldir" ]; then
	config="$g_shorewalldir/$PRODUCT.conf"
    else
	config=$(find_file ${PRODUCT}.conf)
    fi

    if [ -f $config ]; then
	if [ -r $config ]; then
	    . $config
	else
	    fatal_error "Cannot read $config! (Hint: Are you root?)"
	fi
    else
	fatal_error "$config does not exist!"
    fi

    ensure_config_path

    if [ -z "$g_export" -a "$(id -u)" = 0 ]; then
	#
	# This block is avoided for compile for export and when the user isn't root
	#
	if [ "$3" = Yes ]; then
	    setup_logread
	fi

	if [ $g_family -eq 4 ]; then
	    if [ -n "$IPTABLES" ]; then
		if [ ! -x "$IPTABLES" ]; then
		    fatal_error "The program specified in IPTABLES does not exist or is not executable"
		fi
	    else
		IPTABLES=$(mywhich iptables 2> /dev/null)
		if [ -z "$IPTABLES" ] ; then
		    fatal_error "Can't find iptables executable"
		fi
	    fi

	    g_tool=$IPTABLES
	else
	    if [ -n "$IP6TABLES" ]; then
		if [ ! -x "$IP6TABLES" ]; then
		    fatal_error "The program specified in IP6TABLES does not exist or is not executable"
		fi
	    else
		IP6TABLES=$(mywhich ip6tables 2> /dev/null)
		if [ -z "$IP6TABLES" ] ; then
		    fatal_error "Can't find ip6tables executable"
		fi
	    fi

	    g_tool=$IP6TABLES
	fi

	if [ -n "$IPSET" ]; then
	    case "$IPSET" in
		*/*)
		    if [ ! -x "$IPSET" ] ; then
			fatal_error "The program specified in IPSET ($IPSET) does not exist or is not executable"
		    fi
		    ;;
		ipset)
		    #
		    # Old config files had this as default
		    #
		    IPSET=''
		    ;;
		*)
		    prog="$(mywhich $IPSET 2> /dev/null)"
		    if [ -z "$prog" ] ; then
			fatal_error "Can't find $IPSET executable"
		    fi
		    IPSET=$prog
		    ;;
	    esac
	else
	    IPSET=''
	fi

	if [ -n "$TC" ]; then
	    case "$TC" in
		*/*)
		    if [ ! -x "$TC" ] ; then
			fatal_error "The program specified in TC ($TC) does not exist or is not executable"
		    fi
		    ;;
		*)
		    prog="$(mywhich $TC 2> /dev/null)"
		    if [ -z "$prog" ] ; then
			fatal_error "Can't find $TC executable"
		    fi
		    TC=$prog
		    ;;
	    esac
	else
	    TC='tc'
	fi
	#
	# Compile by non-root needs no restore file
	#
	[ -n "$RESTOREFILE" ] || RESTOREFILE=restore

	validate_restorefile RESTOREFILE

	if [ "$2" = Yes ]; then
	    case $STARTUP_ENABLED in
		No|no|NO)
		    not_configured_error "$g_product startup is disabled. To enable startup, set STARTUP_ENABLED=Yes in ${g_confdir}/${PRODUCT}.conf"
		    ;;
		Yes|yes|YES)
		    ;;
		*)
		    if [ -n "$STARTUP_ENABLED" ]; then
			not_configured_error "Invalid Value for STARTUP_ENABLED: $STARTUP_ENABLED"
		    fi
		    ;;
	    esac
	fi

	case ${SHOREWALL_COMPILER:=perl} in
	    perl|Perl)
		;;
	    shell|Shell)
		echo "   WARNING: SHOREWALL_COMPILER=shell ignored. Shorewall-shell support has been removed in this release" >&2
		;;
	    *)
		fatal_error "Invalid value ($SHOREWALL_COMPILER) for SHOREWALL_COMPILER"
		;;
	esac

	case ${TC_ENABLED:=Internal} in
	    No|NO|no)
	        TC_ENABLED=
		;;
	esac

	[ -z "$LOGFORMAT" ] && LOGFORMAT='Shorewall:%s.%s'

	[ -n "$LOGFORMAT" ] && LOGFORMAT="${LOGFORMAT%%%*}"

	if [ -n "$STARTUP_LOG" ]; then
	    if [ -n "$LOG_VERBOSITY" ]; then
		case $LOG_VERBOSITY in
		    -1)
			;;
		    0|1|2)
			;;
		    *)
		        fatal_error "Invalid LOG_VERBOSITY ($LOG_VERBOSITY)"
			;;
		esac
	    else
		LOG_VERBOSITY=2;
	    fi
	else
	    LOG_VERBOSITY=-1;
	fi

    else
	STARTUP_LOG=
	LOG_VERBOSITY=-1
    fi

    if [ -z "${g_export}${g_test}" ]; then
	if [ -n "$SHOREWALL_SHELL" ]; then
	    if [ ! -x "$SHOREWALL_SHELL" ]; then
		echo "   WARNING: The program specified in SHOREWALL_SHELL does not exist or is not executable; falling back to /bin/sh" >&2
		SHOREWALL_SHELL=/bin/sh
	    fi
	fi

	if [ -n "$IP" ]; then
	    case "$IP" in
		*/*)
		    if [ ! -x "$IP" ] ; then
			fatal_error "The program specified in IP ($IP) does not exist or is not executable"
		    fi
		    ;;
		*)
		    prog="$(mywhich $IP 2> /dev/null)"
		    if [ -z "$prog" ] ; then
			fatal_error "Can't find $IP executable"
		    fi
		    IP=$prog
		    ;;
	    esac
	else
	    IP='ip'
	fi
    else
	[ -n "$SHOREWALL_SHELL" ] || SHOREWALL_SHELL=/bin/sh
	[ -n "$IP" ]              || IP='ip'
    fi

    case $VERBOSITY in
	-1|0|1|2)
	    ;;
	*)
	    if [ -n "$VERBOSITY" ]; then
		fatal_error "Invalid VERBOSITY setting ($VERBOSITY)"
	    else
		VERBOSITY=2
	    fi
	    ;;
    esac

    [ -n "$g_use_verbosity" ] && VERBOSITY=$g_use_verbosity || VERBOSITY=$(($g_verbose_offset + $VERBOSITY))

    if [ $VERBOSITY -lt -1 ]; then
	VERBOSITY=-1
    elif [ $VERBOSITY -gt 2 ]; then
	VERBOSITY=2
    fi

    g_hostname=$(hostname 2> /dev/null)

    [ -n "$RSH_COMMAND" ] || RSH_COMMAND='ssh ${root}@${system} ${command}'
    [ -n "$RCP_COMMAND" ] || RCP_COMMAND='scp ${files} ${root}@${system}:${destination}'

    case $MANGLE_ENABLED in
	Yes|yes)
	    ;;
	No|no)
	    MANGLE_ENABLED=
	    ;;
	*)
	    if [ -n "$MANGLE_ENABLED" ]; then
		fatal_error "Invalid MANGLE_ENABLED setting ($MANGLE_ENABLED)"
	    fi
	    ;;
    esac

    case $AUTOMAKE in
	Yes|yes)
	    AUTOMAKE=1
	    ;;
	No|no)
	    AUTOMAKE=
	    ;;
	[1-9])
	    ;;
	[1-9][0-9])
	    ;;
	[Rr]ecursive)
	    AUTOMAKE=recursive
	    ;;
	*)
	    if [ -n "$AUTOMAKE" ]; then
		fatal_error "Invalid AUTOMAKE setting ($AUTOMAKE)"
	    fi
	    ;;
    esac

    if [ -n "$WORKAROUNDS" ]; then
	case $WORKAROUNDS in
	    [Yy]es)
	        ;;
	    [Nn]o)
		WORKAROUNDS=''
		;;
	    *)
		fatal_error "Invalid setting ($WORKAROUNDS) for WORKAROUNDS"
		;;
	esac
    fi

    g_loopback=$(find_loopback_interfaces)

    [ -n "$PAGER" ] || PAGER=$DEFAULT_PAGER

    if [ -z "$g_nopager" ]; then
	if [ -n "$PAGER" -a -t 1 ]; then
	    case $PAGER in
		/*)
		    g_pager="$PAGER"
		    [ -f "$g_pager" ] || fatal_error "PAGER $PAGER does not exist"
		    ;;
		*)
		    g_pager=$(mywhich $PAGER 2> /dev/null)
		    [ -n "$g_pager" ] || fatal_error "PAGER $PAGER not found"
		    ;;
	    esac

	    [ -x "$g_pager" ] || fatal_error "PAGER $g_pager is not executable"

	    g_pager="| $g_pager"
	fi
    fi

    if [ -n "$DYNAMIC_BLACKLIST" -a "$(id -u)" = 0 ]; then
	case $COMMAND in
	    blacklist|allow|drop|logdrop|reject)
		setup_dbl
		;;
	esac
    fi

    if [ -z "$PERL_HASH_SEED" ]; then
	PERL_HASH_SEED=0
    else
	case $PERL_HASH_SEED in
	    [0-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]|random)
	        ;;
	    *)
		fatal_error "Invalid setting ($PERL_HASH_SEED) for PERL_HASH_SEED"
		;;
	esac
    fi

    lib=$(find_file lib.cli-user)

    [ -f $lib ] && . $lib
}

#
# Ensure that the effective UID is 0 or that we are dealing with a private configuration
#
ensure_root() {
    if [ $(id -u) -ne 0 ]; then
	if [ -z "$g_shorewalldir" -o "$g_shorewalldir" = $CONFDIR/$PRODUCT ]; then
	    startup_error "Ordinary users may not $COMMAND the default $PRODUCT configuration"
	fi
    fi
}

#
# Determine if there are config files newer than the passed object
#
uptodate() {
    [ -x $1 ] || return 1

    local dir
    local busybox
    local find

    find=$(mywhich find)

    [ -n "${find}" ] || return 1
    [ -h "${find}" ] && busybox=Yes
    find="${find} -L"

    for dir in $g_shorewalldir $(split $CONFIG_PATH); do
	if [ -n "${busybox}" ]; then
	    #
	    # Busybox 'find' doesn't support -quit.
	    #
	    if [ $AUTOMAKE = recursive ]; then
		if [ -n "$(${find} ${dir} -newer $1 -print)" ]; then
		    return 1;
		fi
	    elif  [ -n "$(${find} ${dir} -maxdepth $AUTOMAKE -type f -newer $1 -print)" ]; then
		return 1;
	    fi
	elif [ "$AUTOMAKE" = recursive ]; then
	    if [ -n "$(${find} ${dir} -newer $1 -print -quit)" ]; then
		return 1;
	    fi
	elif [ -z "$AUTOMAKE" ]; then
	    if [ -n "$(${find} ${dir} -maxdepth 1 -type f -newer $1 -print -quit)" ]; then
		return 1;
	    fi
	elif [ -n "$(${find} ${dir} -maxdepth $AUTOMAKE -type f -newer $1 -print -quit)" ]; then
	    return 1;
	fi
    done

    return 0
}

#
# Run the postcompile user exit
#
run_postcompile() { # $1 is the compiled script
    local script

    script=$(find_file postcompile)

    if [ -f $script ]; then
	. $script $1
    else
	return 0
    fi
}

#
# Run the compiler
#
compiler() {
    local pc
    local shorewallrc
    local shorewallrc1
    local options

    pc=${LIBEXECDIR}/shorewall/compiler.pl

    ensure_root
    #
    # Let params and the compiler know the base configuration directory
    #
    if [ -n "$g_shorewalldir" ]; then
	SW_CONFDIR="$g_shorewalldir"
    else
	SW_CONFDIR="$g_confdir"
    fi

    export SW_CONFDIR
    #
    # We've now set g_shorewalldir so recalculate CONFIG_PATH
    #
    [ -n "$g_haveconfig" ] || ensure_config_path
    #
    # Get the config from $g_shorewalldir
    #
    get_config Yes

    case $COMMAND in
	*start|try|reload|restart|safe-*)
	    ;;
	*)
	    STARTUP_LOG=
	    LOG_VERBOSITY=-1
	    ;;
    esac

    debugflags="-w"
    [ -n "$g_debug" ]   && debugflags='-wd'
    [ -n "$g_profile" ] && debugflags='-wd:NYTProf'

    # Perl compiler only takes the output file as a argument

    [ "$1" = debug -o "$1" = trace ] && shift;
    [ "$1" = nolock ] && shift;
    shift

    shorewallrc=${g_basedir}/shorewallrc

    if [ -n "$g_export" ]; then
	shorewallrc1=$(find_file shorewallrc)
	[ -f "$shorewallrc1" ] || fatal_error "Compiling for export requires a shorewallrc file"
    fi

    if [ -n "$g_conditional" ] && uptodate "$g_file"; then
	echo "$g_file is up to date -- no compilation required"
	g_compiled="$g_file"
	return 0
    fi

    options="--verbose=$VERBOSITY --family=$g_family --config_path=$CONFIG_PATH --shorewallrc=${shorewallrc}"

    [ -n "$shorewallrc1" ]     && options="$options --shorewallrc1=${shorewallrc1}"
    [ -n "$STARTUP_LOG" ]      && options="$options --log=$STARTUP_LOG"
    [ -n "$LOG_VERBOSITY" ]    && options="$options --log_verbosity=$LOG_VERBOSITY";
    [ -n "$g_export" ]         && options="$options --export"
    [ -n "$g_shorewalldir" ]   && options="$options --directory=$g_shorewalldir"
    [ -n "$g_timestamp" ]      && options="$options --timestamp"
    [ -n "$g_test" ]           && options="$options --test"
    [ -n "$g_preview" ]        && options="$options --preview"
    [ -n "$g_trace" ]          && options="$options --debug"
    [ -n "$g_confess" ]        && options="$options --confess"
    [ -n "$g_update" ]         && options="$options --update"
    [ -n "$g_annotate" ]       && options="$options --annotate"

    if [ -n "$PERL" ]; then
	if [ ! -x "$PERL" ]; then
	    echo "   WARNING: The program specified in the PERL option does not exist or is not executable; falling back to /usr/bin/perl" >&2
	    PERL=/usr/bin/perl
	fi
    else
	PERL=/usr/bin/perl
    fi

    case "$g_doing" in
	Compiling|Checking)
	    progress_message3 "$g_doing using Shorewall $SHOREWALL_VERSION..."
	    ;;
	Updating)
	    progress_message3 "Updating $g_product configuration to $SHOREWALL_VERSION..."
	    ;;
	*)
	    [ -n "$g_doing" ] && progress_message3 "$g_doing using Shorewall $SHOREWALL_VERSION..."
	    ;;
    esac
    #
    # Only use the pager if 'trace' or -r was specified and -d was not
    #
    [ -z "$g_trace" -a -z "$g_preview" ] || [ -n "$g_debug" ] && g_pager=

    case $PERL_HASH_SEED in
	random)
	    unset PERL_HASH_SEED
	    unset PERL_PERTURB_KEYS
	    ;;
	*)
	    export PERL_HASH_SEED
	    PERL_PERTURB_KEYS=0
	    export PERL_PERTURB_KEYS
	    ;;
    esac

    if [ ${PERLLIBDIR} = ${LIBEXECDIR}/shorewall ]; then
	eval $PERL $debugflags $pc $options $@ $g_pager
    else
	eval PERL5LIB=${PERLLIBDIR} $PERL $debugflags $pc $options $@ $g_pager
    fi

    status=$?

    if [ $status -eq 0 -a $COMMAND != check -a $COMMAND != update ]; then
	g_compiled="$g_file"
	run_postcompile "$g_compiled"
	return
    fi

    return $status
}

#
# Start Command Executor
#
start_command() {
    local finished
    finished=0
    local rc
    rc=0

    if product_is_started; then
	error_message "Shorewall is already running"
	exit 0
    fi

    [ -n "$STARTUP_ENABLED" ] || not_configured_error "Startup is disabled"

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			d*)
			    g_debug=Yes
			    option=${option#d}
			    ;;
			f*)
			    g_fast=Yes
			    option=${option#f}
			    ;;
			p*)
			    [ -n "$(which conntrack)" ] || fatal_error "The '-p' option requires the conntrack utility which does not appear to be installed on this system"
			    g_purge=Yes
			    option=${option%p}
			    ;;
			c*)
			    AUTOMAKE=
			    option=${option#c}
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			C*)
			    g_counters=Yes
			    option=${option#C}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    ;;
	1)
	    [ -n "$g_shorewalldir" ] && fatal_error "A directory has already been specified: $1"
	    [ -n "$g_fast" ]         && fatal_error "Directory may not be specified with the -f option"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    AUTOMAKE=
	    ;;
	*)
	    too_many_arguments $2
	    ;;
    esac

    if [ -n "${g_fast}${AUTOMAKE}" ]; then
	if ! uptodate $g_firewall; then
	    g_fast=
	    AUTOMAKE=
	fi
    fi

    if [ -n "$AUTOMAKE" ]; then
	[ -n "$g_nolock" ] || mutex_on
	run_it $g_firewall start
	rc=$?
	[ -n "$g_nolock" ] || mutex_off
    else
	g_file="${VARDIR}/.start"
	if compiler compile "$g_file"; then
	    [ -n "$g_nolock" ] || mutex_on
	    run_it ${VARDIR}/.start start
	    rc=$?
	    [ -n "$g_nolock" ] || mutex_off
	else
	    rc=$?
	    mylogger kern.err "ERROR:$g_product start failed"
	fi
    fi

    exit $rc
}

#
# Compile Command Executor
#
compile_command() {
    local finished
    finished=0

    while [ $finished -eq 0 ]; do
	[ $# -eq 0 ] && break
	option=$1
	case $option in
	    -*)
		shift
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			e*)
			    g_export=Yes
			    g_shorewalldir='.'
			    option=${option#e}
			    ;;
			p*)
			    g_profile=Yes
			    option=${option#p}
			    ;;
			t*)
			    g_test=Yes
			    option=${option#t}
			    ;;
			d*)
			    g_debug=Yes;
			    option=${option#d}
			    ;;
			c*)
			    g_conditional=Yes;
			    option=${option#c}
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			-)
			    finished=1
			    option=
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		;;
	    *)
		finished=1
		;;
	esac
    done

    g_file=

    case $# in
	0)
	    [ -n "$g_export" ] && g_file=firewall || g_file=$g_firewall
	    ;;
	1)
	    g_file=$1
	    [ -d "$g_file" ] && fatal_error "$g_file is a directory"
	    ;;
	2)
	    [ -n "$g_shorewalldir" -a -z "$g_export" ] && fatal_error "A directory has already been specified: $1"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    g_file=$2
	    ;;
	*)
	    too_many_arguments $3
	    ;;
    esac

    [ "x$g_file" = x- ] && g_doing=''

    compiler compile "$g_file"
}

#
# Check Command Executor
#
check_command() {
    local finished
    finished=0

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			e*)
			    g_export=Yes
			    g_shorewalldir='.'
			    option=${option#e}
			    ;;
			p*)
			    g_profile=Yes
			    option=${option#p}
			    ;;
			t*)
			    g_test=Yes
			    option=${option#t}
			    ;;
			d*)
			    g_debug=Yes;
			    option=${option#d}
			    ;;
			r*)
			    g_preview=Yes
			    option=${option#r}
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    ;;
	1)
	    [ -n "$g_shorewalldir" -a -z "$g_export" ] && fatal_error "A directory has already been specified: $1"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    ;;
	*)
	    too_many_arguments $2
	    ;;
    esac

    g_doing="Checking"

    compiler check
}

#
# Update Command Executor
#
update_command() {
    local finished
    finished=0

    g_update=Yes

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			e*)
			    g_export=Yes
			    option=${option#e}
			    ;;
			p*)
			    g_profile=Yes
			    option=${option#p}
			    ;;
			t*)
			    g_test=Yes
			    option=${option#t}
			    ;;
			d*)
			    g_debug=Yes;
			    option=${option#d}
			    ;;
			r*)
			    g_preview=Yes
			    option=${option#r}
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			a*)
			    g_annotate=Yes
			    option=${option#a}
			    ;;
			A*)
			    option=${option#A}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    ;;
	1)
	    [ -n "$g_shorewalldir" ] && fatal_error "A directory has already been specified: $1"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    ;;
	*)
	    too_many_arguments $2
	    ;;
    esac

    g_doing="Updating"

    compiler check
}

#
# Reload/Restart Command Executor
#
restart_command() {
    local finished
    finished=0
    local rc
    rc=0
    local restorefile

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			d*)
			    g_debug=Yes
			    option=${option#d}
			    ;;
			f*)
			    g_fast=Yes
			    option=${option#f}
			    ;;
			c*)
			    AUTOMAKE=
			    option=${option#c}
			    ;;
			n*)
			    g_noroutes=Yes
			    option=${option#n}
			    ;;
			p*)
			    [ -n "$(which conntrack)" ] || fatal_error "The '-p' option requires the conntrack utility which does not appear to be installed on this system"
			    g_purge=Yes
			    option=${option%p}
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			i*)
			    option=${option#i}
			    ;;
			C*)
			    g_counters=Yes
			    option=${option#C}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    ;;
	1)
	    [ -n "$g_shorewalldir" ] && fatal_error "A directory has already been specified: $1"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    [ -n "$g_fast" ] && fatal_error "Directory may not be specified with the -f option"
	    AUTOMAKE=
	    ;;
	*)
	    too_many_arguments $2
	    ;;
    esac

    [ -n "$STARTUP_ENABLED" ] || not_configured_error "Startup is disabled"

    if [ -z "$g_fast" -a -n "$AUTOMAKE" ]; then
	uptodate $g_firewall && g_fast=Yes
    fi

    g_file="${VARDIR}/.${COMMAND}"

    if [ -z "$g_fast" ]; then
	if compiler compile "$g_file"; then
	    [ -n "$g_nolock" ] || mutex_on
	    run_it ${VARDIR}/.${COMMAND} ${COMMAND}
	    rc=$?
	    [ -n "$g_nolock" ] || mutex_off
	else
	    rc=$?
	    mylogger kern.err "ERROR:$g_product ${COMMAND} failed"
	fi
    else
	[ -x $g_firewall ] || fatal_error "No $g_firewall file found"
	[ -n "$g_nolock" ] || mutex_on
	run_it $g_firewall $COMMAND
	rc=$?
	[ -n "$g_nolock" ] || mutex_off
    fi

    return $rc
}

read_yesno_with_timeout() {
    local timeout
    timeout=${1:-60}

    case $timeout in
	*s)
	    ;;
	*m)
	    timeout=$((${timeout%m} * 60))
	    ;;
	*h)
	    timeout=$((${timeout%h} * 3600))
	    ;;
    esac

    read -t $timeout yn 2> /dev/null
    if [ $? -eq 2 ]
    then
	# read doesn't support timeout
	test -x /bin/bash || return 2 # bash is not installed so the feature is not available
	/bin/bash -c "read -t $timeout yn ; if [ \"\$yn\" == \"y\" ] ; then exit 0 ; else exit 1 ; fi" # invoke bash and use its version of read
	return $?
    else
	# read supports timeout
	case "$yn" in
	    y|Y)
		return 0
		;;
	    *)
		return 1
		;;
	esac
    fi
}

#
# Safe-start/safe-reload/safe-restart Command Executor
#
safe_commands() {
    local finished
    finished=0
    local command
    local timeout
    timeout=60

    # test is the shell supports timed read
    read -t 0 junk 2> /dev/null
    if [ $? -eq 2 -a ! -x /bin/bash ];then
	echo "Your shell does not support a feature required to execute this command".
	exit 2
    fi

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			n*)
			    g_noroutes=Yes
			    option=${option#n}
			    ;;
			t)
			    [ $# -eq 1 ] && fatal_error "The -t option requires a timeout value"
			    echo $2 | egrep -q '[[:digit:]]+[smh]' || fatal_error "The timeout value must be numeric, optionally followed by a suffix (s, m or h)"
			    timeout=$2
			    option=
			    shift;
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    ;;
	1)
	    [ -n "$g_shorewalldir" ] && fatal_error "A directory has already been specified: $1"

	    if [ ! -d $1 ]; then
		if [ -e $1 ]; then
		    fatal_error "$1 is not a directory"
		else
		    fatal_error "Directory $1 does not exist"
		fi
	    fi

	    g_shorewalldir=$(resolve_file $1)
	    ;;
	*)
	    too_many_arguments $2
	    ;;
    esac

    [ -n "$STARTUP_ENABLED" ] || not_configured_error "Startup is disabled"

    if product_is_started; then
	running=Yes
    else
	running=
    fi

    if [ "$COMMAND" = "safe-start" -a  -n "$running" ]; then
	# the command is safe-start but the firewall is already running
	error_message "Shorewall is already started"
	exit 0
    fi

    if [ "$COMMAND" = "safe-start" -o -z "$running" ]; then
	# the command is safe-start or shorewall[6] is not started yet
	command="start"
    else
	# the command is safe-reload or safe-restart and the firewall is already running
	command="${COMMAND#safe-}"
    fi

    g_file="${VARDIR}/.$command"

    if ! compiler compile "$g_file"; then
	status=$?
	exit $status
    fi

    case $command in
	start)
	    RESTOREFILE=NONE
	    progress_message3 "Starting..."
	    ;;
	reload)
	    RESTOREFILE=.safe
	    g_restorepath=${VARDIR}/.safe
	    save_config
	    progress_message3 "Reloading..."
	    ;;
	restart)
	    RESTOREFILE=.safe
	    g_restorepath=${VARDIR}/.safe
	    save_config
	    progress_message3 "Restarting..."
	    ;;
    esac

    [ -n "$g_nolock" ] || mutex_on

    if run_it ${VARDIR}/.$command $command; then

	printf "Do you want to accept the new firewall configuration? [y/n] "

	if read_yesno_with_timeout $timeout ; then
	    echo "New configuration has been accepted"
	else
	    if [ "$command" = "restart" -o "$command" = "reload" ]; then
		run_it ${VARDIR}/.safe -r restore
	    else
		run_it ${VARDIR}/.$command clear
	    fi

	    [ -n "$g_nolock" ] || mutex_off

	    echo "New configuration has been rejected and the old one restored"
	    exit 2
	fi

    fi

    [ -n "$g_nolock" ] || mutex_off
}

#
# 'try' Command Executor
#
try_command() {
    local finished
    finished=0
    local timeout
    timeout=

    handle_directory() {
	[ -n "$g_shorewalldir" ] && fatal_error "A directory has already been specified: $1"

	if [ ! -d $1 ]; then
	    if [ -e $1 ]; then
		fatal_error "$1 is not a directory"
	    else
		fatal_error "Directory $1 does not exist"
	    fi
	fi

	g_shorewalldir=$(resolve_file $1)
    }

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			n*)
			    g_noroutes=Yes
			    option=${option#n}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    missing_argument
	    ;;
	1)
	    handle_directory $1
	    ;;
	2)
	    handle_directory $1
	    echo $2 | egrep -q '[[:digit:]]+[smh]' || fatal_error "The timeout value must be numeric, optionally followed by a suffix (s, m or h)"
	    timeout=$2
	    ;;
	*)
	    too_many_arguments $3
	    ;;
    esac

    [ -n "$STARTUP_ENABLED" ] || not_configured_error "Startup is disabled"

    if product_is_started; then
	running=Yes
    else
	running=
    fi

    if [ -z "$running" ]; then
	# shorewall[6] is not started yet
	command="start"
    else
	# the firewall is already running
	command="reload"
    fi

    g_file="${VARDIR}/.$command"

    if ! compiler compile "$g_file"; then
	status=$?
	exit $status
    fi

    run_postcompile ${VARDIR}/.restart

    case $command in
	start)
	    RESTOREFILE=NONE
	    progress_message3 "Starting..."
	    ;;
	reload)
	    RESTOREFILE=.try
	    g_restorepath=${VARDIR}/.try
	    save_config
	    progress_message3 "Reloading..."
	    ;;
    esac

    [ -n "$g_nolock" ] || mutex_on

    if run_it ${VARDIR}/.$command $command && [ -n "$timeout" ]; then
	sleep $timeout

	if [ "$command" = "reload" ]; then
	    run_it ${VARDIR}/.try restore
	else
	    run_it ${VARDIR}/.$command clear
	fi
    fi

    [ -n "$g_nolock" ] || mutex_off

    return 0
}

rsh_command() {
    command="$*"

    eval $RSH_COMMAND
}

rcp_command() {
    files="$1"
    destination=$2

    eval $RCP_COMMAND
}

#
# Remote-{getcaps|getrc} command executer
#
remote_capture() # $* = original arguments less the command.
{
    local verbose
    verbose=$(make_verbose)
    local finished
    finished=0
    local system
    local getrc
    getrc=
    local getcaps
    getcaps=
    local remote_sw_dir_path
    remote_sw_dir_path=
    local root
    root=root
    local libexec
    libexec=${LIBEXECDIR}

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			R*)
			    getrc=Yes
			    option=${option#R}
			    ;;
			c*)
			    getcaps=Yes
			    option=${option#c}
			    ;;
			r)
			    [ $# -gt 1 ] || fatal_error "Missing Root User name"
			    root=$2
			    option=
			    shift
			    ;;
			D)
			    [ $# -gt 1 ] || fatal_error "Missing directory name"
			    g_shorewalldir=$2
			    option=
			    shift
			    ;;
			p)
			    [ $# -gt 1 ] || fatal_error "Missing directory name"
			    remote_sw_dir_path=$2
			    option=
			    shift
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    [ -n "$g_shorewalldir" ] || g_shorewalldir='.'
	    ;;
	1)
	    g_shorewalldir="."
	    system=$1
	    ;;
	2)
	    g_shorewalldir=$1
	    system=$2
	    ;;
	*)
	    too_many_arguments $3
	    ;;
    esac

    g_export=Yes

    ensure_config_path

    get_config Yes

    g_haveconfig=Yes

    if [ -z "$system" ]; then
        system=$FIREWALL
        [ -n "$system" ] || fatal_error "No system name given and the FIREWALL option is not set"
    fi

    case $COMMAND in
        remote-getrc)
            getrc=Yes
            ;;
        remote-getcaps)
            getcaps=Yes
            ;;
    esac

    [ -n "$getcaps" ] && getrc=Yes

    if [ -n "$getrc" -o ! -s $g_shorewalldir/shorewallrc ]; then
        progress_message2 "Getting shorewallrc file on system $system..."

        if [ -n "$remote_sw_dir_path" ]; then
            if ! rsh_command "/sbin/shorewall-lite show rc $remote_sw_dir_path" > $g_shorewalldir/shorewallrc; then
                fatal_error "Capturing RC file on system $system failed"
            fi
        elif ! rsh_command "/sbin/shorewall-lite show rc" > $g_shorewalldir/shorewallrc; then
            fatal_error "Capturing RC file on system $system failed"
        fi
    fi

    remote_sw_dir_path=

    if [ -n "$getcaps" -o ! -s $g_shorewalldir/capabilities ]; then
        if [ -f $g_shorewalldir/shorewallrc -a -s $g_shorewalldir/shorewallrc ]; then
            . $g_shorewalldir/shorewallrc
            libexec="$LIBEXECDIR"

            [ -n "$DONT_LOAD" ] && DONT_LOAD="$(echo $DONT_LOAD | tr ',' ' ')"

            progress_message2 "Getting Capabilities on system $system..."

            if [ $g_family -eq 4 ]; then
                if ! rsh_command "MODULESDIR=$MODULESDIR IPTABLES=$IPTABLES DONT_LOAD=\"$DONT_LOAD\" $libexec/shorewall-lite/shorecap" > $g_shorewalldir/capabilities; then
                    fatal_error "Capturing capabilities on system $system failed"
                fi
            elif ! rsh_command "MODULESDIR=$MODULESDIR IP6TABLES=$IP6TABLES DONT_LOAD=\"$DONT_LOAD\" $libexec/shorewall6-lite/shorecap" > $g_shorewalldir/capabilities; then
                fatal_error "Capturing capabilities on system $system failed"
            fi
        else
            fatal_error "$g_shorewalldir/shorewallrc is not present."
        fi
    fi
}

#
# Remote-{start|reload|restart} command executor
#
remote_commands() # $* = original arguments less the command.
{
    local verbose
    verbose=$(make_verbose)
    local file
    file=
    local capabilities
    capabilities=
    local finished
    finished=0
    local saveit
    saveit=
    local result
    local system
    local getcaps
    getcaps=
    local root
    root=root
    local libexec
    libexec=${LIBEXECDIR}
    local confdir
    confdir=${CONFDIR}
    local sbindir
    sbindir=${SBINDIR}
    local sharedir
    sharedir=${SHAREDIR}
    local litedir
    local exitstatus
    local program

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			s*)
			    saveit=Yes
			    option=${option#s}
			    ;;
			c*)
			    getcaps=Yes
			    option=${option#c}
			    ;;
			r)
			    [ $# -gt 1 ] || fatal_error "Missing Root User name"
			    root=$2
			    option=
			    shift
			    ;;
			D)
			    [ $# -gt 1 ] || fatal_error "Missing directory name"
			    g_shorewalldir=$2
			    option=
			    shift
			    ;;
			T*)
			    g_confess=Yes
			    option=${option#T}
			    ;;
			D*)
			    g_trace=Yes
			    option=${option#D}
			    ;;
			*)
			    option_error $option
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	0)
	    [ -n "$g_shorewalldir" ] || g_shorewalldir='.'
	    ;;
	1)
	    g_shorewalldir="."
	    system=$1
	    ;;
	2)
	    g_shorewalldir=$1
	    system=$2
	    ;;
	*)
	    too_many_arguments $3
	    ;;
    esac

    if [ -f $g_shorewalldir/shorewallrc ]; then
	. $g_shorewalldir/shorewallrc
	sbindir="$SBINDIR"
	confdir="$CONFDIR"
	libexec="$LIBEXECDIR"
	litedir="${VARDIR}-lite"
	. $sharedir/shorewall/shorewallrc
    else
	error_message "   WARNING: $g_shorewalldir/shorewallrc does not exist; using settings from $g_basedir/shorewalrc" >&2
	sbindir="$SBINDIR"
	confdir="$CONFDIR"
	libexec="$LIBEXECDIR"
	litedir="${VARDIR}-lite"
    fi

    g_export=Yes

    ensure_config_path

    get_config Yes

    g_haveconfig=Yes

    if [ -z "$system" ]; then
        system=$FIREWALL
        [ -n "$system" ] || fatal_error "No system name given and the FIREWALL option is not set"
    fi

    if [ -z "$getcaps" ]; then
	capabilities=$(find_file capabilities)
	[ ! -f $capabilities -o ! -s $capabilities ] && getcaps=Yes
    fi

    if [ -n "$getcaps" ]; then
	[ -n "$DONT_LOAD" ] && DONT_LOAD="$(echo $DONT_LOAD | tr ',' ' ')"

	progress_message2 "Getting Capabilities on system $system..."
	if [ $g_family -eq 4 ]; then
	    if ! rsh_command "MODULESDIR=$MODULESDIR IPTABLES=$IPTABLES DONT_LOAD=\"$DONT_LOAD\" $libexec/shorewall-lite/shorecap" > $g_shorewalldir/capabilities; then
	        fatal_error "Capturing capabilities on system $system failed"
	    fi
	elif ! rsh_command "MODULESDIR=$MODULESDIR IP6TABLES=$IP6TABLES DONT_LOAD=\"$DONT_LOAD\" $libexec/shorewall6-lite/shorecap" > $g_shorewalldir/capabilities; then
	    fatal_error "Capturing capabilities on system $system failed"
	fi
    fi

    file=$(resolve_file $g_shorewalldir/firewall)

    program=$sbindir/${PRODUCT}-lite
    #
    # Handle nonstandard remote VARDIR
    #
    progress_message3 "Getting VARDIR on system $system..."
    temp=$(rsh_command $program show config 2> /dev/null | grep ^LITEDIR | sed 's/LITEDIR is //')
 
    [ -n "$temp" ] && litedir="$temp"

    g_file="$g_shorewalldir/firewall"

    exitstatus=0

    if compiler compiler "$g_file"; then
	progress_message3 "Copying $file and ${file}.conf to ${system}:${litedir}..."
	if rcp_command "$g_shorewalldir/firewall $g_shorewalldir/firewall.conf" ${litedir};  then
	    save=$(find_file save);

	    if [ -f $save ]; then
		progress_message3 "Copying $save to ${system}:${confdir}/${PRODUCT}-lite/"
		rcp_command $save ${confdir}/$PRODUCT/
		exitstatus=$?
	    fi

	    if [ $exitstatus -eq 0 ]; then
		progress_message3 "Copy complete"

		if [ $COMMAND = remote-reload ]; then
		    if rsh_command "$program $verbose $timestamp reload"; then
			progress_message3 "System $system reloaded"
		    else
			exitstatus=$?
			savit=
		    fi
		elif [ $COMMAND = remote-restart ]; then
		    if rsh_command "$program $verbose $timestamp restart"; then
			progress_message3 "System $system restarted"
		    else
			exitstatus=$?
			saveit=
		    fi
		elif rsh_command "$program $verbose $timestamp start"; then
		    progress_message3 "System $system started"
		else
		    exitstatus=$?
		    saveit=
		fi

		if [ -n "$saveit" ]; then
		    if rsh_command "$program $verbose $timestamp save"; then
			progress_message3 "Configuration on system $system saved"
		    else
			exitstatus=$?
		    fi
		fi
	    fi
	else
	    exitstatus=$?
	fi
    else
	exitstatus=$?
    fi

    return $exitstatus
}

#
# Export command executor
#
export_command() # $* = original arguments less the command.
{
    local verbose
    verbose=$(make_verbose)
    local file
    file=
    local finished
    finished=0
    local target

    while [ $finished -eq 0 -a $# -gt 0 ]; do
	option=$1
	case $option in
	    -*)
		option=${option#-}

		while [ -n "$option" ]; do
		    case $option in
			-)
			    finished=1
			    option=
			    ;;
			*)
			    fatal_error "Unrecognized option \"$option\""
			    ;;
		    esac
		done
		shift
		;;
	    *)
		finished=1
		;;
	esac
    done

    case $# in
	1)
	    g_shorewalldir="."
	    target=$1
	    ;;
	2)
	    g_shorewalldir=$1
	    target=$2
	    ;;
	*)
	    fatal_error "Invalid command syntax (\"man shorewall\" for help)"
	    ;;
    esac

    case $target in
	*:*)
	    ;;
	*)
	    target=$target:
	    ;;
    esac

    file=$(resolve_file $g_shorewalldir/firewall)

    g_export=Yes

    g_file="$g_shorewalldir/firewall"

    if compiler compile "$g_file" && \
	echo "Copying $file and ${file}.conf to ${target#*@}..." && \
	scp $g_shorewalldir/firewall $g_shorewalldir/firewall.conf $target
    then
	save=$(find_file save);

	[ -f $save ] && progress_message3 "Copying $save to ${target#*}..." && rcp_command $save $target

	progress_message3 "Copy complete"
    fi
}

run_command() {
    if [ -x $g_firewall ] ; then
	uptodate $g_firewall || echo "   WARNING: $g_firewall is not up to date" >&2
	run_it $g_firewall $@
    else
	fatal_error "$g_firewall does not exist or is not executable"
    fi
}

compiler_command() {

    case $COMMAND in
	compile|co)
	    shift
	    compile_command $@
	    ;;
	check|ck)
	    shift
	    check_command $@
	    ;;
	update)
	    shift
	    update_command $@
	    ;;
	remote-start|remote-reload|remote-restart)
	    shift
	    remote_commands $@
	    ;;
	export)
	    shift
	    export_command $@
	    ;;
	try)
	    only_root
	    get_config Yes
	    shift
	    try_command $@
	    ;;
	safe-reload|safe-restart|safe-start)
	    only_root
	    get_config Yes
	    shift
	    safe_commands $@
	    ;;
        remote-getrc|remote-getcaps)
            shift
            remote_capture $@
            ;;
	*)
	    fatal_error "Invalid command: $COMMAND"
	    ;;
    esac

}