#!/bin/bash
set -euo pipefail

# Verification script for CVE-2026-48939 iCagenda fix.
# Starts a fresh Joomla 6 + MySQL stack, installs vulnerable iCagenda 4.0.7,
# applies the proposed patch to the live source, and confirms that a PHP
# attachment upload is rejected while a legitimate text attachment is accepted.

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

cd "$ROOT"

LOGFILE="$LOGS/verify_fix.log"
: > "$LOGFILE"
exec > >(tee -a "$LOGFILE")
exec 2>&1

info() { echo "[INFO] $(date -Iseconds) $*"; }
err()  { echo "[ERROR] $(date -Iseconds) $*"; }

on_exit() {
  local status=$?
  if [ -f "$WORKDIR/docker-compose.yml" ]; then
    sudo docker-compose -f "$WORKDIR/docker-compose.yml" down -v --remove-orphans >/dev/null 2>&1 || true
  fi
  if [ "$status" -ne 0 ]; then
    err "Verification failed. See $LOGFILE"
  fi
}
trap 'on_exit' EXIT

ensure_docker() {
  sudo docker ps >/dev/null 2>&1 || { err "Docker is not reachable via sudo"; exit 1; }
  if ! command -v docker-compose >/dev/null 2>&1; then
    info "Installing docker-compose v2.27.0"
    sudo curl -fsSL -o /usr/local/bin/docker-compose "https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64"
    sudo chmod +x /usr/local/bin/docker-compose
  fi
  docker-compose version >/dev/null
}

# Use prepared project cache if available.
CACHE_ROOT="$ROOT/artifacts/icagenda"
if [ -f "$ROOT/project_cache_context.json" ]; then
  prepared=$(jq -r '.prepared // false' "$ROOT/project_cache_context.json" 2>/dev/null || echo false)
  cache_dir=$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)
  if [ "$prepared" = "true" ] && [ -n "$cache_dir" ] && [ -d "$cache_dir" ]; then
    CACHE_ROOT="$cache_dir/repo"
  fi
fi
mkdir -p "$CACHE_ROOT"

VULN_ZIP="$CACHE_ROOT/icagenda-4-0-7-package.zip"
VULN_URL="https://www.joomlic.com/download/icagenda/icagenda-core-4-0-7/package-icagenda-core-4-0-7-zip"
JOOMLA_IMAGE="joomla:6.0-php8.3-apache"
MYSQL_IMAGE="mysql:8.0"
MYSQL_ROOT_PASSWORD="rootpass"
MYSQL_DATABASE="joomla"
MYSQL_USER="joomla"
MYSQL_PASSWORD="joomlapass"
JOOMLA_PREFIX="jos_"
HOST_IP=$(ip route show default | awk '{print $3; exit}')
PORT="18141"
WORKDIR="$REPRO_DIR/verify_stack"
PATCH_FILE="$CODING_DIR/proposed_fix.diff"

PHP_PAYLOAD="$REPRO_DIR/verify_rce.php"
TEXT_PAYLOAD="$REPRO_DIR/verify_legit.txt"

info "CVE-2026-48939 fix verification starting"
info "Using cache root: $CACHE_ROOT"
info "Host IP: $HOST_IP"

ensure_docker

# Download vulnerable package if not cached.
if [ ! -s "$VULN_ZIP" ]; then
  info "Downloading vulnerable iCagenda 4.0.7 package"
  curl -fsSL -o "$VULN_ZIP" "$VULN_URL"
fi
info "Vulnerable package: $VULN_ZIP ($(stat -c%s "$VULN_ZIP") bytes)"

# Verify patch file exists.
if [ ! -f "$PATCH_FILE" ]; then
  err "Patch file not found: $PATCH_FILE"
  exit 1
fi
info "Patch file: $PATCH_FILE"

# Write test payloads.
cat > "$PHP_PAYLOAD" <<'PHP'
<?php
header('Content-Type: text/plain');
echo "VERIFY_PHP_SHELL_SHOULD_NOT_EXECUTE\n";
if (isset($_GET['cmd'])) {
    echo shell_exec($_GET['cmd'] . ' 2>&1');
}
PHP

cat > "$TEXT_PAYLOAD" <<'TXT'
PRUVA_VERIFY_LEGIT_ATTACHMENT_OK
TXT
chmod 0644 "$PHP_PAYLOAD" "$TEXT_PAYLOAD"

# Create Docker compose stack.
rm -rf "$WORKDIR"
mkdir -p "$WORKDIR"
cat > "$WORKDIR/docker-compose.yml" <<EOF
services:
  db:
    image: ${MYSQL_IMAGE}
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 5s
      timeout: 5s
      retries: 20
  joomla:
    image: ${JOOMLA_IMAGE}
    ports:
      - "${PORT}:80"
    environment:
      JOOMLA_DB_HOST: db
      JOOMLA_DB_NAME: ${MYSQL_DATABASE}
      JOOMLA_DB_USER: ${MYSQL_USER}
      JOOMLA_DB_PASSWORD: ${MYSQL_PASSWORD}
      JOOMLA_DB_PREFIX: ${JOOMLA_PREFIX}
      JOOMLA_SITE_NAME: iCagendaVerify
      JOOMLA_ADMIN_USER: Administrator
      JOOMLA_ADMIN_USERNAME: admin
      JOOMLA_ADMIN_PASSWORD: AdminP@ssw0rd123
      JOOMLA_ADMIN_EMAIL: admin@example.com
    depends_on:
      db:
        condition: service_healthy
EOF

DC() { sudo docker-compose -f "$WORKDIR/docker-compose.yml" "$@"; }

info "Starting Joomla verification stack on port $PORT"
DC up -d

# Wait for Joomla to be healthy.
deadline=$((SECONDS + 240))
while [ $SECONDS -lt $deadline ]; do
  code=$(curl -s -o /dev/null -w '%{http_code}' "http://${HOST_IP}:${PORT}/" || true)
  if [ "$code" = "200" ]; then
    info "Joomla healthcheck passed"
    break
  fi
  sleep 5
done
if [ "${code:-}" != "200" ]; then
  err "Joomla did not become healthy"
  exit 1
fi

# Install vulnerable iCagenda.
info "Installing vulnerable iCagenda 4.0.7"
DC exec -T joomla php /var/www/html/cli/joomla.php extension:install --url="$VULN_URL"
DC exec -T joomla sh -lc 'chown -R www-data:www-data /var/www/html/images /var/www/html/components/com_icagenda /var/www/html/administrator/components/com_icagenda /var/www/html/media/com_icagenda 2>/dev/null || true'

# Apply the patch to the live installed source.
info "Applying proposed fix to installed iCagenda source"
DC cp "$PATCH_FILE" "joomla:/tmp/proposed_fix.diff"
DC exec -T joomla sh -lc 'cd /var/www/html && patch -p1 < /tmp/proposed_fix.diff'
DC exec -T joomla sh -lc 'chown www-data:www-data /var/www/html/components/com_icagenda/src/Model/SubmitModel.php'

# Verify the patch is present.
if ! DC exec -T joomla grep -q "MediaHelper" /var/www/html/components/com_icagenda/src/Model/SubmitModel.php; then
  err "Patch did not apply: MediaHelper not found in patched source"
  exit 1
fi
info "Patch applied successfully"

# Restart Apache to clear any opcache.
DC exec -T joomla sh -lc 'apache2ctl graceful || service apache2 restart || true'

# Configure iCagenda public submission form.
cat > "$WORKDIR/configure.sql" <<EOF
USE ${MYSQL_DATABASE};
INSERT INTO ${JOOMLA_PREFIX}icagenda_category (title, alias, state, color, \`desc\`, \`groups\`, language, params, note)
SELECT 'Verify Category', 'verify-category', 1, '', '', '', '*', '{}', ''
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM ${JOOMLA_PREFIX}icagenda_category WHERE state = 1 LIMIT 1);

UPDATE ${JOOMLA_PREFIX}extensions
SET params = JSON_SET(
  COALESCE(params, '{}'),
  '\$.submitAccess', JSON_ARRAY('1'),
  '\$.submit_fileDisplay', '1'
)
WHERE element = 'com_icagenda' AND type = 'component';

SET @component_id := (SELECT extension_id FROM ${JOOMLA_PREFIX}extensions WHERE element = 'com_icagenda' AND type = 'component' LIMIT 1);
SET @maxrgt := (SELECT COALESCE(MAX(rgt), 100) FROM ${JOOMLA_PREFIX}menu);
INSERT INTO ${JOOMLA_PREFIX}menu
  (menutype, title, alias, note, path, link, type, published, parent_id, level, component_id, browserNav, access, img, template_style_id, params, lft, rgt, home, language, client_id, publish_up, publish_down)
SELECT
  'mainmenu', 'Verify Submit', 'verify-submit', '', 'verify-submit', 'index.php?option=com_icagenda&view=submit', 'component', 1, 1, 1, @component_id, 0, 1, '', 0, '{"menu_show":1,"show_page_heading":"1"}', @maxrgt + 1, @maxrgt + 2, 0, '*', 0, NULL, NULL
WHERE NOT EXISTS (SELECT 1 FROM ${JOOMLA_PREFIX}menu WHERE alias = 'verify-submit' AND client_id = 0);
UPDATE ${JOOMLA_PREFIX}menu SET rgt = GREATEST(rgt, @maxrgt + 3) WHERE id = 1;
EOF
info "Configuring iCagenda public submit form"
DC exec -T db mysql -u root -p"${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" < "$WORKDIR/configure.sql"
DC exec -T joomla php /var/www/html/cli/joomla.php cache:clean >/dev/null 2>&1 || true

# Get itemid and catid.
itemid=$(DC exec -T db mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -N -B -e "SELECT id FROM ${MYSQL_DATABASE}.${JOOMLA_PREFIX}menu WHERE alias='verify-submit' AND client_id=0 LIMIT 1;" 2>/dev/null | tail -n1)
catid=$(DC exec -T db mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -N -B -e "SELECT id FROM ${MYSQL_DATABASE}.${JOOMLA_PREFIX}icagenda_category WHERE state=1 LIMIT 1;" 2>/dev/null | tail -n1)
if ! [[ "$itemid" =~ ^[0-9]+$ ]] || ! [[ "$catid" =~ ^[0-9]+$ ]]; then
  err "Could not determine itemid/catid"
  exit 1
fi
info "Menu itemid=$itemid, category catid=$catid"

# Extract CSRF token from the public submit form.
COOKIES="$REPRO_DIR/verify_cookies.txt"
FORM_OUT="$REPRO_DIR/verify_form.html"
rm -f "$COOKIES" "$FORM_OUT"
curl -s -L -c "$COOKIES" -b "$COOKIES" "http://${HOST_IP}:${PORT}/index.php?option=com_icagenda&view=submit&Itemid=${itemid}" > "$FORM_OUT"
if ! grep -q 'id="icagenda-submit"' "$FORM_OUT" || ! grep -q 'name="jform\[file\]"' "$FORM_OUT"; then
  err "Public submit form not found or missing file field"
  exit 1
fi
CSRF_TOKEN=$(grep -oE 'csrf\.token":"[0-9a-f]{32}"' "$FORM_OUT" | head -n1 | sed 's/.*"\([0-9a-f]\{32\}\)".*/\1/')
if [ -z "${CSRF_TOKEN:-}" ]; then
  CSRF_TOKEN=$(grep -oE '<input[^>]*name="[0-9a-f]{32}"[^>]*value="1"' "$FORM_OUT" | head -n1 | grep -oE 'name="[0-9a-f]{32}"' | sed 's/name="//;s/"$//')
fi
if [ -z "${CSRF_TOKEN:-}" ]; then
  err "Could not extract CSRF token"
  exit 1
fi
info "CSRF token extracted"

# Attempt 1: PHP upload should be blocked.
info "Submitting PHP attachment (should be blocked)"
UPLOAD_PHP_OUT="$REPRO_DIR/verify_upload_php.log"
rm -f "$UPLOAD_PHP_OUT"
curl --max-time 60 -s -L -i -c "$COOKIES" -b "$COOKIES" \
  -F "jform[username]=attacker" \
  -F "jform[created_by_email]=attacker@example.com" \
  -F "jform[title]=Verify RCE Test" \
  -F "jform[catid]=${catid}" \
  -F "jform[startdate]=2026-07-06 00:00:00" \
  -F "jform[enddate]=2026-07-07 00:00:00" \
  -F "jform[itemid]=${itemid}" \
  -F "jform[itemid_title]=Verify Submit" \
  -F "jform[consent][consent_tos]=tos" \
  -F "jform[file]=@${PHP_PAYLOAD};type=application/x-php" \
  -F "${CSRF_TOKEN}=1" \
  -F "task=submit.submit" \
  "http://${HOST_IP}:${PORT}/index.php?option=com_icagenda&task=submit.submit" > "$UPLOAD_PHP_OUT" 2>&1 || true

# List files in attachments directory.
DC exec -T joomla sh -lc 'if [ -d /var/www/html/images/icagenda/frontend/attachments ]; then find /var/www/html/images/icagenda/frontend/attachments -maxdepth 1 -type f -printf "%f %s bytes\n" | sort; else echo "attachments directory absent"; fi' > "$REPRO_DIR/verify_files_php.txt" 2>&1 || true
info "Files after PHP upload attempt:"
cat "$REPRO_DIR/verify_files_php.txt"

if grep -qE "verify[_-]?rce.*\.php" "$REPRO_DIR/verify_files_php.txt"; then
  err "PHP payload was stored on disk - fix ineffective"
  exit 1
fi

FETCH_PHP_OUT="$REPRO_DIR/verify_fetch_php.log"
rm -f "$FETCH_PHP_OUT"
PHP_URL="http://${HOST_IP}:${PORT}/images/icagenda/frontend/attachments/verify-rce.php?cmd=id"
fetch_code=$(curl --max-time 30 -s -i -o "$FETCH_PHP_OUT" -w '%{http_code}' "$PHP_URL" || true)
info "PHP shell fetch returned HTTP $fetch_code"
if [ "$fetch_code" = "200" ] || grep -q "VERIFY_PHP_SHELL_SHOULD_NOT_EXECUTE" "$FETCH_PHP_OUT"; then
  err "PHP shell executed - fix ineffective"
  exit 1
fi

info "PHP upload was blocked as expected"

# Attempt 2: legitimate text upload should succeed.
info "Submitting legitimate text attachment (should succeed)"
UPLOAD_TEXT_OUT="$REPRO_DIR/verify_upload_text.log"
rm -f "$COOKIES" "$UPLOAD_TEXT_OUT"
curl -s -L -c "$COOKIES" -b "$COOKIES" "http://${HOST_IP}:${PORT}/index.php?option=com_icagenda&view=submit&Itemid=${itemid}" > "$FORM_OUT" 2>&1
CSRF_TOKEN=$(grep -oE 'csrf\.token":"[0-9a-f]{32}"' "$FORM_OUT" | head -n1 | sed 's/.*"\([0-9a-f]\{32\}\)".*/\1/')
if [ -z "${CSRF_TOKEN:-}" ]; then
  CSRF_TOKEN=$(grep -oE '<input[^>]*name="[0-9a-f]{32}"[^>]*value="1"' "$FORM_OUT" | head -n1 | grep -oE 'name="[0-9a-f]{32}"' | sed 's/name="//;s/"$//')
fi

curl --max-time 60 -s -L -i -c "$COOKIES" -b "$COOKIES" \
  -F "jform[username]=legituser" \
  -F "jform[created_by_email]=legit@example.com" \
  -F "jform[title]=Verify Legit Test" \
  -F "jform[catid]=${catid}" \
  -F "jform[startdate]=2026-07-06 00:00:00" \
  -F "jform[enddate]=2026-07-07 00:00:00" \
  -F "jform[itemid]=${itemid}" \
  -F "jform[itemid_title]=Verify Submit" \
  -F "jform[consent][consent_tos]=tos" \
  -F "jform[file]=@${TEXT_PAYLOAD};type=text/plain" \
  -F "${CSRF_TOKEN}=1" \
  -F "task=submit.submit" \
  "http://${HOST_IP}:${PORT}/index.php?option=com_icagenda&task=submit.submit" > "$UPLOAD_TEXT_OUT" 2>&1 || true

DC exec -T joomla sh -lc 'if [ -d /var/www/html/images/icagenda/frontend/attachments ]; then find /var/www/html/images/icagenda/frontend/attachments -maxdepth 1 -type f -printf "%f %s bytes\n" | sort; else echo "attachments directory absent"; fi' > "$REPRO_DIR/verify_files_text.txt" 2>&1 || true
info "Files after text upload attempt:"
cat "$REPRO_DIR/verify_files_text.txt"

if ! grep -qE "verify[_-]?legit.*\.txt" "$REPRO_DIR/verify_files_text.txt"; then
  err "Legitimate text attachment was not stored - fix is too restrictive"
  exit 1
fi

# Find the stored filename and fetch it.
legit_file=$(grep -oE "verify[_-]?legit.*\.txt" "$REPRO_DIR/verify_files_text.txt" | head -n1)
LEGIT_URL="http://${HOST_IP}:${PORT}/images/icagenda/frontend/attachments/${legit_file}"
FETCH_TEXT_OUT="$REPRO_DIR/verify_fetch_text.log"
rm -f "$FETCH_TEXT_OUT"
fetch_code=$(curl --max-time 30 -s -i -o "$FETCH_TEXT_OUT" -w '%{http_code}' "$LEGIT_URL" || true)
info "Legit attachment fetch returned HTTP $fetch_code"
if [ "$fetch_code" != "200" ] || ! grep -q "PRUVA_VERIFY_LEGIT_ATTACHMENT_OK" "$FETCH_TEXT_OUT"; then
  err "Legitimate text attachment could not be retrieved or content mismatch"
  exit 1
fi

info "Legitimate text attachment uploaded and retrieved successfully"

info "CVE-2026-48939 fix verification PASSED: PHP upload blocked, legitimate upload works"
exit 0
