## -*- mode: shell-script; -*-
##
## Double '##' comments are removed when configlet is processed.
## Single '#' comments stay.
##
## Some shells (not bash) do not like empty functions. Placing a comment
## inside the function does not help. Using dummy ":" as a placeholder.
##
{{$top_comment}}

{{$errors_and_warnings}}

{{$shell_debug}}
FWBDEBUG=""
RETVAL=0

{{$path}}

{{$constants}}

{{$tools}}

{{$shell_functions}}

{{$fwf_chain_functions}}

{{$run_time_address_tables}}

load_modules() {
    :
    {{$load_modules}}
}

verify_interfaces() {
    :
    {{$verify_interfaces}}
}

prolog_commands() {
    echo "Running prolog script"
    {{$prolog_script}}
}

epilog_commands() {
    echo "Running epilog script"
    {{$epilog_script}}
}

run_epilog_and_exit() {
    epilog_commands
    exit "$1"
}

configure_interfaces() {
    :
    {{$configure_interfaces}}
}

# The shell form of the policy is hundreds of separate commands, and the
# status of the function that runs them is the status of the last one - so
# a rule iptables refused in the middle left the ruleset incomplete while
# the script reported a clean activation.  script_body therefore points
# $IPTABLES and $IP6TABLES at these two for as long as it runs, which
# leaves every generated command line exactly as it was, and counts what
# was refused.  "set -e" is not an alternative: the first thing script_body
# does is write kernel variables, which fail in a container and on a
# read-only /proc, and a rule guarded by 'test -n "$i_eth0" &&' is false
# whenever the interface has no address.
fwf_run_tool() {
    fwf_tool=$1
    shift
    "$fwf_tool" "$@" && return 0
    fwf_status=$?
##  A chain that is already there is not a failure: the script asks for
##  every chain on every activation and redirects the answer away for that
##  reason ("-N ... 2>/dev/null").
    case " $* " in
        *" -N "*) return "$fwf_status" ;;
    esac
    fwf_failed_commands=$((fwf_failed_commands + 1))
    echo "Error: \"$fwf_tool $*\" failed" >&2
    return "$fwf_status"
}

fwf_run_iptables() {
    fwf_run_tool "$fwf_tool_v4" "$@"
}

fwf_run_ip6tables() {
    fwf_run_tool "$fwf_tool_v6" "$@"
}

## The colon keeps the function valid when a firewall compiles to no rules
## at all, the same guard the functions above use.  A shell function needs
## at least one command, so an empty body makes the whole script
## unparsable and nothing in it runs.
##
## $IPTABLES and $IP6TABLES name a shell function for as long as the body
## runs, so that a command the tool refuses is counted instead of being
## lost in the status of whichever command happened to run last.  Every
## generated line is unchanged by it, and the two variables hold the tool
## path again afterwards, which is what `check_tools`, `reset_all` and the
## block, stop and status actions read.  See `fwf_run_tool`.
script_body() {
    :
    fwf_failed_commands=0
    fwf_tool_v4="$IPTABLES"
    fwf_tool_v6="$IP6TABLES"
    IPTABLES=fwf_run_iptables
    IP6TABLES=fwf_run_ip6tables
    {{$script_body}}
    IPTABLES="$fwf_tool_v4"
    IP6TABLES="$fwf_tool_v6"
    test "$fwf_failed_commands" -eq 0
}

ip_forward() {
    :
    {{$ip_forward_commands}}
}

reset_all() {
    :
    {{$reset_all}}
}

{{$block_action}}

{{$stop_action}}

{{$status_action}}

# See how we were called.
# For backwards compatibility missing argument is equivalent to 'start'

cmd=$1
test -z "$cmd" && {
    cmd="start"
}

case "$cmd" in
    start)
        log "Activating firewall script generated by {{$user}}"
        check_tools
        {{if prolog_top}} prolog_commands {{endif}}
        check_run_time_address_table_files
        {{if using_ipset}}
        check_module_ipset
##      A rule naming an address table says "-m set --match-set <name>",
##      and iptables refuses that rule outright when no set of that name
##      is there ("Set <name> doesn't exist"): the rule is simply absent
##      from a ruleset the script then reports as activated.  ipsets do
##      not survive a reboot, so the first activation after one lost
##      every such rule and the second got them back from the sets the
##      first one left behind.  Firewall Builder fills the sets after
##      script_body for the same reason it does most things - nobody
##      noticed - and the restore form makes it worse, because
##      iptables-restore refuses the whole table over the one rule.
        load_run_time_address_table_files
        {{endif}}
        load_modules "{{$load_modules_with_nat}} {{$load_modules_with_ipv6}}"
        configure_interfaces
        verify_interfaces
        {{if prolog_after_interfaces}} prolog_commands {{endif}}
##      `iptables-restore` empties the table itself, so `reset_all`
##      only runs where nothing else does: for the shell form, and
##      for the restore form in coexistence mode, where the restore
##      is called with --noflush.
        {{if not_using_iptables_restore}} reset_all {{endif}}
        {{$setup_fwf_jumps_commands}}
        {{if prolog_after_flush}} prolog_commands {{endif}}
        script_body || run_epilog_and_exit 1
        ip_forward
        epilog_commands
        ;;

    stop)
        check_tools
        stop_action
        RETVAL=$?
        ;;

    status)
        check_tools
        status_action
        RETVAL=$?
        ;;

    block)
        check_tools
        block_action
        RETVAL=$?
        ;;

##  "reload" is "start", not "stop" and then "start".  The start branch
##  resets everything the stop branch resets - `reset_all` is the same
##  function both go through - and the only thing the stop adds is setting
##  the three built-in policies to ACCEPT.  Between the two commands that
##  is a machine with no rules and no policy, for as long as a second
##  process needs to start, read its address tables, load its modules and
##  configure its interfaces.  Firewall Builder writes both calls here.
    reload)
        $0 start
        RETVAL=$?
        ;;

    interfaces)
        check_tools
        configure_interfaces
        RETVAL=$?
        ;;

    test_interfaces)
        check_tools
        FWBDEBUG="echo"
        configure_interfaces
        RETVAL=$?
        ;;

{{if using_ipset}}
## Usage:  script.fw reload_address_table <address_table_name> <file_name> [-4|-6]
    reload_address_table)
        check_tools
        reload_address_table "$2" "$3" "$4"
        ;;

## Usage:  script.fw add_to_address_table <address_table_name> <file_name> <address>
    add_to_address_table)
        check_tools
        add_to_address_table "$2" "$3" "$4"
        ;;

## Usage:  script.fw remove_from_address_table <address_table_name> <file_name> <address>
    remove_from_address_table)
        check_tools
        remove_from_address_table "$2" "$3" "$4"
        ;;

## Usage:  script.fw test_address_table <address_table_name> <address>
    test_address_table)
        check_tools
        test_address_table "$2" "$3"
        ;;
{{endif}}

    *)
        echo "Usage $0 [start|stop|status|block|reload|interfaces|test_interfaces{{if using_ipset}}|reload_address_table|add_to_address_table|remove_from_address_table|test_address_table{{endif}}]"
        RETVAL=1
        ;;

esac

exit $RETVAL
