#!/bin/bash
#
# FileWave Server (macOS / Linux): remove the compromised eMudhra emSign G3 root certificates from the
# CA bundle used by the FileWave Python (Django, scheduler, model update, ...).
#
# Background: in October 2026 the private keys of eight eMudhra intermediate CAs were
# compromised. FileWave Python verifies outbound HTTPS against the bundled certifi CA file,
# which does not honour browser revocation lists, so the emSign G3 roots are removed from it, as upstream certifi
# 2026.10.19 does. The other emSign roots (G1, C1, C3) are not affected and are kept.
# The FileWave additions to that file (Apple Root CA for gdmf.apple.com) are kept.
#
# Usage:  sudo ./fw_server_remove_emsign.sh [--dry-run] [--no-restart] [--file PATH]
#   --dry-run     only report what would be removed
#   --no-restart  do not restart the FileWave services (restart them later yourself:
#                 sudo /usr/local/sbin/fwcontrol server restart)
#   --file PATH   clean this CA file instead of the FileWave Python certifi bundle
#
# A backup of the original file is written next to it (cacert.pem.bak-emsign-<timestamp>).
# The script is idempotent: running it again changes nothing.

set -euo pipefail

FWPYTHON=/usr/local/filewave/python/bin/python
FWCONTROL=/usr/local/sbin/fwcontrol

DRY_RUN=0
RESTART=1
FILES=()

usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; }

while [ $# -gt 0 ]; do
    case "$1" in
        --dry-run) DRY_RUN=1 ;;
        --no-restart) RESTART=0 ;;
        --file) shift; FILES+=("${1:?--file needs a path}") ;;
        -h|--help) usage; exit 0 ;;
        *) echo "Unknown option: $1" >&2; usage; exit 2 ;;
    esac
    shift
done

command -v openssl >/dev/null || { echo "ERROR: openssl not found in PATH" >&2; exit 1; }

if [ ${#FILES[@]} -eq 0 ]; then
    for f in /usr/local/filewave/python/lib/python3*/site-packages/certifi/cacert.pem; do
        [ -f "$f" ] && FILES+=("$f")
    done
fi
[ ${#FILES[@]} -gt 0 ] || { echo "ERROR: FileWave Python certifi bundle not found, is this a FileWave Server?" >&2; exit 1; }

REMOVED=0
KEPT=0
CHANGED=0

# The roots excluded by certifi 2026.10.19; the TLS one is not in certifi but may be in an OS store.
AFFECTED_ROOTS='CN ?= ?emSign (ECC Root CA|Root TLS CA) - G3'

# filter_bundle IN OUT: writes IN to OUT without the affected roots (and their comment headers)
filter_bundle() {
    local in="$1" out="$2" chunk="" pem="" in_pem=0 line subject
    REMOVED=0
    KEPT=0
    : > "$out"
    while IFS= read -r line || [ -n "$line" ]; do
        chunk+="$line"$'\n'
        if [ "$line" = "-----BEGIN CERTIFICATE-----" ]; then
            in_pem=1
            pem="$line"$'\n'
        elif [ "$in_pem" -eq 1 ] && [ "$line" = "-----END CERTIFICATE-----" ]; then
            in_pem=0
            pem+="$line"$'\n'
            subject=$(printf '%s' "$pem" | openssl x509 -noout -subject 2>/dev/null || true)
            if printf '%s' "$subject" | grep -qE "$AFFECTED_ROOTS"; then
                echo "  affected root: ${subject#subject=}"
                REMOVED=$((REMOVED + 1))
            else
                printf '%s' "$chunk" >> "$out"
                KEPT=$((KEPT + 1))
            fi
            chunk=""
        elif [ "$in_pem" -eq 1 ]; then
            pem+="$line"$'\n'
        fi
    done < "$in"
    printf '%s' "$chunk" >> "$out"
}

# clean_file FILE BACKUP_DIR
clean_file() {
    local file="$1" backup_dir="$2" tmp backup
    echo "Checking $file"
    [ -f "$file" ] || { echo "  ERROR: file not found" >&2; return 1; }
    if [ "$DRY_RUN" -eq 0 ] && { [ ! -w "$file" ] || [ ! -w "$backup_dir" ]; }; then
        echo "  ERROR: no write access, run with sudo" >&2
        return 1
    fi
    tmp=$(mktemp "$backup_dir/.emsign.XXXXXX")
    cp -p "$file" "$tmp"
    filter_bundle "$file" "$tmp"
    if [ "$REMOVED" -eq 0 ]; then
        echo "  no affected emSign root found, nothing to do"
        rm -f "$tmp"
        return 0
    fi
    if [ "$KEPT" -eq 0 ]; then
        echo "  ERROR: nothing would be left in the file, left untouched" >&2
        rm -f "$tmp"
        return 1
    fi
    if [ "$DRY_RUN" -eq 1 ]; then
        echo "  dry run: would remove $REMOVED certificate(s) and keep $KEPT"
        rm -f "$tmp"
        return 0
    fi
    backup="$backup_dir/$(basename "$file").bak-emsign-$(date +%Y%m%d%H%M%S)"
    cp -p "$file" "$backup"
    mv -f "$tmp" "$file"
    CHANGED=1
    echo "  removed $REMOVED certificate(s), kept $KEPT, backup: $backup"
}

for f in "${FILES[@]}"; do
    clean_file "$f" "$(dirname "$f")"
    grep -q "Added by FileWave build step" "$f" \
        || echo "  WARNING: the FileWave Apple Root CA addition is not in this file, gdmf.apple.com calls may fail"
done

if [ "$CHANGED" -eq 1 ] && [ -x "$FWPYTHON" ]; then
    echo "Verifying the cleaned bundle loads in FileWave Python..."
    for f in "${FILES[@]}"; do
        "$FWPYTHON" -I -c 'import ssl, sys; c = ssl.create_default_context(cafile=sys.argv[1]); print("  OK,", len(c.get_ca_certs()), "CA certificates in", sys.argv[1])' "$f"
    done
fi

if [ -f /private/var/FileWave/trust_store/fw_cacert.pem ] || [ -f /var/FileWave/trust_store/fw_cacert.pem ]; then
    echo "NOTE: a FileWave Booster is installed on this host, also run fw_booster_remove_emsign.sh"
fi

if [ "$CHANGED" -eq 1 ]; then
    if [ "$RESTART" -eq 1 ]; then
        echo "Restarting the FileWave Server so every Python process reloads the bundle..."
        "$FWCONTROL" server restart
    else
        echo "Restart the FileWave Server to apply: sudo $FWCONTROL server restart"
    fi
fi
echo "Done."
