#!/usr/bin/bash
#
##
# Advanced Policy Firewall (APF) v2.0.2
#             (C) 2002-2026, R-fx Networks <proj@rfxn.com>
#             (C) 2026, Ryan MacDonald <ryan@rfxn.com>
# This program may be freely redistributed under the terms of the GNU GPL v2
##
#
VER="2.0.2"
CNF="/etc/apf/conf.apf"

apf_banner() {
	echo "Advanced Policy Firewall (APF) v$VER <proj@rfxn.com>"
	echo "      Copyright (C) 2002-2026, R-fx Networks <proj@rfxn.com>"
	echo "      Copyright (C) 2026, Ryan MacDonald <ryan@rfxn.com>"
	echo "This program may be freely redistributed under the terms of the GNU GPL v2"
	echo ""
}

if [ -f "$CNF" ]; then
   source "$CNF"
else
   apf_banner
   echo "$CNF not found, aborting." >&2
   exit 1
fi

# Initialize log environment (creates dirs, files, enables output modules)
# ELOG_LOG_DIR/ELOG_AUDIT_FILE are set in internals.conf
if ! elog_init; then
	echo "warning: elog_init failed; structured event logging may be unavailable" >&2
fi
# elog_init enables file/audit_file but NOT stdout — enable it for eout() compat
elog_output_enable "stdout" 2>/dev/null || true  # safe: stdout always registered in elog_lib.sh
# Restrict stdout to elog() calls only — elog_event() output goes to audit/SIEM,
# not the terminal. Without this, eout+elog_event pairs duplicate on screen.
if _elog_output_find "stdout"; then
	_ELOG_OUTPUT_SOURCES[$_ELOG_OUTPUT_IDX]="elog"
fi

if [ ! -f "$LOG_APF" ]; then
        command touch "$LOG_APF"
        command chmod 640 "$LOG_APF"
        eout "{glob} status log not found, created"
fi

# Clean up mutex lock on unexpected termination
trap '_apf_cleanup_tmp; mutex_unlock' EXIT

# Acquire exclusive lock for mutating operations via file-based flock.
# Re-invokes this script under flock; child sees _APF_LOCKED=1 and skips gate.
# Read-only ops (-l, -t, -o, -v, -g, -h, --help, help, --templ, --gre-status, --la, --ld, --validate, --rules, --info, --lookup, --cc, --ct-status) excluded.
# Restart (-r) excluded — sub-invocations lock individually to avoid deadlock.
if [ "$_APF_LOCKED" != "1" ]; then
  case "$1" in
    -s|--start|-f|--stop|--flush|-e|--refresh|\
    -a|-d|-u|--allow|--deny|--remove|--unban|\
    -ta|--temp-allow|-td|--temp-deny|\
    --tempf|--temp-flush|--temp-expire|\
    --ipset-update|--gre-up|--gre-down|--cc-update|--ct-scan|\
    -ar|-dr|-tr|\
    trust)
      if command -v flock >/dev/null 2>&1; then
        _APF_LOCKED=1 flock -w "$ENTER_LOCK_TIMEOUT" "$LOCK_FILE" "$0" "$@"
        exit $?
      fi
      ;;
  esac
fi

case "$1" in
-s|--start)
	mutex_lock
	start
	;;
-f|--flush|--stop)
	mutex_lock
	flush
	elog_event "service_state" "info" "APF firewall stopped"
	;;
-l|--list)
	list
	;;
-t|-st|--status)
	status
	;;

-r|--restart)
	$0 --flush || exit $?
	sleep 2
	$0 --start || exit $?
	;;
-a)
	mutex_lock
	# Propagate exit code: invalid host, duplicate, or rule failure → exit 1
	cli_trust "TALLOW" "ALLOW" "$ALLOW_HOSTS" "$2" "$3" || exit 1
	;;
-d)
	mutex_lock
	# Propagate exit code: invalid host, duplicate, or rule failure → exit 1
	cli_trust "TDENY" "DENY" "$DENY_HOSTS" "$2" "$3" || exit 1
	;;
-u)
	mutex_lock
	_cli_trust_remove_with_output "$2" || exit 1
	;;
-e|--refresh)
	mutex_lock
	refresh
	;;
-o)
	apf_banner
	ovars
	;;
-v|--version)
	echo "$VER"
	;;
-g|--search)
	search "$2"
	;;
-h)
	apf_banner
	help
	;;
--help)
	# Progressive disclosure: --help opens the full man page
	# Guard: minimized Ubuntu/Debian Docker images replace man(1) with a
	# stub that prints an "unminimize" advisory instead of rendering pages
	if command -v man >/dev/null 2>&1 && \
	   man -w apf >/dev/null 2>&1 && \
	   ! man apf 2>&1 | command head -1 | command grep -q "minimized"; then  # safe: detect man stub
		man apf
	else
		apf_banner
		help
	fi
	;;

help)
	case "${2:-}" in
	"")     apf_banner; help ;;
	trust)  _dispatch_trust --help ;;
	cc)
		# shellcheck disable=SC1090,SC1091
		. "$INSTALL_PATH/internals/apf_geoip.sh"
		_dispatch_cc --help
		;;
	config) _dispatch_config --help ;;
	status) _dispatch_status --help ;;
	gre)    _dispatch_gre --help ;;
	ipset)  _dispatch_ipset --help ;;
	ct)
		# shellcheck disable=SC1090,SC1091
		. "$INSTALL_PATH/internals/apf_ctlimit.sh"
		_dispatch_ct --help
		;;
	*)
		echo "apf: unknown topic '${2}'. See 'apf help'." >&2
		exit 1
		;;
	esac
	;;

# --- Tier 2: Subcommand noun dispatch ---
trust)
	_dispatch_trust "${@:2}" || exit $?
	;;
cc)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_geoip.sh"
	_dispatch_cc "${@:2}" || exit $?
	;;
config)
	_dispatch_config "${@:2}" || exit $?
	;;
status)
	_dispatch_status "${@:2}" || exit $?
	;;
gre)
	_dispatch_gre "${@:2}" || exit $?
	;;
ipset)
	_dispatch_ipset "${@:2}" || exit $?
	;;
ct)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_ctlimit.sh"
	_dispatch_ct "${@:2}" || exit $?
	;;

# --- Tier 3: Legacy 2.0.x aliases (silent, route through dispatch) ---
--allow)
	_dispatch_trust add "${@:2}"
	;;
--deny)
	_dispatch_trust deny "${@:2}"
	;;
--remove|--unban)
	_dispatch_trust remove "${@:2}"
	;;
-ta|--temp-allow)
	_dispatch_trust temp add "${@:2}"
	;;
-td|--temp-deny)
	_dispatch_trust temp deny "${@:2}"
	;;
--templ|--temp-list)
	_dispatch_trust temp list
	;;
--tempf|--temp-flush)
	_dispatch_trust temp flush
	;;
--temp-expire)
	_dispatch_trust temp expire
	;;
--la|--list-allow)
	_dispatch_trust list --allow
	;;
--ld|--list-deny)
	_dispatch_trust list --deny
	;;
--lookup)
	_dispatch_trust lookup "${@:2}"
	;;
--validate|--check)
	_dispatch_config validate
	;;
--dump-config|--ovars)
	_dispatch_config dump
	;;
--rules)
	_dispatch_status rules
	;;
--info)
	_dispatch_status
	;;
--ct-scan)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_ctlimit.sh"
	_dispatch_ct scan
	;;
--ct-status)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_ctlimit.sh"
	_dispatch_ct status
	;;
--cc)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_geoip.sh"
	_dispatch_cc info "${@:2}"
	;;
--cc-update)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_geoip.sh"
	_dispatch_cc update
	;;
--ipset-update)
	_dispatch_ipset update
	;;
--gre-up)
	_dispatch_gre up
	;;
--gre-down)
	_dispatch_gre down
	;;
--gre-status)
	_dispatch_gre status
	;;
--csf-help)
	_csf_help
	;;

# --- Tier 4: CSF compat aliases (silent) ---
-ar|-dr)
	_dispatch_trust remove "${@:2}"
	;;
-tr)
	_dispatch_trust temp remove "${@:2}"
	;;
-i)
	# shellcheck disable=SC1090,SC1091
	. "$INSTALL_PATH/internals/apf_geoip.sh"
	_dispatch_cc lookup "${@:2}"
	;;
"")
	apf_banner
	help
	exit 1
	;;
*)
	_cli_unknown_command "apf" "$1" "trust status cc ct config ipset gre help -s -f -r -a -d -u -g -e -l -t -o -v -h"
	exit 1
	;;
esac
mutex_unlock
exit 0
