#!/usr/bin/env bash
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# SecuBox-Deb :: torctl — pilote les endpoints Tor (SOCKS LAN + transparent).
set -euo pipefail
SHARE="${TORCTL_SHARE_D:-/usr/share/secubox/tor}"
# Env overrides for tests: TORCTL_SHARE_D / TORCTL_TORRC_D / TORCTL_UNBOUND_D / TORCTL_NFT_D / TORCTL_DRYRUN
TORRC_D="${TORCTL_TORRC_D:-/etc/tor/torrc.d}"
UNBOUND_D="${TORCTL_UNBOUND_D:-/etc/unbound/unbound.conf.d}"
NFT_D="${TORCTL_NFT_D:-/etc/nftables.d}"
DRYRUN="${TORCTL_DRYRUN:-0}"
lan_ip() { /usr/sbin/tor-lan-ip; }

_reload() { [ "$DRYRUN" = "1" ] && return 0; "$@" || true; }

# Copie un template packagé ($SHARE) vers sa destination. En environnement de
# test (TORCTL_DRYRUN=1), $SHARE n'existe pas (pas de paquet installé) : on
# tolère l'absence de la source sans faire échouer le script — seule la
# logique observable (présence/absence des dropins pilotés) est sous test.
# Hors dry-run, un template manquant est une vraie erreur de packaging.
_install_tpl() {
  local src="$1" dst="$2"
  if [ -f "$src" ]; then
    cp "$src" "$dst"
  elif [ "$DRYRUN" = "1" ]; then
    :
  else
    echo "template manquant: $src" >&2
    return 1
  fi
}

socks_lan_ensure() {
  local ip; ip="$(lan_ip)"; [ -n "$ip" ] || { echo "pas d'IP LAN détectée" >&2; return 1; }
  sed "s/__LAN_IP__/$ip/g" "$SHARE/50-secubox-socks-lan.conf" > "$TORRC_D/50-secubox-socks-lan.conf"
  systemctl reload tor@default 2>/dev/null || systemctl restart tor@default || true
  echo "SocksPort LAN $ip:9050"
}

# "Déjà déclaré" exige TransPort 9040 ET DNSPort 9053 tous les deux présents :
# si un seul des deux existe (ex: TransPort posé par un autre paquet sans son
# DNSPort), on pose quand même notre dropin — sinon Unbound forwarderait vers
# un DNSPort mort.
# Déclarés par un AUTRE dropin (toolbox egress) ? Notre propre 60- est exclu
# du balayage : sinon, une fois posé, il se « trouvait » lui-même et restait
# à côté de celui de toolbox — Tor refusait alors de démarrer (TransPort lié
# deux fois : « Address already in use »), constaté sur gk2 le 2026-09-29 (#1765).
transport_already_declared() {
  local autres=() f
  for f in "$TORRC_D"/*.conf; do
    [ -e "$f" ] && [ "$(basename "$f")" != "60-secubox-transparent.conf" ] && autres+=("$f")
  done
  [ "${#autres[@]}" -gt 0 ] || return 1
  grep -qiE '^[[:space:]]*TransPort[[:space:]]+([0-9.]+:)?9040\b' "${autres[@]}" 2>/dev/null \
    && grep -qiE '^[[:space:]]*DNSPort[[:space:]]+([0-9.]+:)?9053\b' "${autres[@]}" 2>/dev/null
}

transparent_on() {
  if transport_already_declared; then
    rm -f "$TORRC_D/60-secubox-transparent.conf"   # doublon éventuel d'avant
    echo "TransPort 9040 + DNSPort 9053 déjà déclarés (toolbox) — réutilisés, pas de dropin dupliqué"
  else
    _install_tpl "$SHARE/60-secubox-transparent.conf" "$TORRC_D/60-secubox-transparent.conf"
  fi
  _install_tpl "$SHARE/secubox-onion-forward.conf" "$UNBOUND_D/secubox-onion-forward.conf"
  _install_tpl "$SHARE/secubox-tor-transparent.nft" "$NFT_D/secubox-tor-transparent.nft"
  # DNAT vers 127.0.0.1 depuis une iface non-lo exige route_localnet=1 SUR
  # L'IFACE ENTRANTE : conf.all ne s'applique PAS rétroactivement aux ifaces
  # déjà créées (wg-toolbox/eth2). On le pose donc par iface, dérivées du set
  # iifname du dropin nft pour rester synchrones.
  _reload sysctl -q -w net.ipv4.conf.all.route_localnet=1
  local ifaces
  ifaces=$(sed -n 's/.*iifname[[:space:]]*{\([^}]*\)}.*/\1/p' "$NFT_D/secubox-tor-transparent.nft" 2>/dev/null | tr ',"' '  ')
  for i in $ifaces; do
    [ -e "/sys/class/net/$i" ] && _reload sysctl -q -w "net.ipv4.conf.$i.route_localnet=1"
  done
  _reload systemctl reload tor@default
  _reload systemctl restart unbound
  _reload nft -f "$NFT_D/secubox-tor-transparent.nft"
  echo "transparent on"
}

transparent_off() {
  rm -f "$TORRC_D/60-secubox-transparent.conf" "$UNBOUND_D/secubox-onion-forward.conf" "$NFT_D/secubox-tor-transparent.nft"
  _reload systemctl reload tor@default
  _reload systemctl restart unbound
  _reload nft delete table inet secubox-tor-transparent 2>/dev/null
  echo "transparent off"
}

transparent_status() {
  local on=false
  [ -f "$UNBOUND_D/secubox-onion-forward.conf" ] && [ -f "$NFT_D/secubox-tor-transparent.nft" ] && on=true
  echo "{\"transparent\": $on}"
}

case "${1:-}" in
  detect-lan-ip) lan_ip ;;
  socks-lan) [ "${2:-}" = "ensure" ] && socks_lan_ensure ;;
  transparent) case "${2:-}" in on) transparent_on;; off) transparent_off;; status) transparent_status;; esac ;;
  *) echo "usage: torctl {detect-lan-ip|socks-lan ensure|transparent {on|off|status}}" >&2; exit 2 ;;
esac
