#!/usr/bin/bash
# Copyright (C) 2025  FreeIPA Contributors see COPYING for license
#
# Sync renewed certificate from NSSDB to PEM files and trigger graceful reload
# This is called by certmonger as a post-command after renewing certificates
#
# Usage: renew_ca_cert "nickname"

set -euo pipefail
umask 077

NICKNAME="$1"

if [ -z "$NICKNAME" ]; then
    echo "Error: Certificate nickname required" >&2
    echo "Usage: renew_ca_cert \"nickname\"" >&2
    exit 1
fi

# Paths
NSSDB_DIR="/etc/pki/pki-tomcat/alias"
PEM_DIR="/var/lib/ipacta"
PASSWORD_FILE="/etc/pki/pki-tomcat/password.conf"

# Create secure temporary directory (mode 700, only accessible by owner)
# Similar to Python's tempfile.mkdtemp()
TEMP_DIR=$(mktemp -d -t renew_ca_cert.XXXXXXXXXX)
if [ ! -d "$TEMP_DIR" ]; then
    echo "Error: Failed to create secure temporary directory" >&2
    exit 1
fi

# Ensure temporary directory is cleaned up on exit
trap 'rm -rf "$TEMP_DIR"' EXIT INT TERM

# Temporary files in secure directory
TEMP_CERT="$TEMP_DIR/cert.pem"
TEMP_P12="$TEMP_DIR/cert.p12"
TEMP_KEY="$TEMP_DIR/key.pem"
TEMP_PWD="$TEMP_DIR/password.txt"

# Extract password from password.conf
if [ ! -f "$PASSWORD_FILE" ]; then
    echo "Error: Password file $PASSWORD_FILE not found" >&2
    exit 1
fi

NSSDB_PASSWORD=$(grep "^internal=" "$PASSWORD_FILE" | cut -d= -f2-)
if [ -z "$NSSDB_PASSWORD" ]; then
    echo "Error: Could not extract NSSDB password from $PASSWORD_FILE" >&2
    exit 1
fi

# Function to export certificate from NSSDB to PEM
export_cert_from_nssdb() {
    local nickname="$1"

    echo "Exporting certificate '$nickname' from NSSDB"

    # Export certificate to PEM format
    certutil -L -d "$NSSDB_DIR" -n "$nickname" -a > "$TEMP_CERT" 2>/dev/null || {
        echo "Error: Failed to export certificate '$nickname' from NSSDB" >&2
        return 1
    }

    # Write password to secure temporary file
    printf '%s\n' "$NSSDB_PASSWORD" > "$TEMP_PWD"

    # Export private key via PKCS#12
    pk12util -o "$TEMP_P12" -d "$NSSDB_DIR" -n "$nickname" \
        -k "$TEMP_PWD" -w "$TEMP_PWD" 2>/dev/null || {
        echo "Error: Failed to export private key for '$nickname'" >&2
        return 1
    }

    # Extract private key from PKCS#12 — use password file, not argv,
    # to avoid exposing the secret in /proc/<pid>/cmdline
    openssl pkcs12 -in "$TEMP_P12" -nodes -nocerts \
        -passin "file:$TEMP_PWD" -out "$TEMP_KEY" 2>/dev/null || {
        echo "Error: Failed to extract private key from PKCS#12" >&2
        return 1
    }
}

# Atomically install a file: copy to temp in target dir, set perms, then mv.
install_file() {
    local src="$1" dst="$2" owner="$3" mode="$4"
    local dst_dir
    dst_dir=$(dirname "$dst")
    local tmp
    tmp=$(mktemp "${dst_dir}/.renew.XXXXXXXXXX")
    cp "$src" "$tmp"
    chown "$owner" "$tmp"
    chmod "$mode" "$tmp"
    mv "$tmp" "$dst"
}

# Map nickname to PEM file location
# This follows the ipacta certificate storage layout
case "$NICKNAME" in
    "caSigningCert cert-pki-ca")
        echo "Syncing CA signing certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update CA certificate and key (atomic)
        install_file "$TEMP_CERT" "$PEM_DIR/certs/ca.crt" ipaca:ipaca 644
        install_file "$TEMP_KEY" "$PEM_DIR/ca/ca-signing.key" ipaca:ipaca 600
        install_file "$TEMP_CERT" "/etc/ipa/ca.crt" root:root 644
        ;;

    "Server-Cert cert-pki-ca")
        echo "Syncing server SSL certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update server certificate and key (atomic)
        install_file "$TEMP_CERT" "$PEM_DIR/certs/server.crt" ipaca:ipaca 644
        install_file "$TEMP_KEY" "$PEM_DIR/private/server.key" ipaca:ipaca 600
        ;;

    "subsystemCert cert-pki-ca")
        echo "Syncing CA subsystem certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update subsystem certificate and key (atomic)
        install_file "$TEMP_CERT" "$PEM_DIR/certs/ca_subsystem.crt" ipaca:ipaca 644
        install_file "$TEMP_KEY" "$PEM_DIR/private/ca_subsystem.key" ipaca:ipaca 600
        ;;

    "auditSigningCert cert-pki-ca")
        echo "Syncing CA audit signing certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update audit certificate and key (atomic)
        install_file "$TEMP_CERT" "$PEM_DIR/certs/ca_audit.crt" ipaca:ipaca 644
        install_file "$TEMP_KEY" "$PEM_DIR/private/ca_audit.key" ipaca:ipaca 600
        ;;

    "ocspSigningCert cert-pki-ca")
        echo "Syncing OCSP signing certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update OCSP certificate and key (atomic)
        install_file "$TEMP_CERT" "$PEM_DIR/certs/ocsp_subsystem.crt" ipaca:ipaca 644
        install_file "$TEMP_KEY" "$PEM_DIR/private/ocsp_subsystem.key" ipaca:ipaca 600
        ;;

    "ipaCert")
        echo "Syncing RA agent certificate to PEM files"
        export_cert_from_nssdb "$NICKNAME" || exit 1

        # Update RA agent certificate and key (atomic)
        install_file "$TEMP_CERT" "/var/lib/ipa/ra-agent.pem" root:ipaapi 440
        install_file "$TEMP_KEY" "/var/lib/ipa/ra-agent.key" root:ipaapi 440
        ;;

    *)
        echo "Warning: Unknown certificate nickname '$NICKNAME', skipping PEM sync" >&2
        ;;
esac

# Note: Temporary directory cleanup is handled by trap on exit

# Reload certificates in ipacta service without restart (graceful reload via SIGHUP)
if systemctl is-active --quiet ipacta.service 2>/dev/null; then
    echo "Sending SIGHUP to ipacta service for graceful certificate reload"

    # Get the main PID of ipacta service
    IPACTA_PID=$(systemctl show --property MainPID --value ipacta.service)

    if [ -n "$IPACTA_PID" ] && [ "$IPACTA_PID" != "0" ]; then
        # Validate PID is a positive integer before passing to kill
        if ! [[ "$IPACTA_PID" =~ ^[0-9]+$ ]]; then
            echo "Warning: Unexpected MainPID value '$IPACTA_PID', restarting service" >&2
            systemctl restart ipacta.service
            exit 0
        fi
        # Send SIGHUP to trigger graceful certificate reload
        if kill -HUP -- "$IPACTA_PID" 2>/dev/null; then
            echo "Certificate reload signal sent successfully (no service restart needed)"

            # Wait briefly for reload to complete
            sleep 2

            # Verify service is still running
            if systemctl is-active --quiet ipacta.service; then
                echo "Service is running, certificate reload successful"
            else
                echo "Warning: Service not running after reload, attempting restart" >&2
                systemctl start ipacta.service
            fi
        else
            echo "Warning: Failed to send SIGHUP signal, restarting service instead" >&2
            systemctl restart ipacta.service
        fi
    else
        echo "Warning: Could not get ipacta PID, restarting service instead" >&2
        systemctl restart ipacta.service
    fi
elif systemctl is-enabled --quiet ipacta.service 2>/dev/null; then
    # Service is configured but not running, start it
    echo "Starting ipacta service with renewed certificate"
    systemctl start ipacta.service
elif systemctl is-active --quiet pki-tomcatd@pki-tomcat.service 2>/dev/null; then
    # Dogtag fallback for compatibility (Dogtag requires restart, no SIGHUP support)
    echo "Restarting pki-tomcat service with renewed certificate (Dogtag mode)"
    systemctl restart pki-tomcatd@pki-tomcat.service
else
    echo "Warning: No CA service running, not reloading certificates" >&2
fi

echo "Certificate renewal completed successfully for '$NICKNAME'"
exit 0
