#!/bin/bash
#
# ferm-systemd - Systemd wrapper for ferm with caching support
# Usage: ferm-systemd {activate|deactivate|early_activate|early_deactivate}
#

set -e

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Parse command
COMMAND=${1:-activate}

debug() {
    [ "${DEBUG}" ] || return 0
    printf 'DEBUG: %s\n' "$@"
}

debug "starting up"

case "$COMMAND" in
    activate_early)
        # These should be set by systemd or have defaults
        : "${FERM_EARLY:=/usr/sbin/ferm}"
        : "${CONFIG_EARLY:=/etc/ferm/ferm-early.conf}"
        : "${CACHE_DIR_EARLY:=/var/cache/ferm/early}"
        : "${CACHE_EARLY:=no}"
        : "${OPTIONS_EARLY:=}"
        : "${NAME_EARLY:=Early Firewall rules}"
        : "${FERM:=$FERM_EARLY}"
        : "${CONFIG:=$CONFIG_EARLY}"
        : "${CACHE_DIR:=$CACHE_DIR_EARLY}"
        : "${CACHE:=$CACHE_EARLY}"
        : "${OPTIONS:=$OPTIONS_EARLY}"
        : "${NAME:=$NAME_EARLY}"
        ;;
    activate)
        # These should be set by systemd or have defaults
        : "${FERM:=/usr/sbin/ferm}"
        : "${CONFIG:=/etc/ferm/ferm.conf}"
        : "${CACHE_DIR:=/var/cache/ferm}"
        : "${CACHE:=no}"
        : "${OPTIONS:=}"
        : "${NAME:=Firewall rules}"
        ;;
    *)
        echo "Usage: $0 {activate||activate_early|deactivate}" >&2
        exit 1
        ;;
esac

debug "variable FERM=${FERM}"
debug "variable CONFIG=${CONFIG}"
debug "variable CACHE_DIR=${CACHE_DIR}"
debug "variable CACHE=${CACHE}"
debug "variable OPTIONS=${OPTIONS}"
debug "variable NAME=${NAME}"

# SLOW takes precedence; if not set, initialize from FAST for backwards compatibility
if [ -z "${SLOW}" ]; then
    if [ "${FAST}" = "no" ]; then
        OPTIONS="${OPTIONS} --slow"
    fi
fi
debug "variable OPTIONS=${OPTIONS}"

# Validate cache directory
if [ ! -d "${CACHE_DIR}" ] || [ ! -w "${CACHE_DIR}" ]; then
    CACHE="no"
fi
debug "variable CACHE=${CACHE}"

# Helper function to check if cache needs regeneration
cache_needs_regen() {
    local CACHE_FILE=$1
    local KERNEL_FILE=$2
    debug "cache_needs_regen ${CACHE_FILE} ${KERNEL_FILE}"

    # Check kernel version
    if ! diff /proc/version "${KERNEL_FILE}" >/dev/null 2>&1; then
        debug "cache needs regen: other kernel"
        return 0  # needs regen
    fi

    # Check if cache file exists
    if [ ! -f "${CACHE_FILE}" ]; then
        debug "cache needs regen: cache file does not exist"
        return 0
    fi

    # Check if config is newer
    if [ "${CACHE_FILE}" -ot "${CONFIG}" ]; then
        debug "cache needs regen: config file is newer than cache file"
        return 0
    fi

    # Check if any file in /etc/ferm is newer
    if [ -n "$(find /etc/ferm -maxdepth 2 -newer "${CACHE_FILE}" 2>/dev/null)" ]; then
        debug "cache needs regen: some file in /etc/ferm is newer than cache file"
        return 0
    fi

    # Check if ferm executable is newer
    if [ "${CACHE_FILE}" -ot "${FERM}" ]; then
        debug "cache needs regen: ferm executable is newer than cache file"
        return 0
    fi

    debug "cache is valid"
    return 1
}

# Prepare cache and run ferm
prepare_and_run() {
    local CONFIG=${1:-ferm-cache}
    local CACHE_NAME
    CACHE_NAME="$(systemd-escape "${CONFIG}")"

    if [ "${CACHE}" = "yes" ]; then
        local CACHE_FILE="${CACHE_DIR}/${CACHE_NAME}.sh"
        local KERNEL_FILE="${CACHE_DIR}/${CACHE_NAME}.kernel"
        debug "variable CACHE_FILE=${CACHE_FILE}"
        debug "variable KERNEL_FILE=${KERNEL_FILE}"

        if cache_needs_regen "${CACHE_FILE}" "${KERNEL_FILE}"; then
            echo "Regenerating ferm cache... ${FERM} ${OPTIONS} --shell ${CONFIG} to ${CACHE_FILE}.tmp"
            rm -f "${CACHE_FILE}" "${CACHE_FILE}.tmp" "${KERNEL_FILE}"

            "${FERM}" ${OPTIONS} --shell "${CONFIG}" > "${CACHE_FILE}.tmp" || return $?

            cp /proc/version "${KERNEL_FILE}"
            mv "${CACHE_FILE}.tmp" "${CACHE_FILE}" || return $?
            echo "Cache generated successfully. Now using fresh cache to activate rules"
        else
            echo "Activating firewall rules Using existing ferm cache"
        fi

        # Execute the cached script
        debug "execute cache file ${CACHE_FILE}"
        . "${CACHE_FILE}" || return $?
    else
        # No caching - run ferm directly
        echo "Activating firewall rules: ${FERM} ${OPTIONS} ${CONFIG}"
        "${FERM}" ${OPTIONS} "${CONFIG}" || return $?
    fi
}

# Main execution
case "$COMMAND" in
    activate|activate_early)
        debug "activate or activate_early, CONFIG=${CONFIG}"
        prepare_and_run "${CONFIG}" || exit $?
        echo "${NAME} activated successfully"
        ;;
    deactivate)
        debug "deactivate, CONFIG=${CONFIG}"
        echo "Flushing firewall rules..."
        # For stop, run ferm directly with --flush (don't use/generate cache)
        "${FERM}" "${OPTIONS}" --flush "${CONFIG}" || exit $?
        echo "${NAME} deactivated succesfully"
        ;;
    deactivate_early)
        echo "Doing nothing, deactivate_early is a no-op..."
        ;;
esac

[ "${DEBUG}" ] || exit 0
for backend in "nft" "legacy"; do
    for table in filter nat mangle raw security; do
        printf "DEBUG: rule output iptables-%s --table %s -nvL" "${backend}" "${table}"
        iptables-${backend} --table ${table} -nvL 2>&1 | sed 's/^/DEBUG: /'
    done
done

exit 0
