#!/usr/bin/env bash
# =============================================================================
# onx-fail2ban-jail-write — Provision a fail2ban jail file under /etc/fail2ban/jail.d
#
# Input (stdin JSON):
#   {
#     "jail_name": "sshd",
#     "enabled":   true,
#     "port":      "ssh,22",
#     "filter":    "sshd",
#     "logpath":   "/var/log/secure",
#     "maxretry":  3,
#     "findtime":  600,
#     "bantime":   3600,
#     "action":    "%(action_mwl)s",
#     "backend":   "auto"        (optional)
#   }
#
# Output (stdout JSON):
#   { "jail":"sshd", "applied":true,
#     "file":"/etc/fail2ban/jail.d/sshd.local",
#     "reloaded":true }
#
# Deployed to: /usr/local/onoxsoft/bin/onx-fail2ban-jail-write
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

readonly JAIL_D="/etc/fail2ban/jail.d"

require_root
onx_json_input

JAIL=$(onx_json_field      "jail_name" "")
ENABLED=$(onx_json_get_bool "${INPUT}" "enabled" "true")
PORT=$(onx_json_field      "port"     "ssh,22")
FILTER=$(onx_json_field    "filter"   "${JAIL}")
LOGPATH=$(onx_json_field   "logpath"  "/var/log/secure")
MAXRETRY=$(onx_json_field  "maxretry" "5")
FINDTIME=$(onx_json_field  "findtime" "600")
BANTIME=$(onx_json_field   "bantime"  "3600")
ACTION=$(onx_json_field    "action"   "%(action_)s")
BACKEND=$(onx_json_field   "backend"  "auto")

[[ -n "${JAIL}" ]] || onx_die 1 "jail_name required"
[[ "${JAIL}" =~ ^[a-zA-Z0-9_-]{1,40}$ ]] \
    || onx_die 1 "invalid jail_name '${JAIL}' (allowed: [a-zA-Z0-9_-]{1,40})"

# Numeric guards
for var in MAXRETRY FINDTIME BANTIME; do
    val="${!var}"
    [[ "${val}" =~ ^[0-9]+$ ]] || onx_die 1 "${var,,} must be integer (got '${val}')"
done

# Logpath safety — keep under /var/log/
[[ "${LOGPATH}" =~ ^/var/log/[A-Za-z0-9._/-]+$ ]] \
    || onx_die 1 "logpath must live under /var/log/ (got '${LOGPATH}')"

# Filter / action / port — restrictive regex (fail2ban directive value charset)
[[ "${FILTER}" =~ ^[a-zA-Z0-9_-]{1,40}$ ]]   || onx_die 1 "invalid filter '${FILTER}'"
[[ "${PORT}"   =~ ^[a-zA-Z0-9,_-]{1,200}$ ]] || onx_die 1 "invalid port spec '${PORT}'"
[[ "${ACTION}" =~ ^[a-zA-Z0-9_%()-]{1,80}$ ]] \
    || onx_die 1 "invalid action '${ACTION}'"

JAIL_FILE="${JAIL_D}/${JAIL}.local"

# Defence-in-depth: jail file must resolve inside /etc/fail2ban/jail.d
JAIL_PARENT="$(realpath -m "${JAIL_D}" 2>/dev/null || printf '%s' "${JAIL_D}")"
[[ "${JAIL_PARENT}" == "${JAIL_D}" ]] \
    || onx_die 1 "jail.d directory resolves outside /etc/fail2ban/jail.d/"

if [[ "${MOCK_MODE}" == "1" ]]; then
    onx_audit "onx-fail2ban" "MOCK jail-write ${JAIL} -> ${JAIL_FILE}"
    jq -nc --arg j "${JAIL}" --arg f "${JAIL_FILE}" \
        '{jail:$j, applied:true, file:$f, reloaded:true, mock:true}'
    exit 0
fi

if [[ ! -d "${JAIL_D}" ]]; then
    mkdir -p "${JAIL_D}" || onx_die 3 "failed to create ${JAIL_D}"
    chmod 0755 "${JAIL_D}"
fi

TMP_FILE="$(mktemp -t onx-f2b.XXXXXX)"
chmod 0644 "${TMP_FILE}"

{
    printf '# Onoxsoft managed — DO NOT EDIT MANUALLY\n'
    printf '# Written: %s\n\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
    printf '[%s]\n' "${JAIL}"
    printf 'enabled  = %s\n' "${ENABLED}"
    printf 'port     = %s\n' "${PORT}"
    printf 'filter   = %s\n' "${FILTER}"
    printf 'logpath  = %s\n' "${LOGPATH}"
    printf 'maxretry = %s\n' "${MAXRETRY}"
    printf 'findtime = %s\n' "${FINDTIME}"
    printf 'bantime  = %s\n' "${BANTIME}"
    if [[ "${BACKEND}" != "auto" && -n "${BACKEND}" ]]; then
        printf 'backend  = %s\n' "${BACKEND}"
    fi
    printf 'action   = %s\n' "${ACTION}"
} > "${TMP_FILE}"

mv -f "${TMP_FILE}" "${JAIL_FILE}" || onx_die 3 "failed to install ${JAIL_FILE}"
chown root:root "${JAIL_FILE}"
chmod 0644 "${JAIL_FILE}"

RELOADED="false"
if command -v fail2ban-client >/dev/null 2>&1; then
    if fail2ban-client reload >/dev/null 2>&1; then
        RELOADED="true"
    else
        onx_log "WARNING: fail2ban-client reload returned non-zero"
    fi
fi

onx_audit "onx-fail2ban" "jail-write ${JAIL} -> ${JAIL_FILE} reloaded=${RELOADED}"

jq -nc \
    --arg j "${JAIL}" \
    --arg f "${JAIL_FILE}" \
    --argjson r "${RELOADED}" \
    '{jail:$j, applied:true, file:$f, reloaded:$r}'
