#!/usr/bin/env bash
# =====================================================================
# cdp-fw-restrict-ssh — die Beschraenkung von Port 22 auf eine neue
# Adresse richten. Rein von Hand; nichts ruft dieses Werkzeug selbsttaetig
# auf, und es gibt mit Absicht KEINEN Mechanismus, der die Beschraenkung
# von selbst wieder aufhebt.
#
#   cdp-fw-restrict-ssh 203.0.113.9
#   cdp-fw-restrict-ssh --open        # Beschraenkung aufheben, bewusst
#
# Traegt den Wert in /etc/controldeskpanel/firewall.conf ein und gleicht
# die Ausnahmeliste von fail2ban ab, damit die neue Adresse sich nicht
# selbst aussperren kann.
# =====================================================================
set -uo pipefail

CONF=/etc/controldeskpanel/firewall.conf
[[ -f "$CONF" ]] || { echo "fehlt: $CONF" >&2; exit 2; }
# shellcheck disable=SC1090
. "$CONF"
OLD="${CDP_ADMIN_SSH_SOURCES:-}"

usage() {
  echo "Aufruf: $0 <adresse-oder-cidr> [weitere...]" >&2
  echo "        $0 --open" >&2
  echo "aktuell: ${OLD:-<keine, Port 22 offen>}" >&2
  exit 2
}
[[ $# -gt 0 ]] || usage

valid() {
  python3 -c 'import ipaddress,sys
try:
    ipaddress.ip_network(sys.argv[1], strict=False)
except Exception:
    sys.exit(1)' "$1"
}

covers() {
  python3 -c 'import ipaddress,sys
try:
    sys.exit(0 if ipaddress.ip_address(sys.argv[1]) in ipaddress.ip_network(sys.argv[2], strict=False) else 1)
except Exception:
    sys.exit(1)' "$1" "$2"
}

if [[ "$1" == "--open" ]]; then
  echo "== Port 22 wieder fuer alle oeffnen =="
  ufw allow 22/tcp comment 'SSH (unbeschraenkt, bewusst gesetzt)'
  for ip in $OLD; do
    ufw --force delete allow from "$ip" to any port 22 proto tcp 2>&1 | sed 's/^/  /'
  done
  sed -i 's|^CDP_ADMIN_SSH_SOURCES=.*|CDP_ADMIN_SSH_SOURCES=""|' "$CONF"
  cdp-f2b-sync-ignoreip || echo "  WARNUNG: Ausnahmeliste von Hand abgleichen" >&2
  ufw status | grep -E "^22/tcp" || true
  exit 0
fi

for ip in "$@"; do
  valid "$ip" || { echo "keine gueltige Adresse: $ip" >&2; exit 2; }
done

# Sicherung gegen das eigene Aussperren: kommt die laufende Sitzung ueber
# SSH und deckt keine der neuen Adressen sie ab, wird nichts umgestellt.
CLIENT="${SSH_CONNECTION%% *}"
if [[ -n "${CLIENT:-}" && "$CLIENT" != "${SSH_CONNECTION:-}" ]]; then
  ok=0
  for ip in "$@"; do covers "$CLIENT" "$ip" && { ok=1; break; }; done
  if [[ "$ok" -eq 0 ]]; then
    echo "ABBRUCH: die laufende Sitzung kommt von ${CLIENT}." >&2
    echo "Keine der angegebenen Adressen deckt sie ab — das waere die letzte" >&2
    echo "Sitzung gewesen. Adresse pruefen und erneut aufrufen." >&2
    exit 3
  fi
fi

echo "== neue Quellen erlauben =="
for ip in "$@"; do
  ufw allow from "$ip" to any port 22 proto tcp comment "SSH admin source $ip"
done

echo "== bisherige Quellen entfernen =="
for ip in $OLD; do
  for keep in "$@"; do [[ "$ip" == "$keep" ]] && continue 2; done
  ufw --force delete allow from "$ip" to any port 22 proto tcp 2>&1 | sed 's/^/  /'
done

echo "== eine offene Regel fuer Port 22 entfernen, falls vorhanden =="
ufw --force delete allow 22/tcp 2>&1 | sed 's/^/  /'

echo "== Wert eintragen =="
sed -i "s|^CDP_ADMIN_SSH_SOURCES=.*|CDP_ADMIN_SSH_SOURCES=\"$*\"|" "$CONF"
grep "^CDP_ADMIN_SSH_SOURCES=" "$CONF"

echo "== Ausnahmeliste von fail2ban abgleichen =="
cdp-f2b-sync-ignoreip || echo "  WARNUNG: Ausnahmeliste von Hand abgleichen" >&2

echo
ufw status | grep -E "22" || echo "  WARNUNG: keine Regel fuer Port 22 vorhanden!" >&2