#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=/dev/null
source "$SCRIPT_DIR/libenv.sh"

ENV_FILE=$(limristem_mail_resolve_main_env_file)

usage() {
  cat <<'EOF'
Usage:
  manage-rbl.sh show [--json]
  manage-rbl.sh set <yes|no> [zones...]

Configures optional inbound DNSBL helpers for Rspamd (limristem_rbl.conf).
EOF
}

require_root() {
  if [[ $EUID -ne 0 ]]; then
    echo "Run as root." >&2
    exit 1
  fi
}

load_env() {
  limristem_mail_load_env_file "$ENV_FILE"
}

show() {
  local enabled=${LIMRISTEM_MAIL_RSPAMD_DNSBL_ENABLED:-no}
  local zones=${LIMRISTEM_MAIL_RSPAMD_DNSBL_ZONES:-zen.spamhaus.org bl.spamcop.net}
  if [[ ${1:-} == "--json" ]]; then
    python3 - "$enabled" "$zones" <<'PY'
import json, sys
print(json.dumps({"enabled": sys.argv[1].lower() in {"1","true","yes","on"}, "zones": sys.argv[2].split()}))
PY
    return
  fi
  printf 'enabled=%s\n' "$enabled"
  printf 'zones=%s\n' "$zones"
}

sanitize_zone() {
  # Whitelist, not escape: this value is interpolated into an Rspamd config file, where
  # a stray quote or brace would inject configuration. Anything outside the DNS
  # character set is dropped.
  local zone=${1,,}
  zone=${zone//[^a-z0-9._-]/}
  zone=${zone#.}
  zone=${zone%.}
  printf '%s' "$zone"
}

rbl_symbol_for() {
  local safe=${1//[^A-Za-z0-9]/_}
  safe=${safe^^}
  safe=${safe#_}
  safe=${safe%_}
  printf 'LIMRISTEM_RBL_%s' "$safe" | cut -c1-60
}

write_conf() {
  local enabled=$1
  shift
  local zones=("$@")
  # Rspamd reads local.d/<module>.conf only. The previous target,
  # local.d/limristem_rbl.conf, matches no module and was never loaded: switching the
  # DNSBL on from the panel reported success and changed nothing.
  local conf=/etc/rspamd/local.d/rbl.conf
  local legacy=/etc/rspamd/local.d/limristem_rbl.conf
  local tmp zone safe symbol emitted=0
  mkdir -p /etc/rspamd/local.d
  tmp=$(mktemp)
  {
    echo "# Generated by limristem-mail manage-rbl.sh — do not edit by hand."
    echo "# Reference: https://rspamd.com/doc/modules/rbl.html"
    echo "# enabled=${enabled}"
    echo
  } > "$tmp"
  if [[ "$enabled" == "yes" ]]; then
    for zone in "${zones[@]}"; do
      safe=$(sanitize_zone "$zone")
      [[ -n "$safe" ]] || continue
      symbol=$(rbl_symbol_for "$safe")
      if (( emitted == 0 )); then
        echo "rbls {" >> "$tmp"
      else
        echo >> "$tmp"
      fi
      cat >> "$tmp" <<EOF
  ${symbol} {
    rbl = "${safe}";
    symbol = "${symbol}";
    ipv4 = true;
    ipv6 = false;
    from = true;
    received = false;
  }
EOF
      emitted=$((emitted + 1))
    done
    if (( emitted > 0 )); then
      echo "}" >> "$tmp"
    fi
  fi
  if (( emitted == 0 )); then
    echo "# No Limristem-managed DNSBL zones are active." >> "$tmp"
  fi

  local user
  if id _rspamd >/dev/null 2>&1; then user=_rspamd
  elif id rspamd >/dev/null 2>&1; then user=rspamd
  else user=root
  fi
  install -m 0640 "$tmp" "$conf"
  chown root:"$user" "$conf" 2>/dev/null || true
  rm -f "$tmp" "$legacy"
  systemctl reload rspamd >/dev/null 2>&1 || systemctl restart rspamd >/dev/null 2>&1 || true
}

require_root
load_env
# The "set" command writes /etc/rspamd and must escape the app's read-only
# mount namespace when invoked from the panel/API (see libenv.sh).
case "${1:-}" in
  set)
    limristem_mail_escape_write_namespace LIMRISTEM_MAIL_RBL_WORKER limristem-mail-rbl "$@"
    ;;
esac

cmd=${1:-}
case "$cmd" in
  show)
    show "${2:-}"
    ;;
  set)
    enabled=${2:-}
    case "${enabled,,}" in
      yes|true|1|on) enabled=yes ;;
      no|false|0|off) enabled=no ;;
      *) echo "enabled must be yes or no" >&2; exit 1 ;;
    esac
    shift 2 || true
    zones=("$@")
    if (( ${#zones[@]} == 0 )); then
      zones=(zen.spamhaus.org bl.spamcop.net)
    fi
    limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_RSPAMD_DNSBL_ENABLED "$enabled"
    limristem_mail_upsert_env_value "$ENV_FILE" LIMRISTEM_MAIL_RSPAMD_DNSBL_ZONES "${zones[*]}"
    load_env
    write_conf "$enabled" "${zones[@]}"
    show --json
    ;;
  *)
    usage >&2
    exit 1
    ;;
esac
