#!/bin/bash
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
#
# Phase 6 (#496) — provision the WireGuard server-side state for R3 mode.
# Idempotent : safe to re-run. Creates :
#   - server keypair (privkey + pubkey)
#   - wg-toolbox systemd unit (wg-quick@wg-toolbox.service)
#   - dedicated CA root for the WG mitm container (/etc/secubox/toolbox/ca-wg/)
#   - nft set for R3-consented MACs
#
# Reused by debian/postinst.

set -euo pipefail

WG_DIR="/etc/secubox/toolbox/wg"
WG_CONF="/etc/wireguard/wg-toolbox.conf"
WG_INTERFACE="wg-toolbox"
WG_SERVER_IP="10.99.1.1"
WG_SUBNET="10.99.1.0/24"
WG_PORT=51820
CA_WG_DIR="/etc/secubox/toolbox/ca-wg"

log() { printf '[toolbox-wg-provision] %s\n' "$*"; }
err() { printf '[toolbox-wg-provision] ERROR: %s\n' "$*" >&2; exit 1; }

command -v wg >/dev/null || err "wg not found (apt install wireguard-tools)"

install -d -m 0750 -o root -g secubox-toolbox "$WG_DIR" "$CA_WG_DIR"

# ── Server keypair (idempotent : keep existing if already there) ──
if [ ! -f "$WG_DIR/server.privkey" ]; then
    log "generating server keypair"
    umask 077
    wg genkey > "$WG_DIR/server.privkey"
    wg pubkey < "$WG_DIR/server.privkey" > "$WG_DIR/server.pubkey"
    chmod 0600 "$WG_DIR/server.privkey"
    chmod 0644 "$WG_DIR/server.pubkey"
else
    log "server keypair exists, keeping"
fi

# ── wg-quick interface conf (no PostUp iptables — gk2 uses nft natively) ──
log "writing $WG_CONF"
mkdir -p /etc/wireguard
cat > "$WG_CONF" <<EOF
# Generated by secubox-toolbox-wg-provision — DO NOT EDIT MANUALLY
# Phase 6 (#496) WireGuard server for ToolBoX R3 clients.
[Interface]
PrivateKey = $(cat "$WG_DIR/server.privkey")
Address = ${WG_SERVER_IP}/24
ListenPort = ${WG_PORT}
SaveConfig = false
# NAT/forwarding is provisioned separately via the inet wg-toolbox nft table
# (see below) — gk2 uses pure nft, iptables-nft compat layer is unreliable.
# Peers added dynamically by /wg/profile/new endpoint via 'wg set' command.
EOF
chmod 0600 "$WG_CONF"

# ── nft table for WG NAT + forward + DNAT (optional) ──
log "ensuring nft inet wg-toolbox table"
nft add table inet wg-toolbox 2>/dev/null || true
# Postrouting masquerade : packets out via lan0 get source-NAT'd
nft -- add chain inet wg-toolbox postrouting "{ type nat hook postrouting priority srcnat; }" 2>/dev/null || true
nft add rule inet wg-toolbox postrouting ip saddr ${WG_SUBNET} oif lan0 masquerade 2>/dev/null || true
# Forward : explicit accept for wg-toolbox <-> lan0 traffic
nft -- add chain inet wg-toolbox forward "{ type filter hook forward priority filter; }" 2>/dev/null || true
nft add rule inet wg-toolbox forward iif wg-toolbox oif lan0 accept 2>/dev/null || true
nft add rule inet wg-toolbox forward iif lan0 oif wg-toolbox ct state established,related accept 2>/dev/null || true

# Phase 6.K (#496) : drop UDP 443 (QUIC/HTTP3) from WG clients.
# Chrome and Firefox aggressively prefer HTTP/3 over QUIC, which bypasses
# our TCP-only transparent mitm entirely. By dropping UDP 443, browsers
# transparently fall back to HTTP/2 over TCP → DNAT'd to mitm → analyzed.
# Cost : ~50ms initial fallback handshake. Trade well worth it for visibility.
log "dropping UDP 443 (QUIC) on wg-toolbox to force HTTP/2 fallback"
nft add rule inet wg-toolbox forward iif wg-toolbox udp dport 443 counter drop 2>/dev/null || true

# CRITICAL : allow WG handshake UDP packets to reach the kernel module.
# Without this, the default inet filter input chain (policy=drop) silently
# drops WG packets BEFORE they reach the wireguard kernel module → handshake
# never completes despite tcpdump showing packets arriving.
log "allowing UDP ${WG_PORT} on inet filter input"
nft add rule inet filter input udp dport ${WG_PORT} accept 2>/dev/null || true

# Phase 6.C (#496) : DNAT prerouting for mitm interception.
# secubox-toolbox-mitm-wg.service starts in parallel ; in the worst case the
# tunnel briefly proxies through a not-yet-listening port and curls retry.
# Trade-off accepted — banner injection + WAF visibility require interception.
nft -- add chain inet wg-toolbox prerouting "{ type nat hook prerouting priority dstnat; }" 2>/dev/null || true
nft add rule inet wg-toolbox prerouting iif wg-toolbox tcp dport 443 dnat ip to ${WG_SERVER_IP}:8081 2>/dev/null || true
nft add rule inet wg-toolbox prerouting iif wg-toolbox tcp dport 80 dnat ip to ${WG_SERVER_IP}:8081 2>/dev/null || true

# ── Persist the same rules in /etc/nftables.d/ so they survive reboot ──
# The runtime nft add commands above evaporate on reboot ; the drop-in
# below is loaded by nftables.service via /etc/nftables.conf include.
NFT_DROPIN=/etc/nftables.d/secubox-toolbox-wg.nft
NFT_DROPIN_SRC=/usr/share/secubox/toolbox/nftables.d/secubox-toolbox-wg.nft
if [ -f "$NFT_DROPIN_SRC" ]; then
    install -d -m 0755 /etc/nftables.d
    install -m 0644 "$NFT_DROPIN_SRC" "$NFT_DROPIN"
    log "installed nft drop-in $NFT_DROPIN (boot-survival)"
fi

# ── Enable IP forwarding (persist) ──
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-secubox-wg.conf
sysctl -p /etc/sysctl.d/99-secubox-wg.conf >/dev/null 2>&1 || true

# ── CA WG : mitmproxy-readable combined PEM (key+cert) ──
# mitm 11 looks for <confdir>/mitmproxy-ca.pem at startup. If present, it
# uses our pre-generated CA instead of auto-generating an ugly one with
# CN=mitmproxy. We MUST pre-generate it with a full DN so Android/Chrome
# don't show "Ce certificat émis par null" (Chrome can't render certs
# without a complete subject).
if [ ! -f "$CA_WG_DIR/mitmproxy-ca.pem" ]; then
    log "generating dedicated WG CA (full DN, 10y)"
    cd "$CA_WG_DIR"

    # OpenSSL config — minimal DN, NO SAN (Android Settings parser chokes
    # on SAN with spaces — shows "émis par null" in install dialog).
    # Classic RFC 5280 X.509v3 root, sha256, pathlen 0.
    cat > _ca.cnf <<'CNF'
[req]
distinguished_name = dn
prompt             = no
x509_extensions    = v3_ca

[dn]
C  = FR
O  = CyberMind Gondwana
CN = Gondwana ToolBoX R3 CA

[v3_ca]
basicConstraints       = critical, CA:TRUE, pathlen:0
keyUsage               = critical, keyCertSign, cRLSign
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always
CNF

    openssl genrsa -out _key.pem 2048 2>/dev/null
    openssl req -new -x509 -days 3650 -key _key.pem -out _cert.pem \
        -config _ca.cnf -sha256 2>/dev/null

    # Combined key+cert for mitm (mitmproxy-ca.pem)
    cat _key.pem _cert.pem > mitmproxy-ca.pem
    # 0640 (not 0600) : mitm-wg.service runs as secubox-toolbox group and
    # needs to read the combined key+cert. 0600 → PermissionError →
    # service crash-loops (~190 restarts in 5 min if undetected).
    chmod 0640 mitmproxy-ca.pem
    chown root:secubox-toolbox mitmproxy-ca.pem

    # Public cert only (what we serve via /wg/ca.pem)
    cp _cert.pem mitmproxy-ca-cert.pem
    chmod 0644 mitmproxy-ca-cert.pem
    chown root:secubox-toolbox mitmproxy-ca-cert.pem

    # DER fallback for Android (some installers prefer .cer DER)
    openssl x509 -in _cert.pem -outform DER -out mitmproxy-ca-cert.cer 2>/dev/null
    chmod 0644 mitmproxy-ca-cert.cer
    chown root:secubox-toolbox mitmproxy-ca-cert.cer

    rm -f _key.pem _cert.pem _ca.cnf
    log "WG CA generated : $(openssl x509 -in mitmproxy-ca-cert.pem -noout -subject 2>&1 | head -1)"
else
    log "WG CA exists, keeping ($(openssl x509 -in $CA_WG_DIR/mitmproxy-ca-cert.pem -noout -subject 2>&1 | head -1))"
fi

# ── Enable wg-quick service ──
if [ -d /run/systemd/system ]; then
    log "enabling wg-quick@${WG_INTERFACE}.service"
    systemctl enable "wg-quick@${WG_INTERFACE}.service" 2>/dev/null || true
    # Try to start it ; if kernel module missing we log + continue.
    if systemctl start "wg-quick@${WG_INTERFACE}.service" 2>/dev/null; then
        log "WireGuard interface ${WG_INTERFACE} up"
        wg show "${WG_INTERFACE}" 2>&1 | sed 's/^/    /' || true
    else
        log "WARN: wg-quick@${WG_INTERFACE} failed to start (kernel module missing ?)"
    fi
fi

# ── nft set for R3 ──
log "ensuring nft set consented_r3_wg_macs exists"
if nft list table inet toolbox >/dev/null 2>&1; then
    nft add set inet toolbox consented_r3_wg_macs '{ type ether_addr; flags timeout; timeout 24h; }' 2>/dev/null || true
fi

log "provision complete"
