#!/usr/bin/env bash
#
# whm-canary.sh — canário dos includes do Apache do WHM (spec 16 / UPDATE_INFRA I3.T5).
#
# Duas provas, ambas antes de tocar o host:
#   1. RENDER: substitui <acct>/<domain> e grava os dois arquivos finais;
#   2. CONFIGTEST: valida os includes com um Apache 2.4 real (mesmos directivas/contextos),
#      pegando o que o grep não pega — comentário inline, directiva fora de contexto, etc.
# E, já no host, os testes HTTP que só o ambiente real responde (HSTS, TRACE, versão do
# servidor, `security.txt` chegando ao app, deny efetivo).
#
# Uso:
#   tools/whm-canary.sh --render <acct> <domain> [--dest DIR]
#   tools/whm-canary.sh --config-test [--dest DIR]
#   tools/whm-canary.sh --url https://portal.exemplo.gov.br [--require-deny]
#   tools/whm-canary.sh --self-test          # render + configtest + controle negativo
#
# Rodar no host do WHM (tem `httpd`), não no container. Sem `httpd`, o `--config-test`
# sai com código 2 (SKIP explícito) e o `--self-test` só valida o render.
set -Eeuo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"

DEST="$ROOT/build/whm"
ACCT=""
DOMAIN=""
DO_RENDER=0
DO_CONFIG=0
DO_SELF=0
URL=""
REQUIRE_DENY=0

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

while [ $# -gt 0 ]; do
    case "$1" in
        --render) DO_RENDER=1; shift; ACCT="${1:-}"; shift; DOMAIN="${1:-}";;
        --dest) shift; DEST="${1:-}";;
        --dest=*) DEST="${1#*=}";;
        --config-test) DO_CONFIG=1;;
        --url) shift; URL="${1:-}";;
        --url=*) URL="${1#*=}";;
        --require-deny) REQUIRE_DENY=1;;
        --self-test) DO_SELF=1;;
        -h | --help) usage; exit 0;;
        *) echo "flag desconhecida: $1" >&2; usage >&2; exit 2;;
    esac
    shift
done

die() { echo "whm-canary: $*" >&2; exit 1; }

# placeholder → valor real. O app.conf não tem outros `<...>`; se tiver, falha alto.
render() { # <origem> <destino> <acct> <domain>
    sed -e "s#<acct>#$3#g" -e "s#<domain>#$4#g" "$1" > "$2"
    if grep -nE '<[a-z_]+>' "$2" >/dev/null 2>&1; then
        echo "whm-canary: placeholder não substituído em $2:" >&2
        grep -nE '<[a-z_]+>' "$2" >&2
        return 1
    fi
}

do_render() {
    [ -n "$ACCT" ] && [ -n "$DOMAIN" ] || die "--render exige <acct> <domain>."
    case "$ACCT$DOMAIN" in
        *[\ /]*) die "acct/domain não podem conter espaço ou barra.";;
    esac
    mkdir -p "$DEST"
    render "$ROOT/docker/web/app.conf" "$DEST/app.conf" "$ACCT" "$DOMAIN"
    render "$ROOT/docker/web/app-ssl.conf" "$DEST/app-ssl.conf" "$ACCT" "$DOMAIN"
    echo "whm-canary: renderizado em $DEST (app.conf → std, app.conf+app-ssl.conf → ssl)"
}

# Apache descartável: mesma árvore de directivas do vhost, sem porta pública nem TLS.
config_test() {
    command -v httpd >/dev/null 2>&1 || { echo "whm-canary: SKIP — sem 'httpd' no PATH (rode no host do WHM)." >&2; return 2; }
    [ -f "$DEST/app.conf" ] && [ -f "$DEST/app-ssl.conf" ] || die "rode --render primeiro (ou --self-test)."

    local t m out rc=0
    t="$(mktemp -d)"
    m="$(httpd -V 2>/dev/null | sed -n 's/^ *-D HTTPD_ROOT="\(.*\)"/\1/p')"
    [ -n "$m" ] || m="/usr/lib64/httpd"
    m="$m/modules"

    mkdir -p "$t/public_html/.well-known/acme-challenge" "$t/public_html/.well-known/pki-validation" "$t/logs"
    cat > "$t/httpd.conf" <<EOF
ServerRoot "$t"
Listen 127.0.0.1:18089
LoadModule mpm_event_module $m/mod_mpm_event.so
LoadModule unixd_module $m/mod_unixd.so
LoadModule authz_core_module $m/mod_authz_core.so
LoadModule log_config_module $m/mod_log_config.so
LoadModule proxy_module $m/mod_proxy.so
LoadModule proxy_http_module $m/mod_proxy_http.so
LoadModule headers_module $m/mod_headers.so
LoadModule remoteip_module $m/mod_remoteip.so
LoadModule reqtimeout_module $m/mod_reqtimeout.so
User apache
Group apache
ErrorLog $t/logs/error.log
PidFile $t/httpd.pid
DocumentRoot "$t/public_html"
<VirtualHost *:18089>
    ServerName canary.local
    Include "$DEST/app.conf"
    Include "$DEST/app-ssl.conf"
</VirtualHost>
EOF

    local out
    if out="$(httpd -t -f "$t/httpd.conf" 2>&1)"; then
        rm -rf "$t"
        echo "whm-canary: configtest OK ($DEST)"
        return 0
    fi
    printf '%s\n' "$out" >&2
    rm -rf "$t"
    echo "whm-canary: configtest FALHOU — o Apache do WHM recusaria os includes." >&2
    return 1
}

# ── Host real ────────────────────────────────────────────────────────────────
probe() {
    [ -n "$URL" ] || die "--url ausente."
    local base="${URL%/}" fail=0 code hdr

    code() { curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "$@" 2>/dev/null || true; }
    hdrs() { curl -sS -D - -o /dev/null --max-time 20 "$@" 2>/dev/null || true; }
    one() { # <status> <rótulo> <detalhe>
        printf '%-34s %-5s %s\n' "$2" "$1" "$3"
        [ "$1" = OK ] || fail=1
    }

    echo "==> canário: $base"
    printf '%-34s %-5s %s\n' CAMINHO STATUS OBSERVAÇÃO

    code "$base/" | grep -qx 200 && one OK '/ (home)' 200 || one FAIL '/ (home)' "HTTP $(code "$base/")"

    hdr="$(hdrs "$base/")"
    if printf '%s' "$hdr" | grep -qiE '^server:[[:space:]]*apache/[0-9]'; then
        one FAIL '#server' "$(printf '%s' "$hdr" | grep -i '^server:' | tr -d '\r')"
    else
        one OK '#server' 'sem versão do Apache'
    fi

    code "$base/.well-known/security.txt" | grep -qx 200 \
        && one OK '/.well-known/security.txt' 200 \
        || one FAIL '/.well-known/security.txt' "HTTP $(code "$base/.well-known/security.txt") (proxy estreito quebrou o RFC 9116?)"

    local p
    for p in '/.env' '/vendor/autoload.php' '/composer.json'; do
        c="$(code "$base$p")"
        if [ "$REQUIRE_DENY" = 1 ]; then
            [ "$c" = 403 ] && one OK "$p" "$c" || one FAIL "$p" "HTTP $c (esperado 403 do Apache)"
        else
            case "$c" in 403|404) one OK "$p" "$c";; *) one FAIL "$p" "HTTP $c";; esac
        fi
    done

    c="$(code -X TRACE "$base/")"
    case "$c" in 403|405) one OK '#trace' "$c";; *) one FAIL '#trace' "HTTP $c";; esac

    hdr="$(hdrs -H 'Accept-Encoding: gzip' "$base/")"
    if [ "$(printf '%s' "$hdr" | tr -d '\r' | grep -ci '^content-encoding:')" -gt 1 ]; then
        one FAIL '#compress' 'Content-Encoding duplicado (dois donos)'
    else
        one OK '#compress' 'um dono'
    fi

    case "$base" in
        https://*)
            if printf '%s' "$(hdrs "$base/")" | grep -qi '^strict-transport-security:.*max-age=31536000.*includeSubDomains'; then
                one OK '#hsts' 'max-age + includeSubDomains'
            else
                one FAIL '#hsts' 'ausente/incompleto'
            fi
            if printf '%s' "$(hdrs "$base/")" | grep -qi '^strict-transport-security:.*preload'; then
                one FAIL '#hsts-preload' 'preload na 1ª janela (só após todos os subdomínios HTTPS)'
            else
                one OK '#hsts-preload' 'ausente'
            fi
            ;;
        *)
            one OK '#hsts' 'SKIP (use URL https para checar)'
            ;;
    esac

    [ "$fail" -eq 0 ] || { echo "==> canário FALHOU" >&2; return 1; }
    echo "==> canário OK"
}

self_test() {
    ACCT="${ACCT:-canary}"
    DOMAIN="${DOMAIN:-canary.local}"
    do_render
    local rc=0
    config_test || rc=$?
    if [ "$rc" -eq 1 ]; then
        echo "whm-canary: self-test FALHOU no configtest." >&2
        return 1
    fi
    # Controle negativo: uma directiva de contexto de servidor dentro do vhost TEM de falhar.
    if command -v httpd >/dev/null 2>&1; then
        local backup="$DEST/app.conf"
        cp "$backup" "$backup.bak"
        printf 'ServerTokens Prod\n' >> "$backup"
        if config_test >/dev/null 2>&1; then
            mv -f "$backup.bak" "$backup"
            echo "whm-canary: self-test FALHOU — configtest aceitou ServerTokens no vhost." >&2
            return 1
        fi
        mv -f "$backup.bak" "$backup"
        echo "whm-canary: controle negativo OK (ServerTokens no vhost é recusado)."
    else
        echo "whm-canary: SKIP configtest (sem httpd); render validado."
    fi
}

[ "$DO_SELF" = 1 ] && { self_test; exit $?; }
[ "$DO_RENDER" = 1 ] && do_render
[ "$DO_CONFIG" = 1 ] && { config_test; exit $?; }
[ -n "$URL" ] && { probe; exit $?; }

[ "$DO_RENDER" = 0 ] && [ "$DO_CONFIG" = 0 ] && [ -z "$URL" ] && { usage >&2; exit 2; }
exit 0
