#!/bin/bash
set -euo pipefail

# Convertisseur de configuration strongSwan pour ARV / Sphynx.
#
# ARV génère historiquement une configuration au format ipsec.conf,
# ipsec.secrets et /etc/ipsec.d/*. Ce format était chargé par
# strongswan-starter via les commandes :
#
#   ipsec rereadall
#   ipsec update
#
# En strongSwan 6 avec charon-systemd, strongswan-starter et la commande
# ipsec ne sont plus utilisés. Le service strongswan charge désormais sa
# configuration via swanctl.
#
# Ce script sert donc de couche de compatibilité temporaire :
#
#   1. conserver la génération ARV existante au format ipsec.conf ;
#   2. recopier les certificats/clefs vers l’arborescence attendue par swanctl ;
#   3. aplatir les include de /etc/ipsec.conf, car ipsec2swanctl ne les traite
#      pas correctement dans le cas ARV ;
#   4. convertir la configuration legacy avec ipsec2swanctl.py ;
#   5. corriger la valeur version=ike générée par le convertisseur ;
#   6. charger la configuration finale avec swanctl --load-all --noprompt.
#
# L’objectif est d’éviter une réécriture complète immédiate du générateur ARV,
# tout en permettant à Sphynx 2.11 d’utiliser le modèle strongSwan 6/swanctl.

EXPANDED_CONF="/run/eole-ipsec-expanded.conf"
SWANCTL_CONF="/etc/swanctl/conf.d/eole-arv.conf"
CONVERTER="/usr/lib/python3/dist-packages/arv/lib/ipsec2swanctl.py"

mkdir -p /etc/swanctl/conf.d \
         /etc/swanctl/x509 \
         /etc/swanctl/x509ca \
         /etc/swanctl/private \
         /etc/swanctl/x509crl

# Copie les magasins historiques strongSwan vers les répertoires utilisés
# par swanctl.
#
# /etc/ipsec.d/certs    -> certificats machine
# /etc/ipsec.d/cacerts  -> certificats d'autorités
# /etc/ipsec.d/private  -> clefs privées
# /etc/ipsec.d/crls     -> listes de révocation
#
# Les copies sont tolérantes : tous les dossiers ne sont pas forcément
# présents selon l’état de la configuration ARV.
cp -af /etc/ipsec.d/certs/.    /etc/swanctl/x509/    2>/dev/null || true
cp -af /etc/ipsec.d/cacerts/.  /etc/swanctl/x509ca/  2>/dev/null || true
cp -af /etc/ipsec.d/private/.  /etc/swanctl/private/ 2>/dev/null || true
cp -af /etc/ipsec.d/crls/.     /etc/swanctl/x509crl/ 2>/dev/null || true

# Génère les fichiers include par défaut utilisés par ipsec2swanctl.
#
# Le convertisseur crée notamment :
#   /etc/swanctl/conf.d/ike_sa_default.conf
#   /etc/swanctl/conf.d/child_sa_default.conf
#
# Ces fichiers sont ensuite inclus dans les connexions converties.
python3 "$CONVERTER" \
  --sysconfdir /etc \
  -o /tmp/eole-empty-swanctl.conf \
  -w >/dev/null

# Correction du convertisseur.
#
# ARV utilise keyexchange=ike dans ipsec.conf. ipsec2swanctl le convertit en :
#
#   version=ike
#
# Or swanctl attend une valeur numérique :
#
#   0 = IKEv1/IKEv2
#   1 = IKEv1
#   2 = IKEv2
#
# La valeur équivalente à keyexchange=ike est donc version=0.
sed -i 's/^version=ike$/version=0/' /etc/swanctl/conf.d/ike_sa_default.conf

# Aplatit les include de /etc/ipsec.conf.
#
# ARV génère un fichier principal qui contient généralement :
#
#   include /etc/ipsec.d/conf/*
#
# Le convertisseur ne développe pas correctement ces include dans notre cas.
# On produit donc un fichier temporaire complet dans /run avant conversion.
python3 - <<'PY'
from pathlib import Path
import glob

src = Path("/etc/ipsec.conf")
dst = Path("/run/eole-ipsec-expanded.conf")

out = []
for line in src.read_text().splitlines():
    stripped = line.strip()
    if stripped.startswith("include "):
        pattern = stripped.split(None, 1)[1]
        for fname in sorted(glob.glob(pattern)):
            out.append(f"\n# BEGIN include {fname}")
            out.append(Path(fname).read_text())
            out.append(f"# END include {fname}\n")
    else:
        out.append(line)

dst.write_text("\n".join(out) + "\n")
PY

# Convertit la configuration réellement utilisée par ARV vers swanctl.conf.
python3 "$CONVERTER" \
  --ipsecconf "$EXPANDED_CONF" \
  -o "$SWANCTL_CONF" \
  -w

# Post-traitement de la sortie ipsec2swanctl.
#
# Les noms de connexion générés par ARV peuvent contenir des caractères
# acceptés par ipsec.conf mais problématiques dans les noms de sections
# swanctl.conf, notamment des points et des guillemets. 
# Exemple : conn "aca.sphynx-default-2.11.0-etb1.amon-default-2.10.0_1-admin-interco"
#
# ipsec2swanctl conserve ce nom entre guillemets pour les sections swanctl. 
# Le parseur swanctl les refuse. 
# On normalise donc uniquement les noms de sections, jamais les valeurs de configuration.
#
# Le convertisseur recopie aussi l'option legacy leftupdown. 
# Dans la configuration ARV actuelle elle est vide (leftupdown="") et swanctl ne
# connaît pas cette option : on la supprime.
#
# Enfin, certaines versions de ipsec2swanctl écrivent filename= dans la section secrets, 
# alors que swanctl attend file=. On corrige ce nom de clé sans toucher à sa valeur.
python3 - <<'PY'
from pathlib import Path
import re

path = Path("/etc/swanctl/conf.d/eole-arv.conf")
lines = path.read_text().splitlines()

reserved_sections = {
    "connections",
    "children",
    "pools",
    "authorities",
    "secrets",
}

# Nom de section swanctl, avec ou sans guillemets.
section_re = re.compile(r'^(\s*)(?:"([^"]+)"|([^\s#{}][^{}]*?))\s*\{\s*$')

def sanitize_section_name(name):
    name = name.strip().strip('"')
    # Garde uniquement les caractères simples acceptés dans les noms de
    # sections swanctl. Les points des noms ARV deviennent donc des "_".
    name = re.sub(r"[^A-Za-z0-9_-]", "_", name)

    # Par sécurité, évite un nom commençant par un chiffre.
    if not re.match(r"^[A-Za-z_]", name):
        name = "c_" + name

    return name

out = []

for line in lines:
    # Option legacy issue de ipsec.conf, inconnue de swanctl.
    if re.match(r'^\s*leftupdown\s*=', line):
        continue

    # ipsec2swanctl peut produire "filename=..." dans secrets.
    # swanctl attend "file = ...".
    line = re.sub(r'^(\s*)filename\s*=\s*', r'\1file = ', line)

    match = section_re.match(line)
    if match:
        indent = match.group(1)
        name = (match.group(2) or match.group(3)).strip()

        if name not in reserved_sections:
            line = f"{indent}{sanitize_section_name(name)} {{"

    out.append(line)

path.write_text("\n".join(out) + "\n")
PY

# Charge la configuration swanctl, les certificats, les clefs et les secrets.
#
# --noprompt évite toute question interactive lors d’un reconfigure/posttemplate ou d’un appel ARV.
#
# Certaines erreurs de parsing peuvent être affichées dans la sortie de swanctl sans provoquer 
# un code retour non nul. 
# Donc on inspecte la sortie pour faire échouer le wrapper si la configuration convertie est invalide.
set +e
LOAD_OUTPUT="$(swanctl --load-all --noprompt 2>&1)"
LOAD_RC=$?
set -e

printf '%s\n' "$LOAD_OUTPUT"

if [ "$LOAD_RC" -ne 0 ] || printf '%s\n' "$LOAD_OUTPUT" | grep -Eiq 'invalid config file|syntax error|unknown option|loading connection .* failed|loaded [0-9]+ of [0-9]+ connections, [1-9][0-9]* failed to load'; then
    exit 1
fi
