#!/bin/bash
set -uo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CODING_DIR="$ROOT/coding"
PATCH="$CODING_DIR/proposed_fix.diff"
LOG_DIR="$CODING_DIR/logs"
ENV_DIR="$CODING_DIR/verify_env"
mkdir -p "$LOG_DIR" "$ENV_DIR"
chmod 755 "$ROOT" "$CODING_DIR" "$LOG_DIR" "$ENV_DIR" 2>/dev/null || true

# Pull the project cache repo path from the context if available.
CACHE_DIR="/data/pruva/project-cache/e16fa440-7670-4503-8601-378cf2096f7e"
if [ -f "$ROOT/project_cache_context.json" ]; then
    CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
fi
REPO="${CACHE_DIR}/repo"
[ -d "$REPO/.git" ] || { echo "Project cache repo not found: $REPO"; exit 1; }
[ -f "$PATCH" ] || { echo "Patch file not found: $PATCH"; exit 1; }

cd "$REPO"

# Make sure we start from a clean vulnerable tree at v1.3.9b, then apply the fix.
git checkout -f v1.3.9b || { echo "git checkout failed"; exit 1; }
git apply "$PATCH" || { echo "Failed to apply patch"; exit 1; }

# Build a patched binary. Re-use the existing vulnerable build directory as a
# template so we only need to recompile the changed file and relink.
BUILD_DIR="$REPO/build-fix"
if [ -d "$BUILD_DIR" ]; then
    make -C "$BUILD_DIR" -j$(nproc) > "$LOG_DIR/proftpd_build.log" 2>&1 || { echo "Build failed"; exit 1; }
else
    VULN_BUILD="$REPO/build-vuln"
    if [ -d "$VULN_BUILD" ]; then
        cp -a "$VULN_BUILD" "$BUILD_DIR"
        make -C "$BUILD_DIR" -j$(nproc) > "$LOG_DIR/proftpd_build.log" 2>&1 || { echo "Build failed"; exit 1; }
    else
        mkdir -p "$BUILD_DIR"
        cd "$BUILD_DIR"
        "$REPO/configure" --disable-auth-pam --enable-ipv6 \
            --without-getopt --without-howl --without-geoip --without-ldap \
            --without-mysql --without-pgsql --without-sqlite --without-redis \
            --without-memcached --without-sodium --without-krb5 --without-ident \
            --without-oath --without-jwt --without-curl --without-maxminddb \
            --without-exif --without-libjpeg --without-libpng --without-libtiff \
            --without-libwebp --without-libmagic > "$LOG_DIR/proftpd_configure.log" 2>&1 || { echo "configure failed"; exit 1; }
        make -C "$BUILD_DIR" -j$(nproc) > "$LOG_DIR/proftpd_build.log" 2>&1 || { echo "Build failed"; exit 1; }
    fi
fi

PROFTPD="$BUILD_DIR/proftpd"
[ -x "$PROFTPD" ] || { echo "Patched proftpd binary not found at $PROFTPD"; exit 1; }

# ---------------------------------------------------------------------------
# Runtime verification: start patched server and try the known bypass vectors.
# ---------------------------------------------------------------------------

cd "$ENV_DIR"

cleanup() {
    if [ -n "${PROFTPD_PID:-}" ] && kill -0 "$PROFTPD_PID" 2>/dev/null; then
        kill "$PROFTPD_PID" 2>/dev/null || true
    fi
    if [ -n "${PID_FILE:-}" ] && [ -f "$PID_FILE" ]; then
        kill "$(cat "$PID_FILE")" 2>/dev/null || true
    fi
}

trap cleanup EXIT

CURRENT_USER="$(id -un)"
CURRENT_GROUP="$(id -gn)"
CURRENT_UID="$(id -u)"
CURRENT_GID="$(id -g)"

TEST_ROOT="$ENV_DIR/ftp-root"
rm -rf "$TEST_ROOT"
mkdir -p "$TEST_ROOT/protected" "$TEST_ROOT/public"
echo "protected secret" > "$TEST_ROOT/protected/secret.txt"
echo "protected secret2" > "$TEST_ROOT/protected/secret2.txt"
echo "public content" > "$TEST_ROOT/public/file.txt"
echo "public delete-me" > "$TEST_ROOT/public/file2.txt"
chmod 755 "$TEST_ROOT/protected" "$TEST_ROOT/public"
chmod 644 "$TEST_ROOT/protected/"* "$TEST_ROOT/public/"*

PASSWD_FILE="$ENV_DIR/proftpd.passwd"
GROUP_FILE="$ENV_DIR/proftpd.group"
HASH="$(openssl passwd -6 testpass)"
printf 'testuser:%s:%s:%s:testuser:%s:/bin/false\n' "$HASH" "$CURRENT_UID" "$CURRENT_GID" "$TEST_ROOT" > "$PASSWD_FILE"
printf 'testgroup:x:%s:testuser\n' "$CURRENT_GID" > "$GROUP_FILE"
chmod 600 "$PASSWD_FILE" "$GROUP_FILE"

PORT=2123
PID_FILE="$ENV_DIR/proftpd.pid"
SCOREBOARD="$ENV_DIR/proftpd.scoreboard"

cat > "$ENV_DIR/proftpd.conf" <<EOF
ServerName "ProFTPD-Fix-Verify"
ServerType standalone
DefaultServer on
Port $PORT
User $CURRENT_USER
Group $CURRENT_GROUP

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

rm -f "$PID_FILE"
"$PROFTPD" -c "$ENV_DIR/proftpd.conf" -d 10 > "$LOG_DIR/proftpd_verify.log" 2>&1 &
PROFTPD_PID=$!

for i in $(seq 1 30); do
    if nc -z localhost "$PORT" 2>/dev/null; then
        echo "ProFTPD ready on port $PORT"
        break
    fi
    if ! kill -0 "$PROFTPD_PID" 2>/dev/null; then
        echo "ProFTPD exited prematurely"
        tail -n 50 "$LOG_DIR/proftpd_verify.log"
        exit 1
    fi
    sleep 1
done
nc -z localhost "$PORT" 2>/dev/null || { echo "ProFTPD did not start"; exit 1; }

export TEST_ROOT PORT
python3 - <<'PYEOF'
import ftplib
import os
import sys

host = "localhost"
port = int(os.environ["PORT"])
test_root = os.environ["TEST_ROOT"]

results = {}

def expect_denied(desc, fn):
    try:
        fn()
        print(f"{desc}: ALLOWED (FAIL)")
        return False
    except ftplib.error_perm as e:
        code = str(e).split()[0]
        print(f"{desc}: DENIED {code}")
        return True

print("=== Connecting to patched ProFTPD ===")
ftp = ftplib.FTP()
ftp.connect(host, port)
ftp.login("testuser", "testpass")
print("Logged in")

ok = True
ok &= expect_denied("Direct RNFR protected", lambda: ftp.sendcmd("RNFR protected/secret.txt"))
ok &= expect_denied("/proc/self/root RNFR protected", lambda: ftp.sendcmd("RNFR /proc/self/root" + os.path.join(test_root, "protected", "secret.txt")))
ok &= expect_denied("Direct DELE protected", lambda: ftp.delete("protected/secret2.txt"))
ok &= expect_denied("/proc/self/root DELE protected", lambda: ftp.sendcmd("DELE /proc/self/root" + os.path.join(test_root, "protected", "secret.txt")))

print("\n=== Regression checks for normal operations ===")
try:
    ftp.sendcmd("RNFR public/file.txt")
    ftp.sendcmd("RNTO public/file_renamed.txt")
    print("Normal RNFR/RNTO: OK")
except Exception as e:
    print(f"Normal RNFR/RNTO: FAIL {e}")
    ok = False

try:
    ftp.delete("public/file2.txt")
    print("Normal DELE: OK")
except Exception as e:
    print(f"Normal DELE: FAIL {e}")
    ok = False

ftp.quit()

if ok:
    print("\nSUCCESS: all bypass attempts are blocked and normal operations still work.")
    sys.exit(0)
else:
    print("\nFAILURE: the fix did not block all bypass attempts.")
    sys.exit(1)
PYEOF

echo ""
echo "=== ProFTPD log tail ==="
tail -n 30 "$LOG_DIR/proftpd_verify.log" || true
