#!/usr/bin/env bash
# check-zoom.sh: strictly read-only, offline version check for Zoom Workplace for Windows CVE-2026-108186 (ZSB-26023).
# Zoom (the CNA) scores it 9.6 Critical, CVSS 3.1 AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H.
# It only compares the version strings you pass or a CSV you saved (for example an RMM or Intune export).
# No network, no file writes, no payloads, never contacts Zoom or an endpoint.
#
# Usage:
#   check-zoom.sh --version VER [--platform PLATFORM] [--scope user|machine] [--name NAME]
#   check-zoom.sh --inventory FILE      (CSV header: name,platform,version,scope; scope may be blank)
# Zoom's range (ZSB-26023, 10 Nov 2026): "Zoom Workplace for Windows starting on version 7.1.0 and before version 7.1.8".
#   7.1.0 to 7.1.7 on Windows: VULNERABLE. 7.1.8 or later on Windows: PATCHED.
#   Below 7.1.0 on Windows: OUT OF RANGE (not in Zoom's range for this CVE, but still old; update to 7.1.8 or later).
#   Any platform other than Windows, or a version this script cannot read: UNKNOWN (never PATCHED).
# Exit: 0 PATCHED, 1 VULNERABLE, 2 OUT OF RANGE (none vulnerable), 3 UNKNOWN (none vulnerable or out of range), 4 usage or read error.
set -u

usage() {
  cat >&2 <<'U'
Usage: check-zoom.sh --version VER [--platform PLATFORM] [--scope user|machine] [--name NAME]
       check-zoom.sh --inventory FILE
  --version    Zoom Workplace version such as 7.1.8, 7.1.8.12345 or "7.1.8 (12345)" (quote it)
  --platform   windows (default), or anything else, which is reported UNKNOWN
  --scope      user (EXE install under %AppData%\Zoom) or machine (MSI under Program Files); optional
  --name       label for the output (default: input)
  --inventory  CSV: name,platform,version,scope
Exit: 0 PATCHED, 1 VULNERABLE, 2 OUT OF RANGE, 3 UNKNOWN, 4 usage or read error
U
  exit 4
}
die() { echo "ERROR: $1" >&2; exit 4; }
lc() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
trim() { local s=$1; s="${s#"${s%%[![:space:]]*}"}"; s="${s%"${s##*[![:space:]]}"}"; printf '%s' "$s"; }

norm_platform() {
  local p; p=$(lc "$(trim "$1")")
  case $p in
    windows|win|win32|win64|"windows 10"|"windows 11"|"microsoft windows") echo windows;;
    *) echo "$p";;
  esac
}
norm_scope() {
  local s; s=$(lc "$(trim "$1")")
  case $s in
    user|per-user|peruser|exe) echo user;;
    machine|machine-wide|system|all-users|msi) echo machine;;
    *) echo "$s";;
  esac
}

say() { # verdict where reason
  printf '%s: %s: %s\n' "$1" "$2" "$3"
}

judge() { # name platform version scope
  local n=$1 p v s w a b c note=""
  p=$(norm_platform "$2"); v=$(trim "$3"); s=$(norm_scope "$4")
  w="$n (${p:-no platform}, ${v:-no version}${s:+, $s})"
  if [ -z "$v" ]; then say UNKNOWN "$w" "no version given."; return; fi
  if [ -z "$p" ]; then say UNKNOWN "$w" "no platform given. ZSB-26023 lists only Zoom Workplace for Windows."; return; fi
  if [ "$p" != windows ]; then say UNKNOWN "$w" "ZSB-26023 lists only Zoom Workplace for Windows. This pack does not judge other platforms or products."; return; fi
  if ! [[ $v =~ ^[vV]?([0-9]{1,4})\.([0-9]{1,4})\.([0-9]{1,6})(\.[0-9]{1,8})?(\ \([0-9]{1,8}\))?$ ]]; then
    say UNKNOWN "$w" "not a Zoom Workplace version this pack can judge (use the form 7.1.8 or 7.1.8.12345)."; return
  fi
  a=$((10#${BASH_REMATCH[1]})); b=$((10#${BASH_REMATCH[2]})); c=$((10#${BASH_REMATCH[3]}))
  case $s in
    user) note=' Per-user install under %AppData%\Zoom: machine-wide patching often misses it, so update it in that user profile.';;
    machine) note=' Machine-wide (MSI) install: Zoom says AutoUpdate is off by default for MSI installs, so push the update.';;
  esac
  if [ "$a" -gt 7 ] || { [ "$a" -eq 7 ] && { [ "$b" -gt 1 ] || { [ "$b" -eq 1 ] && [ "$c" -ge 8 ]; }; }; }; then
    say PATCHED "$w" "at or above 7.1.8, outside Zoom's affected range for CVE-2026-108186."; return
  fi
  if [ "$a" -eq 7 ] && [ "$b" -eq 1 ]; then
    say VULNERABLE "$w" "in Zoom's affected range 7.1.0 to before 7.1.8 for CVE-2026-108186. Update to 7.1.8 or later from https://zoom.us/download.$note"; return
  fi
  say "OUT OF RANGE" "$w" "below 7.1.0, where Zoom's ZSB-26023 range starts, so not listed for CVE-2026-108186. Zoom's separate ZSB-26024 (CVE-2026-108187, High) lists Windows before 7.1.5. Update to 7.1.8 or later.$note"
}

read_inventory() {
  local f=$1 h line n p v s rows=0
  if [ ! -f "$f" ] || [ ! -r "$f" ]; then die "cannot read inventory file: $f"; fi
  IFS= read -r h < "$f" || die "inventory file is empty: $f"
  h=${h%$'\r'}
  [ "$(lc "$(trim "$h")")" = "name,platform,version,scope" ] || die "CSV header must be: name,platform,version,scope"
  while IFS= read -r line || [ -n "$line" ]; do
    line=${line%$'\r'}
    case $(trim "$line") in ''|\#*) continue;; esac
    IFS=, read -r n p v s _ <<< "$line"
    n=$(trim "${n:-}")
    judge "${n:-name not given}" "${p:-}" "${v:-}" "${s:-}"
    rows=$((rows+1))
  done < <(tail -n +2 "$f")
  [ "$rows" -gt 0 ] || die "inventory has no data rows: $f"
}

ver="" plat="windows" scope="" name="input" inv="" seen_ver=0 seen_other=0
while [ $# -gt 0 ]; do
  [ $# -ge 2 ] || usage
  case $1 in
    --version) ver=$2; seen_ver=1;;
    --platform) plat=$2; seen_other=1;;
    --scope) scope=$2; seen_other=1;;
    --name) name=$2; seen_other=1;;
    --inventory) inv=$2;;
    *) usage;;
  esac
  shift 2
done
if [ -n "$inv" ] && [ $((seen_ver + seen_other)) -gt 0 ]; then usage; fi
if [ -z "$inv" ] && [ "$seen_ver" -eq 0 ]; then usage; fi

# Read the inventory fully before printing anything, so read errors leave stdout empty.
if [ -n "$inv" ]; then
  out=$(read_inventory "$inv") || exit 4
else
  out=$(judge "$name" "$plat" "$ver" "$scope")
fi
NP=$(printf '%s\n' "$out" | grep -c '^PATCHED: ')
NV=$(printf '%s\n' "$out" | grep -c '^VULNERABLE: ')
NO=$(printf '%s\n' "$out" | grep -c '^OUT OF RANGE: ')
NU=$(printf '%s\n' "$out" | grep -c '^UNKNOWN: ')
N=$((NP+NV+NO+NU))

echo "check-zoom: offline version check for Zoom Workplace for Windows CVE-2026-108186 (ZSB-26023 read 10 Nov 2026 18:08 GMT)."
echo "KEV: CVE-2026-108186 not listed in CISA KEV catalog 2026.11.09, which predates disclosure. Patch now regardless."
printf '%s\n' "$out"
if [ "$NV" -gt 0 ]; then res=VULNERABLE; rc=1
elif [ "$NO" -gt 0 ]; then res="OUT OF RANGE"; rc=2
elif [ "$NU" -gt 0 ]; then res=UNKNOWN; rc=3
else res=PATCHED; rc=0; fi
echo "RESULT: $res ($N checked: $NP PATCHED, $NV VULNERABLE, $NO OUT OF RANGE, $NU UNKNOWN)"
exit "$rc"
