/* udns_resolver.c
   resolver stuff (main module)

   Copyright (C) 2005  Michael Tokarev <mjt@corpit.ru>
   This file is part of UDNS library, an async DNS stub resolver.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library, in file named COPYING.LGPL; if not,
   write to the Free Software Foundation, Inc., 59 Temple Place,
   Suite 330, Boston, MA  02111-1307  USA

 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if defined(_WINDOWS) || defined(WINDOWS)
# include <winsock2.h>          /* includes <windows.h> */
# include <ws2tcpip.h>          /* needed for struct in6_addr */
#else
# include <sys/types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <unistd.h>
# include <fcntl.h>
# include <sys/time.h>
# ifdef HAVE_POLL
#  include <sys/poll.h>
# else
#  ifdef HAVE_SYS_SELECT_H
#   include <sys/select.h>
#  endif
# endif
# ifdef HAVE_TIMES
#  include <sys/times.h>
# endif
# define closesocket(sock) close(sock)
#endif	/* !WINDOWS */

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <stddef.h>
#include "udns.h"

#ifndef EAFNOSUPPORT
# define EAFNOSUPPORT EINVAL
#endif
#ifndef MSG_DONTWAIT
# define MSG_DONTWAIT 0
#endif

struct dns_qlist {
  struct dns_query *head, *tail;
};

struct dns_query {
  struct dns_query *dnsq_next;          /* double-linked list */
  struct dns_query *dnsq_prev;
  unsigned dnsq_origdnl0;		/* original query DN len w/o last 0 */
  unsigned dnsq_flags;			/* control flags for this query */
  unsigned dnsq_servi;			/* index of next server to try */
  unsigned dnsq_servwait;		/* bitmask: servers left to wait */
  unsigned dnsq_servskip;		/* bitmask: servers to skip */
  unsigned dnsq_servnEDNS0;		/* bitmask: servers refusing EDNS0 */
  unsigned dnsq_try;			/* number of tries made so far */
  dnscc_t *dnsq_nxtsrch;		/* next search pointer @dnsc_srchbuf */
  time_t dnsq_deadline;			/* when current try will expire */
  dns_parse_fn *dnsq_parse;		/* parse: raw => application */
  dns_query_fn *dnsq_cbck;		/* the callback to call when done */
  void *dnsq_cbdata;			/* user data for the callback */
#ifndef NDEBUG
  struct dns_ctx *dnsq_ctx;		/* the resolver context */
#endif
  /* char fields at the end to avoid padding */
  dnsc_t dnsq_id[2];			/* query ID */
  dnsc_t dnsq_typcls[4];		/* requested RR type+class */
  dnsc_t dnsq_dn[DNS_MAXDN+DNS_DNPAD];	/* the query DN +alignment */
};

/* working with dns_query lists */

static __inline void qlist_init(struct dns_qlist *list) {
  list->head = list->tail = NULL;
}

static __inline void qlist_remove(struct dns_qlist *list, struct dns_query *q) {
   if (q->dnsq_prev) q->dnsq_prev->dnsq_next = q->dnsq_next;
   else list->head = q->dnsq_next;
   if (q->dnsq_next) q->dnsq_next->dnsq_prev = q->dnsq_prev;
   else list->tail = q->dnsq_prev;
}

static __inline void
qlist_add_head(struct dns_qlist *list, struct dns_query *q) {
  q->dnsq_next = list->head;
  if (list->head) list->head->dnsq_prev = q;
  else list->tail = q;
  list->head = q;
  q->dnsq_prev = NULL;
}

static __inline void
qlist_insert_after(struct dns_qlist *list,
                   struct dns_query *q, struct dns_query *prev) {
  if ((q->dnsq_prev = prev) != NULL) {
    if ((q->dnsq_next = prev->dnsq_next) != NULL)
      q->dnsq_next->dnsq_prev = q;
    else
      list->tail = q;
    prev->dnsq_next = q;
  }
  else
    qlist_add_head(list, q);
}

union sockaddr_ns {
  struct sockaddr sa;
  struct sockaddr_in sin;
#ifdef HAVE_IPv6
  struct sockaddr_in6 sin6;
#endif
};

#define sin_eq(a,b) \
	((a).sin_port == (b).sin_port && \
	 (a).sin_addr.s_addr == (b).sin_addr.s_addr)
#define sin6_eq(a,b) \
	((a).sin6_port == (b).sin6_port && \
	 memcmp(&(a).sin6_addr, &(b).sin6_addr, sizeof(struct in6_addr)) == 0)

struct dns_ctx {		/* resolver context */
  /* settings */
  unsigned dnsc_flags;			/* various flags */
  unsigned dnsc_timeout;		/* timeout (base value) for queries */
  unsigned dnsc_ntries;			/* number of retries */
  unsigned dnsc_ndots;			/* ndots to assume absolute name */
  unsigned dnsc_port;			/* default port (DNS_PORT) */
  unsigned dnsc_udpbuf;			/* size of UDP buffer */
  /* array of nameserver addresses */
  union sockaddr_ns dnsc_serv[DNS_MAXSERV];
  unsigned dnsc_nserv;			/* number of nameservers */
  unsigned dnsc_salen;			/* length of socket addresses */
  dnsc_t dnsc_srchbuf[1024];		/* buffer for searchlist */
  dnsc_t *dnsc_srchend;			/* current end of srchbuf */

  dns_utm_fn *dnsc_utmfn;		/* register/cancel timer events */
  void *dnsc_utmctx;			/* user timer context for utmfn() */
  time_t dnsc_utmexp;			/* when user timer expires */

  dns_dbgfn *dnsc_udbgfn;		/* debugging function */

  /* dynamic data */
  struct udns_jranctx dnsc_jran;	/* random number generator state */
  unsigned dnsc_nextid;			/* next queue ID to use if !0 */
  int dnsc_udpsock;			/* UDP socket */
  struct dns_qlist dnsc_qactive;	/* active list sorted by deadline */
  int dnsc_nactive;			/* number entries in dnsc_qactive */
  dnsc_t *dnsc_pbuf;			/* packet buffer (udpbuf size) */
  int dnsc_qstatus;			/* last query status value */
};

static const struct {
  const char *name;
  enum dns_opt opt;
  unsigned offset;
  unsigned min, max;
} dns_opts[] = {
#define opt(name,opt,field,min,max) \
	{name,opt,offsetof(struct dns_ctx,field),min,max}
  opt("retrans", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300),
  opt("timeout", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300),
  opt("retry",    DNS_OPT_NTRIES, dnsc_ntries, 1,50),
  opt("attempts", DNS_OPT_NTRIES, dnsc_ntries, 1,50),
  opt("ndots", DNS_OPT_NDOTS, dnsc_ndots, 0,1000),
  opt("port", DNS_OPT_PORT, dnsc_port, 1,0xffff),
  opt("udpbuf", DNS_OPT_UDPSIZE, dnsc_udpbuf, DNS_MAXPACKET,65536),
#undef opt
};
#define dns_ctxopt(ctx,idx) (*((unsigned*)(((char*)ctx)+dns_opts[idx].offset)))

#define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n')

struct dns_ctx dns_defctx;

#define SETCTX(ctx) if (!ctx) ctx = &dns_defctx
#define SETCTXINITED(ctx) SETCTX(ctx); assert(CTXINITED(ctx))
#define CTXINITED(ctx) (ctx->dnsc_flags & DNS_INITED)
#define SETCTXFRESH(ctx) SETCTXINITED(ctx); assert(!CTXOPEN(ctx))
#define SETCTXINACTIVE(ctx) \
		SETCTXINITED(ctx); assert(!ctx->dnsc_nactive)
#define SETCTXOPEN(ctx) SETCTXINITED(ctx); assert(CTXOPEN(ctx))
#define CTXOPEN(ctx) (ctx->dnsc_udpsock >= 0)

#if defined(NDEBUG) || !defined(DEBUG)
#define dns_assert_ctx(ctx)
#else
static void dns_assert_ctx(const struct dns_ctx *ctx) {
  int nactive = 0;
  const struct dns_query *q;
  for(q = ctx->dnsc_qactive.head; q; q = q->dnsq_next) {
    assert(q->dnsq_ctx == ctx);
    assert(q == (q->dnsq_next ?
                 q->dnsq_next->dnsq_prev : ctx->dnsc_qactive.tail));
    assert(q == (q->dnsq_prev ?
                 q->dnsq_prev->dnsq_next : ctx->dnsc_qactive.head));
    ++nactive;
  }
  assert(nactive == ctx->dnsc_nactive);
}
#endif

enum {
  DNS_INTERNAL		= 0xffff, /* internal flags mask */
  DNS_INITED		= 0x0001, /* the context is initialized */
  DNS_ASIS_DONE		= 0x0002, /* search: skip the last as-is query */
  DNS_SEEN_NODATA	= 0x0004, /* search: NODATA has been received */
};

int dns_add_serv(struct dns_ctx *ctx, const char *serv) {
  union sockaddr_ns *sns;
  SETCTXFRESH(ctx);
  if (!serv)
    return (ctx->dnsc_nserv = 0);
  if (ctx->dnsc_nserv >= DNS_MAXSERV)
    return errno = ENFILE, -1;
  sns = &ctx->dnsc_serv[ctx->dnsc_nserv];
  memset(sns, 0, sizeof(*sns));
  if (dns_pton(AF_INET, serv, &sns->sin.sin_addr) > 0) {
    sns->sin.sin_family = AF_INET;
    return ++ctx->dnsc_nserv;
  }
#ifdef HAVE_IPv6
  if (dns_pton(AF_INET6, serv, &sns->sin6.sin6_addr) > 0) {
    sns->sin6.sin6_family = AF_INET6;
    return ++ctx->dnsc_nserv;
  }
#endif
  errno = EINVAL;
  return -1;
}

int dns_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa) {
  SETCTXFRESH(ctx);
  if (!sa)
    return (ctx->dnsc_nserv = 0);
  if (ctx->dnsc_nserv >= DNS_MAXSERV)
    return errno = ENFILE, -1;
#ifdef HAVE_IPv6
  else if (sa->sa_family == AF_INET6)
    ctx->dnsc_serv[ctx->dnsc_nserv].sin6 = *(struct sockaddr_in6*)sa;
#endif
  else if (sa->sa_family == AF_INET)
    ctx->dnsc_serv[ctx->dnsc_nserv].sin = *(struct sockaddr_in*)sa;
  else
    return errno = EAFNOSUPPORT, -1;
  return ++ctx->dnsc_nserv;
}

int dns_set_opts(struct dns_ctx *ctx, const char *opts) {
  unsigned i, v;
  int err = 0;
  SETCTXINACTIVE(ctx);
  for(;;) {
    while(ISSPACE(*opts)) ++opts;
    if (!*opts) break;
    for(i = 0; ; ++i) {
      if (i >= sizeof(dns_opts)/sizeof(dns_opts[0])) { ++err; break; }
      v = strlen(dns_opts[i].name);
      if (strncmp(dns_opts[i].name, opts, v) != 0 ||
          (opts[v] != ':' && opts[v] != '='))
        continue;
      opts += v + 1;
      v = 0;
      if (*opts < '0' || *opts > '9') { ++err; break; }
      do v = v * 10 + (*opts++ - '0');
      while (*opts >= '0' && *opts <= '9');
      if (v < dns_opts[i].min) v = dns_opts[i].min;
      if (v > dns_opts[i].max) v = dns_opts[i].max;
      dns_ctxopt(ctx, i) = v;
      break;
    }
    while(*opts && !ISSPACE(*opts)) ++opts;
  }
  return err;
}

int dns_set_opt(struct dns_ctx *ctx, enum dns_opt opt, int val) {
  int prev;
  unsigned i;
  SETCTXINACTIVE(ctx);
  for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) {
    if (dns_opts[i].opt != opt) continue;
    prev = dns_ctxopt(ctx, i);
    if (val >= 0) {
      unsigned v = val;
      if (v < dns_opts[i].min || v > dns_opts[i].max) {
        errno = EINVAL;
        return -1;
      }
      dns_ctxopt(ctx, i) = v;
    }
    return prev;
  }
  if (opt == DNS_OPT_FLAGS) {
    prev = ctx->dnsc_flags & ~DNS_INTERNAL;
    if (val >= 0)
      ctx->dnsc_flags =
        (ctx->dnsc_flags & DNS_INTERNAL) | (val & ~DNS_INTERNAL);
    return prev;
  }
  errno = ENOSYS;
  return -1;
}

int dns_add_srch(struct dns_ctx *ctx, const char *srch) {
  int dnl;
  SETCTXINACTIVE(ctx);
  if (!srch) {
    memset(ctx->dnsc_srchbuf, 0, sizeof(ctx->dnsc_srchbuf));
    ctx->dnsc_srchend = ctx->dnsc_srchbuf;
    return 0;
  }
  dnl =
    sizeof(ctx->dnsc_srchbuf) - (ctx->dnsc_srchend - ctx->dnsc_srchbuf) - 1;
  dnl = dns_sptodn(srch, ctx->dnsc_srchend, dnl);
  if (dnl > 0)
    ctx->dnsc_srchend += dnl;
  ctx->dnsc_srchend[0] = '\0';	/* we ensure the list is always ends at . */
  if (dnl > 0)
    return 0;
  errno = EINVAL;
  return -1;
}

static void dns_drop_utm(struct dns_ctx *ctx) {
  if (ctx->dnsc_utmfn)
    ctx->dnsc_utmfn(NULL, -1, ctx->dnsc_utmctx);
  ctx->dnsc_utmctx = NULL;
  ctx->dnsc_utmexp = -1;
}

static void
_dns_request_utm(struct dns_ctx *ctx, time_t now) {
  struct dns_query *q;
  time_t deadline;
  int timeout;
  q = ctx->dnsc_qactive.head;
  if (!q)
    deadline = -1, timeout = -1;
  else if (!now || q->dnsq_deadline <= now)
    deadline = 0, timeout = 0;
  else
    deadline = q->dnsq_deadline, timeout = (int)(deadline - now);
  if (ctx->dnsc_utmexp == deadline)
    return;
  ctx->dnsc_utmfn(ctx, timeout, ctx->dnsc_utmctx);
  ctx->dnsc_utmexp = deadline;
}

static __inline void
dns_request_utm(struct dns_ctx *ctx, time_t now) {
  if (ctx->dnsc_utmfn)
    _dns_request_utm(ctx, now);
}

void dns_set_dbgfn(struct dns_ctx *ctx, dns_dbgfn *dbgfn) {
  SETCTXINITED(ctx);
  ctx->dnsc_udbgfn = dbgfn;
}

void
dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data) {
  SETCTXINITED(ctx);
  dns_drop_utm(ctx);
  ctx->dnsc_utmfn = fn;
  ctx->dnsc_utmctx = data;
  if (CTXOPEN(ctx))
    dns_request_utm(ctx, 0);
}

static unsigned dns_nonrandom_32(void) {
#if defined(_WINDOWS) || defined(WINDOWS)
  FILETIME ft;
  GetSystemTimeAsFileTime(&ft);
  return ft.dwLowDateTime;
#else
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return tv.tv_usec;
#endif
}

/* This is historic deprecated API */
UDNS_API unsigned dns_random16(void);
unsigned dns_random16(void) {
  unsigned x = dns_nonrandom_32();
  return (x ^ (x >> 16)) & 0xffff;
}

static void dns_init_rng(struct dns_ctx *ctx) {
  udns_jraninit(&ctx->dnsc_jran, dns_nonrandom_32());
  ctx->dnsc_nextid = 0;
}

void dns_close(struct dns_ctx *ctx) {
  struct dns_query *q, *p;
  SETCTX(ctx);
  if (CTXINITED(ctx)) {
    if (ctx->dnsc_udpsock >= 0)
      closesocket(ctx->dnsc_udpsock);
    ctx->dnsc_udpsock = -1;
    if (ctx->dnsc_pbuf)
      free(ctx->dnsc_pbuf);
    ctx->dnsc_pbuf = NULL;
    q = ctx->dnsc_qactive.head;
    while((p = q) != NULL) {
      q = q->dnsq_next;
      free(p);
    }
    qlist_init(&ctx->dnsc_qactive);
    ctx->dnsc_nactive = 0;
    dns_drop_utm(ctx);
  }
}

void dns_reset(struct dns_ctx *ctx) {
  SETCTX(ctx);
  dns_close(ctx);
  memset(ctx, 0, sizeof(*ctx));
  ctx->dnsc_timeout = 4;
  ctx->dnsc_ntries = 3;
  ctx->dnsc_ndots = 1;
  ctx->dnsc_udpbuf = DNS_EDNS0PACKET;
  ctx->dnsc_port = DNS_PORT;
  ctx->dnsc_udpsock = -1;
  ctx->dnsc_srchend = ctx->dnsc_srchbuf;
  qlist_init(&ctx->dnsc_qactive);
  dns_init_rng(ctx);
  ctx->dnsc_flags = DNS_INITED;
}

struct dns_ctx *dns_new(const struct dns_ctx *copy) {
  struct dns_ctx *ctx;
  SETCTXINITED(copy);
  dns_assert_ctx(copy);
  ctx = malloc(sizeof(*ctx));
  if (!ctx)
    return NULL;
  *ctx = *copy;
  ctx->dnsc_udpsock = -1;
  qlist_init(&ctx->dnsc_qactive);
  ctx->dnsc_nactive = 0;
  ctx->dnsc_pbuf = NULL;
  ctx->dnsc_qstatus = 0;
  ctx->dnsc_srchend = ctx->dnsc_srchbuf +
    (copy->dnsc_srchend - copy->dnsc_srchbuf);
  ctx->dnsc_utmfn = NULL;
  ctx->dnsc_utmctx = NULL;
  dns_init_rng(ctx);
  return ctx;
}

void dns_free(struct dns_ctx *ctx) {
  assert(ctx != NULL && ctx != &dns_defctx);
  dns_reset(ctx);
  free(ctx);
}

int dns_open(struct dns_ctx *ctx) {
  int sock;
  unsigned i;
  int port;
  union sockaddr_ns *sns;
#ifdef HAVE_IPv6
  unsigned have_inet6 = 0;
#endif

  SETCTXINITED(ctx);
  assert(!CTXOPEN(ctx));

  port = htons((unsigned short)ctx->dnsc_port);
  /* ensure we have at least one server */
  if (!ctx->dnsc_nserv) {
    sns = ctx->dnsc_serv;
    sns->sin.sin_family = AF_INET;
    sns->sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    ctx->dnsc_nserv = 1;
  }

  for (i = 0; i < ctx->dnsc_nserv; ++i) {
    sns = &ctx->dnsc_serv[i];
    /* set port for each sockaddr */
#ifdef HAVE_IPv6
    if (sns->sa.sa_family == AF_INET6) {
      if (!sns->sin6.sin6_port) sns->sin6.sin6_port = (unsigned short)port;
      ++have_inet6;
    }
    else
#endif
    {
      assert(sns->sa.sa_family == AF_INET);
      if (!sns->sin.sin_port) sns->sin.sin_port = (unsigned short)port;
    }
  }

#ifdef HAVE_IPv6
  if (have_inet6 && have_inet6 < ctx->dnsc_nserv) {
    /* convert all IPv4 addresses to IPv6 V4MAPPED */
    struct sockaddr_in6 sin6;
    memset(&sin6, 0, sizeof(sin6));
    sin6.sin6_family = AF_INET6;
    /* V4MAPPED: ::ffff:1.2.3.4 */
    sin6.sin6_addr.s6_addr[10] = 0xff;
    sin6.sin6_addr.s6_addr[11] = 0xff;
    for(i = 0; i < ctx->dnsc_nserv; ++i) {
      sns = &ctx->dnsc_serv[i];
      if (sns->sa.sa_family == AF_INET) {
        sin6.sin6_port = sns->sin.sin_port;
        memcpy(sin6.sin6_addr.s6_addr + 4*3, &sns->sin.sin_addr, 4);
        sns->sin6 = sin6;
      }
    }
  }

  ctx->dnsc_salen = have_inet6 ?
    sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);

  if (have_inet6)
    sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  else
    sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
#else /* !HAVE_IPv6 */
  sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  ctx->dnsc_salen = sizeof(struct sockaddr_in);
#endif /* HAVE_IPv6 */

  if (sock < 0) {
    ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
    return -1;
  }
#if defined(_WINDOWS) || defined(WINDOWS)
  { unsigned long on = 1;
    if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) {
      closesocket(sock);
      ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
      return -1;
    }
  }
#else	/* !WINDOWS */
  if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0 ||
      fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
    closesocket(sock);
    ctx->dnsc_qstatus = DNS_E_TEMPFAIL;
    return -1;
  }
#endif	/* WINDOWS */
  /* allocate the packet buffer */
  if ((ctx->dnsc_pbuf = malloc(ctx->dnsc_udpbuf)) == NULL) {
    closesocket(sock);
    ctx->dnsc_qstatus = DNS_E_NOMEM;
    errno = ENOMEM;
    return -1;
  }

  ctx->dnsc_udpsock = sock;
  dns_request_utm(ctx, 0);
  return sock;
}

int dns_sock(const struct dns_ctx *ctx) {
  SETCTXINITED(ctx);
  return ctx->dnsc_udpsock;
}

int dns_active(const struct dns_ctx *ctx) {
  SETCTXINITED(ctx);
  dns_assert_ctx(ctx);
  return ctx->dnsc_nactive;
}

int dns_status(const struct dns_ctx *ctx) {
  SETCTX(ctx);
  return ctx->dnsc_qstatus;
}
void dns_setstatus(struct dns_ctx *ctx, int status) {
  SETCTX(ctx);
  ctx->dnsc_qstatus = status;
}

/* End the query: disconnect it from the active list, free it,
 * and return the result to the caller.
 */
static void
dns_end_query(struct dns_ctx *ctx, struct dns_query *q,
              int status, void *result) {
  dns_query_fn *cbck = q->dnsq_cbck;
  void *cbdata = q->dnsq_cbdata;
  ctx->dnsc_qstatus = status;
  assert((status < 0 && result == 0) || (status >= 0 && result != 0));
  assert(cbck != 0);	/*XXX callback may be NULL */
  assert(ctx->dnsc_nactive > 0);
  --ctx->dnsc_nactive;
  qlist_remove(&ctx->dnsc_qactive, q);
  /* force the query to be unconnected */
  /*memset(q, 0, sizeof(*q));*/
#ifndef NDEBUG
  q->dnsq_ctx = NULL;
#endif
  free(q);
  cbck(ctx, result, cbdata);
}

#define DNS_DBG(ctx, code, sa, slen, pkt, plen) \
  do { \
    if (ctx->dnsc_udbgfn) \
      ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, 0, 0); \
  } while(0)
#define DNS_DBGQ(ctx, q, code, sa, slen, pkt, plen) \
  do { \
    if (ctx->dnsc_udbgfn) \
      ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, q, q->dnsq_cbdata); \
  } while(0)

static void dns_newid(struct dns_ctx *ctx, struct dns_query *q) {
  /* this is how we choose an identifier for a new query (qID).
   * For now, it's just sequential number, incremented for every query, and
   * thus obviously trivial to guess.
   * There are two choices:
   *  a) use sequential numbers.  It is plain insecure. In DNS, there are two
   *   places where random numbers are (or can) be used to increase security:
   *   random qID and random source port number.  Without this randomness
   *   (udns uses fixed port for all queries), or when the randomness is weak,
   *   it's trivial to spoof query replies.  With randomness however, it
   *   becomes a bit more difficult task.  Too bad we only have 16 bits for
   *   our security, as qID is only two bytes.  It isn't a security per se,
   *   to rely on those 16 bits - an attacker can just flood us with fake
   *   replies with all possible qIDs (only 65536 of them), and in this case,
   *   even if we'll use true random qIDs, we'll be in trouble (not protected
   *   against spoofing).  Yes, this is only possible on a high-speed network
   *   (probably on the LAN only, since usually a border router for a LAN
   *   protects internal machines from packets with spoofed local addresses
   *   from outside, and usually a nameserver resides on LAN), but it's
   *   still very well possible to send us fake replies.
   *   In other words: there's nothing a DNS (stub) resolver can do against
   *   spoofing attacks, unless DNSSEC is in use, which helps here alot.
   *   Too bad that DNSSEC isn't widespread, so relying on it isn't an
   *   option in almost all cases...
   *  b) use random qID, based on some random-number generation mechanism.
   *   This way, we increase our protection a bit (see above - it's very weak
   *   still), but we also increase risk of qID reuse and matching late replies
   *   that comes to queries we've sent before against new queries.  There are
   *   some more corner cases around that, as well - for example, normally,
   *   udns tries to find the query for a given reply by qID, *and* by
   *   verifying that the query DN and other parameters are also the same
   *   (so if the new query is against another domain name, old reply will
   *   be ignored automatically).  But certain types of replies which we now
   *   handle - for example, FORMERR reply from servers which refuses to
   *   process EDNS0-enabled packets - comes without all the query parameters
   *   but the qID - so we're forced to use qID only when determining which
   *   query the given reply corresponds to.  This makes us even more
   *   vulnerable to spoofing attacks, because an attacker don't even need to
   *   know which queries we perform to spoof the replies - he only needs to
   *   flood us with fake FORMERR "replies".
   *
   * That all to say: using sequential (or any other trivially guessable)
   * numbers for qIDs is insecure, but the whole thing is inherently insecure
   * as well, and this "extra weakness" that comes from weak qID choosing
   * algorithm adds almost nothing to the underlying problem.
   *
   * It CAN NOT be made secure.  Period.  That's it.
   * Unless we choose to implement DNSSEC, which is a whole different story.
   * Forcing TCP mode makes it better, but who uses TCP for DNS anyway?
   * (and it's hardly possible because of huge impact on the recursive
   * nameservers).
   *
   * Note that ALL stub resolvers (again, unless they implement and enforce
   * DNSSEC) suffers from this same problem.
   *
   * Here, I use a pseudo-random number generator for qIDs, instead of a
   * simpler sequential IDs.  This is _not_ more secure than sequential
   * ID, but some found random IDs more enjoyeable for some reason.  So
   * here it goes.
   */

  /* Use random number and check if it's unique.
   * If it's not, try again up to 5 times.
   */
  unsigned loop;
  dnsc_t c0, c1;
  for(loop = 0; loop < 5; ++loop) {
    const struct dns_query *c;
    if (!ctx->dnsc_nextid)
      ctx->dnsc_nextid = udns_jranval(&ctx->dnsc_jran);
    c0 = ctx->dnsc_nextid & 0xff;
    c1 = (ctx->dnsc_nextid >> 8) & 0xff;
    ctx->dnsc_nextid >>= 16;
    for(c = ctx->dnsc_qactive.head; c; c = c->dnsq_next)
      if (c->dnsq_id[0] == c0 && c->dnsq_id[1] == c1)
        break; /* found such entry, try again */
    if (!c)
      break;
  }
  q->dnsq_id[0] = c0; q->dnsq_id[1] = c1;

  /* reset all parameters relevant for previous query lifetime */
  q->dnsq_try = 0;
  q->dnsq_servi = 0;
  /*XXX probably should keep dnsq_servnEDNS0 bits?
   * See also comments in dns_ioevent() about FORMERR case */
  q->dnsq_servwait = q->dnsq_servskip = q->dnsq_servnEDNS0 = 0;
}

/* Find next search suffix and fills in q->dnsq_dn.
 * Return 0 if no more to try. */
static int dns_next_srch(struct dns_ctx *ctx, struct dns_query *q) {
  unsigned dnl;

  for(;;) {
    if (q->dnsq_nxtsrch > ctx->dnsc_srchend)
      return 0;
    dnl = dns_dnlen(q->dnsq_nxtsrch);
    if (dnl + q->dnsq_origdnl0 <= DNS_MAXDN &&
        (*q->dnsq_nxtsrch || !(q->dnsq_flags & DNS_ASIS_DONE)))
      break;
    q->dnsq_nxtsrch += dnl;
  }
  memcpy(q->dnsq_dn + q->dnsq_origdnl0, q->dnsq_nxtsrch, dnl);
  if (!*q->dnsq_nxtsrch)
    q->dnsq_flags |= DNS_ASIS_DONE;
  q->dnsq_nxtsrch += dnl;
  dns_newid(ctx, q); /* new ID for new qDN */
  return 1;
}

/* find the server to try for current iteration.
 * Note that current dnsq_servi may point to a server we should skip --
 * in that case advance to the next server.
 * Return true if found, false if all tried.
 */
static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) {
  while(q->dnsq_servi < ctx->dnsc_nserv) {
    if (!(q->dnsq_servskip & (1 << q->dnsq_servi)))
      return 1;
    ++q->dnsq_servi;
  }
  return 0;
}

/* format and send the query to a given server.
 * In case of network problem (sendto() fails), return -1,
 * else return 0.
 */
static int
dns_send_this(struct dns_ctx *ctx, struct dns_query *q,
              unsigned servi, time_t now) {
  unsigned qlen;
  unsigned tries;

  { /* format the query buffer */
    dnsc_t *p = ctx->dnsc_pbuf;
    memset(p, 0, DNS_HSIZE);
    if (!(q->dnsq_flags & DNS_NORD)) p[DNS_H_F1] |= DNS_HF1_RD;
    if (q->dnsq_flags & DNS_AAONLY) p[DNS_H_F1] |= DNS_HF1_AA;
    if (q->dnsq_flags & DNS_SET_CD) p[DNS_H_F2] |= DNS_HF2_CD;
    p[DNS_H_QDCNT2] = 1;
    memcpy(p + DNS_H_QID, q->dnsq_id, 2);
    p = dns_payload(p);
    /* copy query dn */
    p += dns_dntodn(q->dnsq_dn, p, DNS_MAXDN);
    /* query type and class */
    memcpy(p, q->dnsq_typcls, 4); p += 4;
    /* add EDNS0 record. DO flag requires it */
    if (q->dnsq_flags & DNS_SET_DO ||
        (ctx->dnsc_udpbuf > DNS_MAXPACKET &&
         !(q->dnsq_servnEDNS0 & (1 << servi)))) {
      *p++ = 0;			/* empty (root) DN */
      p = dns_put16(p, DNS_T_OPT);
      p = dns_put16(p, ctx->dnsc_udpbuf);
      /* EDNS0 RCODE & VERSION; rest of the TTL field; RDLEN */
      memset(p, 0, 2+2+2);
      if (q->dnsq_flags & DNS_SET_DO) p[2] |= DNS_EF1_DO;
      p += 2+2+2;
      ctx->dnsc_pbuf[DNS_H_ARCNT2] = 1;
    }
    qlen = p - ctx->dnsc_pbuf;
    assert(qlen <= ctx->dnsc_udpbuf);
  }

  /* send the query */
  tries = 10;
  while (sendto(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, qlen, 0,
                &ctx->dnsc_serv[servi].sa, ctx->dnsc_salen) < 0) {
    /*XXX just ignore the sendto() error for now and try again.
     * In the future, it may be possible to retrieve the error code
     * and find which operation/query failed.
     *XXX try the next server too? (if ENETUNREACH is returned immediately)
     */
    if (--tries) continue;
    /* if we can't send the query, fail it. */
    dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
    return -1;
  }
  DNS_DBGQ(ctx, q, 1,
           &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns),
           ctx->dnsc_pbuf, qlen);
  q->dnsq_servwait |= 1 << servi;	/* expect reply from this ns */

  q->dnsq_deadline = now +
    (dns_find_serv(ctx, q) ? 1 : ctx->dnsc_timeout << q->dnsq_try);

  /* move the query to the proper place, according to the new deadline */
  qlist_remove(&ctx->dnsc_qactive, q);
  { /* insert from the tail */
    struct dns_query *p;
    for(p = ctx->dnsc_qactive.tail; p; p = p->dnsq_prev)
      if (p->dnsq_deadline <= q->dnsq_deadline)
	break;
    qlist_insert_after(&ctx->dnsc_qactive, q, p);
  }

  return 0;
}

/* send the query out using next available server
 * and add it to the active list, or, if no servers available,
 * end it.
 */
static void
dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) {

  /* if we can't send the query, return TEMPFAIL even when searching:
   * we can't be sure whenever the name we tried to search exists or not,
   * so don't continue searching, or we may find the wrong name. */

  if (!dns_find_serv(ctx, q)) {
    /* no more servers in this iteration.  Try the next cycle */
    q->dnsq_servi = 0;	/* reset */
    q->dnsq_try++;	/* next try */
    if (q->dnsq_try >= ctx->dnsc_ntries ||
        !dns_find_serv(ctx, q)) {
      /* no more servers and tries, fail the query */
      /* return TEMPFAIL even when searching: no more tries for this
       * searchlist, and no single definitive reply (handled in dns_ioevent()
       * in NOERROR or NXDOMAIN cases) => all nameservers failed to process
       * current search list element, so we don't know whenever the name exists.
       */
      dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0);
      return;
    }
  }

  dns_send_this(ctx, q, q->dnsq_servi++, now);
}

static void dns_dummy_cb(struct dns_ctx *ctx, void *result, void *data) {
  if (result) free(result);
  data = ctx = 0;	/* used */
}

/* The (only, main, real) query submission routine.
 * Allocate new query structure, initialize it, check validity of
 * parameters, and add it to the head of the active list, without
 * trying to send it (to be picked up on next event).
 * Error return (without calling the callback routine) -
 *  no memory or wrong parameters.
 *XXX The `no memory' case probably should go to the callback anyway...
 */
struct dns_query *
dns_submit_dn(struct dns_ctx *ctx,
              dnscc_t *dn, int qcls, int qtyp, int flags,
              dns_parse_fn *parse, dns_query_fn *cbck, void *data) {
  struct dns_query *q;
  SETCTXOPEN(ctx);
  dns_assert_ctx(ctx);

  q = calloc(sizeof(*q), 1);
  if (!q) {
    ctx->dnsc_qstatus = DNS_E_NOMEM;
    return NULL;
  }

#ifndef NDEBUG
  q->dnsq_ctx = ctx;
#endif
  q->dnsq_parse = parse;
  q->dnsq_cbck = cbck ? cbck : dns_dummy_cb;
  q->dnsq_cbdata = data;

  q->dnsq_origdnl0 = dns_dntodn(dn, q->dnsq_dn, sizeof(q->dnsq_dn));
  assert(q->dnsq_origdnl0 > 0);
  --q->dnsq_origdnl0;		/* w/o the trailing 0 */
  dns_put16(q->dnsq_typcls+0, qtyp);
  dns_put16(q->dnsq_typcls+2, qcls);
  q->dnsq_flags = (flags | ctx->dnsc_flags) & ~DNS_INTERNAL;

  if (flags & DNS_NOSRCH ||
      dns_dnlabels(q->dnsq_dn) > ctx->dnsc_ndots) {
    q->dnsq_nxtsrch = flags & DNS_NOSRCH ?
      ctx->dnsc_srchend /* end of the search list if no search requested */ :
      ctx->dnsc_srchbuf /* beginning of the list, but try as-is first */;
    q->dnsq_flags |= DNS_ASIS_DONE;
    dns_newid(ctx, q);
  }
  else {
    q->dnsq_nxtsrch = ctx->dnsc_srchbuf;
    dns_next_srch(ctx, q);
  }

  /* q->dnsq_deadline is set to 0 (calloc above): the new query is
   * "already expired" when first inserted into queue, so it's safe
   * to insert it into the head of the list.  Next call to dns_timeouts()
   * will actually send it.
   */
  qlist_add_head(&ctx->dnsc_qactive, q);
  ++ctx->dnsc_nactive;
  dns_request_utm(ctx, 0);

  return q;
}

struct dns_query *
dns_submit_p(struct dns_ctx *ctx,
             const char *name, int qcls, int qtyp, int flags,
             dns_parse_fn *parse, dns_query_fn *cbck, void *data) {
  int isabs;
  SETCTXOPEN(ctx);
  if (dns_ptodn(name, 0, ctx->dnsc_pbuf, DNS_MAXDN, &isabs) <= 0) {
    ctx->dnsc_qstatus = DNS_E_BADQUERY;
    return NULL;
  }
  if (isabs)
    flags |= DNS_NOSRCH;
  return
    dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data);
}

/* process readable fd condition.
 * To be usable in edge-triggered environment, the routine
 * should consume all input so it should loop over.
 * Note it isn't really necessary to loop here, because
 * an application may perform the loop just fine by it's own,
 * but in this case we should return some sensitive result,
 * to indicate when to stop calling and error conditions.
 * Note also we may encounter all sorts of recvfrom()
 * errors which aren't fatal, and at the same time we may
 * loop forever if an error IS fatal.
 */
void dns_ioevent(struct dns_ctx *ctx, time_t now) {
  int r;
  unsigned servi;
  struct dns_query *q;
  dnsc_t *pbuf;
  dnscc_t *pend, *pcur;
  void *result;
  union sockaddr_ns sns;
  socklen_t slen;

  SETCTX(ctx);
  if (!CTXOPEN(ctx))
    return;
  dns_assert_ctx(ctx);
  pbuf = ctx->dnsc_pbuf;

  if (!now) now = time(NULL);

again: /* receive the reply */

  slen = sizeof(sns);
  r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf,
               MSG_DONTWAIT, &sns.sa, &slen);
  if (r < 0) {
    /*XXX just ignore recvfrom() errors for now.
     * in the future it may be possible to determine which
     * query failed and requeue it.
     * Note there may be various error conditions, triggered
     * by both local problems and remote problems.  It isn't
     * quite trivial to determine whenever an error is local
     * or remote.  On local errors, we should stop, while
     * remote errors should be ignored (for now anyway).
     */
#if defined(_WINDOWS) || defined(WINDOWS)
    if (WSAGetLastError() == WSAEWOULDBLOCK)
#else
    if (errno == EAGAIN)
#endif
    {
      dns_request_utm(ctx, now);
      return;
    }
    goto again;
  }

  pend = pbuf + r;
  pcur = dns_payload(pbuf);

  /* check reply header */
  if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) {
    DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
    goto again;
  }

  /* find the matching query, by qID */
  for (q = ctx->dnsc_qactive.head; ; q = q->dnsq_next) {
    if (!q) {
      /* no more requests: old reply? */
      DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
      goto again;
    }
    if (pbuf[DNS_H_QID1] == q->dnsq_id[0] &&
        pbuf[DNS_H_QID2] == q->dnsq_id[1])
      break;
  }

  /* if we have numqd, compare with our query qDN */
  if (dns_numqd(pbuf)) {
    /* decode the qDN */
    dnsc_t dn[DNS_MAXDN];
    if (dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 ||
        pcur + 4 > pend) {
      DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
      goto again;
    }
    if (!dns_dnequal(dn, q->dnsq_dn) ||
        memcmp(pcur, q->dnsq_typcls, 4) != 0) {
      /* not this query */
      DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r);
      goto again;
    }
    /* here, query match, and pcur points past qDN in query section in pbuf */
  }
  /* if no numqd, we only allow FORMERR rcode */
  else if (dns_rcode(pbuf) != DNS_R_FORMERR) {
    /* treat it as bad reply if !FORMERR */
    DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r);
    goto again;
  }
  else {
    /* else it's FORMERR, handled below */
  }

  /* find server */
#ifdef HAVE_IPv6
  if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) {
    for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
      if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns.sin6))
        break;
  }
  else
#endif
  if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) {
    for(servi = 0; servi < ctx->dnsc_nserv; ++servi)
      if (sin_eq(ctx->dnsc_serv[servi].sin, sns.sin))
        break;
  }
  else
    servi = ctx->dnsc_nserv;

  /* check if we expect reply from this server.
   * Note we can receive reply from first try if we're already at next */
  if (!(q->dnsq_servwait & (1 << servi))) { /* if ever asked this NS */
    DNS_DBG(ctx, -2/*wrong server*/, &sns.sa, slen, pbuf, r);
    goto again;
  }

  /* we got (some) reply for our query */

  DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r);
  q->dnsq_servwait &= ~(1 << servi);	/* don't expect reply from this serv */

  /* process the RCODE */
  switch(dns_rcode(pbuf)) {

  case DNS_R_NOERROR:
    if (dns_tc(pbuf)) {
      /* possible truncation.  We can't deal with it. */
      /*XXX for now, treat TC bit the same as SERVFAIL.
       * It is possible to:
       *  a) try to decode the reply - may be ANSWER section is ok;
       *  b) check if server understands EDNS0, and if it is, and
       *   answer still don't fit, end query.
       */
      break;
    }
    if (!dns_numan(pbuf)) {	/* no data of requested type */
      if (dns_next_srch(ctx, q)) {
        /* if we're searching, try next searchlist element,
         * but remember NODATA reply. */
        q->dnsq_flags |= DNS_SEEN_NODATA;
        dns_send(ctx, q, now);
      }
      else
        /* else - nothing to search any more - finish the query.
         * It will be NODATA since we've seen a NODATA reply. */
        dns_end_query(ctx, q, DNS_E_NODATA, 0);
    }
    /* we've got a positive reply here */
    else if (q->dnsq_parse) {
      /* if we have parsing routine, call it and return whatever it returned */
      /* don't try to re-search if NODATA here.  For example,
       * if we asked for A but only received CNAME.  Unless we'll
       * someday do recursive queries.  And that's problematic too, since
       * we may be dealing with specific AA-only nameservers for a given
       * domain, but CNAME points elsewhere...
       */
      r = q->dnsq_parse(q->dnsq_dn, pbuf, pcur, pend, &result);
      dns_end_query(ctx, q, r, r < 0 ? NULL : result);
    }
    /* else just malloc+copy the raw DNS reply */
    else if ((result = malloc(r)) == NULL)
      dns_end_query(ctx, q, DNS_E_NOMEM, NULL);
    else {
      memcpy(result, pbuf, r);
      dns_end_query(ctx, q, r, result);
    }
    goto again;

  case DNS_R_NXDOMAIN:	/* Non-existing domain. */
    if (dns_next_srch(ctx, q))
      /* more search entries exists, try them. */
      dns_send(ctx, q, now);
    else
      /* nothing to search anymore. End the query, returning either NODATA
       * if we've seen it before, or NXDOMAIN if not. */
      dns_end_query(ctx, q,
           q->dnsq_flags & DNS_SEEN_NODATA ? DNS_E_NODATA : DNS_E_NXDOMAIN, 0);
    goto again;

  case DNS_R_FORMERR:
  case DNS_R_NOTIMPL:
    /* for FORMERR and NOTIMPL rcodes, if we tried EDNS0-enabled query,
     * try w/o EDNS0. */
    if (ctx->dnsc_udpbuf > DNS_MAXPACKET &&
        !(q->dnsq_servnEDNS0 & (1 << servi))) {
      /* we always trying EDNS0 first if enabled, and retry a given query
       * if not available. Maybe it's better to remember inavailability of
       * EDNS0 in ctx as a per-NS flag, and never try again for this NS.
       * For long-running applications.. maybe they will change the nameserver
       * while we're running? :)  Also, since FORMERR is the only rcode we
       * allow to be header-only, and in this case the only check we do to
       * find a query it belongs to is qID (not qDN+qCLS+qTYP), it's much
       * easier to spoof and to force us to perform non-EDNS0 queries only...
       */
      q->dnsq_servnEDNS0 |= 1 << servi;
      dns_send_this(ctx, q, servi, now);
      goto again;
    }
    /* else we handle it the same as SERVFAIL etc */

  case DNS_R_SERVFAIL:
  case DNS_R_REFUSED:
    /* for these rcodes, advance this request
     * to the next server and reschedule */
  default: /* unknown rcode? hmmm... */
    break;
  }

  /* here, we received unexpected reply */
  q->dnsq_servskip |= (1 << servi);	/* don't retry this server */

  /* we don't expect replies from this server anymore.
   * But there may be other servers.  Some may be still processing our
   * query, and some may be left to try.
   * We just ignore this reply and wait a bit more if some NSes haven't
   * replied yet (dnsq_servwait != 0), and let the situation to be handled
   * on next event processing.  Timeout for this query is set correctly,
   * if not taking into account the one-second difference - we can try
   * next server in the same iteration sooner.
   */

  /* try next server */
  if (!q->dnsq_servwait) {
    /* next retry: maybe some other servers will reply next time.
     * dns_send() will end the query for us if no more servers to try.
     * Note we can't continue with the next searchlist element here:
     * we don't know if the current qdn exists or not, there's no definitive
     * answer yet (which is seen in cases above).
     *XXX standard resolver also tries as-is query in case all nameservers
     * failed to process our query and if not tried before.  We don't do it.
     */
    dns_send(ctx, q, now);
  }
  else {
    /* else don't do anything - not all servers replied yet */
  }
  goto again;

}

/* handle all timeouts */
int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) {
  /* this is a hot routine */
  struct dns_query *q;

  SETCTX(ctx);
  dns_assert_ctx(ctx);

  /* Pick up first entry from query list.
   * If its deadline has passed, (re)send it
   * (dns_send() will move it next in the list).
   * If not, this is the query which determines the closest deadline.
   */

  q = ctx->dnsc_qactive.head;
  if (!q)
    return maxwait;
  if (!now)
    now = time(NULL);
  do {
    if (q->dnsq_deadline > now) { /* first non-expired query */
      int w = (int)(q->dnsq_deadline - now);
      if (maxwait < 0 || maxwait > w)
        maxwait = w;
      break;
    }
    else {
      /* process expired deadline */
      dns_send(ctx, q, now);
    }
  } while((q = ctx->dnsc_qactive.head) != NULL);

  dns_request_utm(ctx, now); /* update timer with new deadline */
  return maxwait;
}

struct dns_resolve_data {
  int   dnsrd_done;
  void *dnsrd_result;
};

static void dns_resolve_cb(struct dns_ctx *ctx, void *result, void *data) {
  struct dns_resolve_data *d = data;
  d->dnsrd_result = result;
  d->dnsrd_done = 1;
  ctx = ctx;
}

void *dns_resolve(struct dns_ctx *ctx, struct dns_query *q) {
  time_t now;
  struct dns_resolve_data d;
  int n;
  SETCTXOPEN(ctx);

  if (!q)
    return NULL;

  assert(ctx == q->dnsq_ctx);
  dns_assert_ctx(ctx);
  /* do not allow re-resolving syncronous queries */
  assert(q->dnsq_cbck != dns_resolve_cb && "can't resolve syncronous query");
  if (q->dnsq_cbck == dns_resolve_cb) {
    ctx->dnsc_qstatus = DNS_E_BADQUERY;
    return NULL;
  }
  q->dnsq_cbck = dns_resolve_cb;
  q->dnsq_cbdata = &d;
  d.dnsrd_done = 0;

  now = time(NULL);
  while(!d.dnsrd_done && (n = dns_timeouts(ctx, -1, now)) >= 0) {
#ifdef HAVE_POLL
    struct pollfd pfd;
    pfd.fd = ctx->dnsc_udpsock;
    pfd.events = POLLIN;
    n = poll(&pfd, 1, n * 1000);
#else
    fd_set rfd;
    struct timeval tv;
    FD_ZERO(&rfd);
    FD_SET(ctx->dnsc_udpsock, &rfd);
    tv.tv_sec = n; tv.tv_usec = 0;
    n = select(ctx->dnsc_udpsock + 1, &rfd, NULL, NULL, &tv);
#endif
    now = time(NULL);
    if (n > 0)
      dns_ioevent(ctx, now);
  }

  return d.dnsrd_result;
}

void *dns_resolve_dn(struct dns_ctx *ctx,
                     dnscc_t *dn, int qcls, int qtyp, int flags,
                     dns_parse_fn *parse) {
  return
    dns_resolve(ctx,
      dns_submit_dn(ctx, dn, qcls, qtyp, flags, parse, NULL, NULL));
}

void *dns_resolve_p(struct dns_ctx *ctx,
                    const char *name, int qcls, int qtyp, int flags,
                    dns_parse_fn *parse) {
  return
    dns_resolve(ctx,
      dns_submit_p(ctx, name, qcls, qtyp, flags, parse, NULL, NULL));
}

int dns_cancel(struct dns_ctx *ctx, struct dns_query *q) {
  SETCTX(ctx);
  dns_assert_ctx(ctx);
  assert(q->dnsq_ctx == ctx);
  /* do not allow cancelling syncronous queries */
  assert(q->dnsq_cbck != dns_resolve_cb && "can't cancel syncronous query");
  if (q->dnsq_cbck == dns_resolve_cb)
    return (ctx->dnsc_qstatus = DNS_E_BADQUERY);
  qlist_remove(&ctx->dnsc_qactive, q);
  --ctx->dnsc_nactive;
  dns_request_utm(ctx, 0);
  return 0;
}

