/* ==========================================================================
 * lookup.c - Simple DNS Query Interface
 * --------------------------------------------------------------------------
 * Copyright (c) 2004, 2005, 2006  Barracuda Networks, Inc.
 * Copyright (c) 2006  William Ahern <william@25thandClement.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 * --------------------------------------------------------------------------
 * History
 *
 * 2006-04-22 (william@25thandClement.com)
 * 	Remove some BSD dependencies. Change interface to make difficult
 * 	things possible (like cancellations), and keep simple things simple.
 * 	This includes exposing a lookup_open()/lookup_close() so lookups
 * 	can have handles held by the user for cancellation.
 *
 * 2006-02-14 (william@25thandClement.com)
 * 	Published by Barracuda Networks, originally authored by
 * 	employee William Ahern (wahern@barracudanetworks.com).
 * --------------------------------------------------------------------------
 * Description
 *
 * Simple DNS query state machine which wraps the c-ares asynchronous client
 * DNS library. Supports compound queries for one or more of the following
 * record types: PTR, A, AAAA, CNAME, NS, MX, TXT, SOA and SRV.
 *
 * ==========================================================================
 */
#ifdef USE_CARES
#include <stdio.h>		/* FILE fopen(3) fread(3) fclose(3) fileno(3) */
#include <stdlib.h>		/* NULL free(3) */
#include <time.h>		/* time_t time(2) */

#include <string.h>		/* memmove(3) strsep(3) strlen(3)
				 * strcasecmp(3) strncpy(3) strcspn(3)
				 * strerror(3)
				 */
#include <string.h>		/* strlcpy(3) */

#include <errno.h>

#include <assert.h>

#include <sys/types.h>		/* dev_t ino_t */
#include <sys/stat.h>		/* struct stat */

#include <sys/time.h>		/* struct timeval timerclear */

#ifndef WIN32
#include <sys/select.h>		/* FD_ZERO FD_SET fd_set */
#else
#include <select.h>
#endif
#include <sys/socket.h>		/* socklen_t struct sockaddr_storage */

#include <sys/param.h>		/* MIN MAX */

#include <sys/queue.h>		/* SLIST LIST CIRCLEQ */

#include <unistd.h>		/* fstat(2) */

#include <arpa/inet.h>		/* inet_pton(3) */

#include <arpa/nameser.h>
/* HFIXEDSZ QFIXEDSZ RRFIXEDSZ T_A T_AAAA T_MX T_CNAME C_IN
 *
 * NOTE: T_A, T_AAAA, T_MX and C_IN are deprecated. Some systems, like
 * OpenBSD, do not yet define the new enum types (ns_t_a, ns_t_a6,
 * ns_t_cname, ns_t_mx, ns_c_in).
 */

#include <netinet/in.h>		/* IN6_IS_ADDR_V4MAPPED */      

#include <netdb.h>		/* NI_MAXHOST */

#ifndef NI_MAXHOST
#define NI_MAXHOST	1024
#endif

#ifdef USE_PTHREADS
#include <pthread.h>		/* pthread_once_t pthread_key_t pthread_mutex_t
				 * pthread_once(3) pthread_setspecific(3)
				 * pthread_getspecific(3) pthread_mutex_init(3)
				 * pthread_mutex_lock(3) pthread_mutex_unlock(3)
				 */
#endif /* USE_PTHREADS */

#include <event.h>

#include <err.h>		/* warn(3) */

#include <ares.h>
#if 0
#include <ares_dns.h>		/* DNS_HEADER_* DNS_RR_* */
#endif

#include <arena/proto.h>
#include <arena/util.h>

#include "lookup.h"

#define MARK (fprintf(stderr, "@%s:%d\n", __FUNCTION__, __LINE__))


/* -- COMPILE-TIME CONSTANTS ---------------------------------------------- */

#ifndef LOOKUP_RESOLV_PATH
#define LOOKUP_RESOLV_PATH		/etc/resolv.conf
#endif

#ifndef LOOKUP_HOSTS_PATH
#define LOOKUP_HOSTS_PATH		/etc/hosts
#endif

#ifndef LOOKUP_HOSTS_CHECK_INTERVAL
#define LOOKUP_HOSTS_CHECK_INTERVAL	10
#endif

#ifndef LOOKUP_HOSTS_ALIAS_MAX
#define LOOKUP_HOSTS_ALIAS_MAX		32
#endif

#ifndef LOOKUP_CACHE_MAX
#define LOOKUP_CACHE_MAX		8192
#endif

#ifndef LOOKUP_CACHE_MAP
#define LOOKUP_CACHE_MAP		/dev/zero
#endif

#ifndef LOOKUP_CHAIN_MAX
#define LOOKUP_CHAIN_MAX		3
#endif

#ifndef LOOKUP_QUERY_MAX
#define LOOKUP_QUERY_MAX		0	/* unlimited queries per lookup */
#endif

#ifndef LOOKUP_NCHANNELS
#define LOOKUP_NCHANNELS		3
#endif


/* -- STRING UTILITIES ---------------------------------------------------- */

#ifndef STRINGIFY
#define STRINGIFY_(s)	#s
#define STRINGIFY(s)	STRINGIFY_(s)
#endif


static int strlcasecmp(const char *a, size_t alen, const char *b, size_t blen) {
	int cmp;

	cmp	= strncasecmp(a, b, MIN(alen, blen));

	if (cmp || alen == blen)
		return cmp;

	/* NOTE: A '\0' char will compare equal */
	cmp	= (alen > blen)
		? (unsigned char)a[alen] - '\0'
		: (unsigned char)b[blen] - '\0';

	return cmp;
} /* strlcasecmp() */


static void split(int *argc, char *argv[], int len, char *line, char *sep) {
	char **ap;
	char *nxt	= line;

	if (!sep)
		sep	= " \t";

	for (*argc = 0, ap = argv; ap < &argv[len - 1];) {
		if ((*ap = strsep(&nxt, sep)) == 0)
			break;

		if (**ap != '\0')
			ap++, (*argc)++;
	}

	*ap	= 0;

	return /* void */;
} /* split() */


static char *ip_canonname(char *buf, size_t buflen, int *family, char *ip) {
	static const struct addrinfo hints =
		{ .ai_family = AF_UNSPEC, .ai_flags = AI_NUMERICHOST };
	struct addrinfo *res;
	struct sockaddr_storage ss;
	socklen_t sslen;

	if (0 != getaddrinfo(ip, NULL, &hints, &res))
		return 0;

	(void)memcpy(&ss, res->ai_addr, res->ai_addrlen);
	sslen	= res->ai_addrlen;

	freeaddrinfo(res);

	if (0 != getnameinfo((struct sockaddr *)&ss, sslen, buf, buflen, 0, 0, NI_NUMERICHOST))
		return 0;

	if (family)
		*family	= ss.ss_family;

	return buf;
} /* ip_canonname() */


/* -- ERROR STRINGS ------------------------------------------------------- */

const char *lookup_errlist[LOOKUP_NERR] = {
	/*
	 * The error codes are sparse, so initialize the array with a
	 * default string using C99 designated array initializers.
	 */
	[0 ... (LOOKUP_NERR - 1)]	= "Unknown",

	[LOOKUP_ESUCCESS]		= "Success",
	[LOOKUP_ESYSTEM]		= "System error",
	[LOOKUP_ETIMEDOUT]		= "Operation timed out",
	[LOOKUP_EBAD_FAMILY]		= "Bad socket address family",
	[LOOKUP_EBAD_RESPONSE]		= "Corrupt or badly formed DNS response",
	[LOOKUP_EARES]			= "C-Ares error",
	[LOOKUP_ENOTFOUND]		= "Specified host not found",
	[LOOKUP_WNOTFOUND]		= "Specified host not found",
	[LOOKUP_ENODATA]		= "Request name is valid but does not have an IP address",
	[LOOKUP_WNODATA]		= "Request name is valid but does not have an IP address",
	[LOOKUP_EBAD_REQUEST]		= "Request name is invalid (e.g. bad SRV syntax)",
	[LOOKUP_WBAD_REQUEST]		= "Request name is invalid (e.g. bad SRV syntax)",
	[LOOKUP_WEMPTY_RESPONSE]	= "Empty DNS response",
	[LOOKUP_WUNKNOWN_RESPONSE]	= "Unknown RR type in response",
	[LOOKUP_WDUPLICATE_RESPONSE]	= "Duplicate RR encountered",
};

const unsigned lookup_nerr	= LOOKUP_NERR;


/* -- STACK IPC ----------------------------------------------------------- */

struct lookup;
struct lookup_query;

#define LOOKUP_FRAME_PUSH(p, f)	do {					\
	(f)->xp	= &(p);							\
	SLIST_INSERT_HEAD(&(p)->frames, (f), sle);			\
} while(0)

#define LOOKUP_FRAME_OKAY(f)	(*(f)->xp != 0)

#define LOOKUP_FRAME_POP(p, f)	do {					\
	if (LOOKUP_FRAME_OKAY((f))) {					\
		assert((f) == SLIST_FIRST(&(p)->frames));		\
		SLIST_REMOVE_HEAD(&(p)->frames, sle);			\
	}								\
} while(0)

#define LOOKUP_FRAME_KILL(f)	(*(f)->xp = 0)

struct lookup_frame {
	struct lookup **xp;

	SLIST_ENTRY(lookup_frame) sle;
}; /* struct lookup_frame */

struct lookup_query_frame {
	struct lookup_query **xp;

	SLIST_ENTRY(lookup_query_frame) sle;
}; /* struct lookup_query_frame */


/* -- LOOKUP QUERY DEFINITIONS -------------------------------------------- */

/*
 * Macro's just give that "static" initialization feeling.
 */
#define LOOKUP_QUERY_INITIALIZER	(lookup_query_initializer)
#define LOOKUP_QUERY_INIT(lq)		lookup_query_init(lq)


/*
 * Query state machine cases. Power of two codes so state information
 * can be used in bit-wise expressions.
 */
enum lookup_query_state {
	LOOKUP_QUERY_INITIATE	= 0,
	LOOKUP_QUERY_A		= 1 << 0,
	LOOKUP_QUERY_AAAA	= 1 << 1,
	LOOKUP_QUERY_PTR	= 1 << 2,
	LOOKUP_QUERY_MX		= 1 << 3,
	LOOKUP_QUERY_CNAME	= 1 << 4,
	LOOKUP_QUERY_TXT	= 1 << 5,
	LOOKUP_QUERY_NS		= 1 << 6,
	LOOKUP_QUERY_MX_IP	= 1 << 7,
	LOOKUP_QUERY_NS_IP	= 1 << 8,
	LOOKUP_QUERY_SOA	= 1 << 9,
	LOOKUP_QUERY_SRV	= 1 << 10,
	LOOKUP_QUERY_SRV_IP	= 1 << 11,
}; /* enum lookup_query_state */


static const struct lookup_query {
	struct {
		unsigned int exec;
		unsigned int done;
		unsigned int next;
	} state;

	struct lookup *lookup;

	unsigned nchains;		/* Running count of CNAME chains. */

	enum lookup_errno status;
	struct ares_channel *channel;

	int type;

	char qname[NI_MAXHOST];
	size_t qnamelen;

	int flags;

	lookup_return cb;
	void *arg;
	
	struct timeval timeout;

	unsigned live_queries_made;

	LIST_HEAD(,lookup_rr) records;
	unsigned nrecords;

	LIST_HEAD(,lookup_rr) answers;
	unsigned nanswers;

	LIST_HEAD(,lookup_rr) additional;
	unsigned nadditional;
	
	LIST_HEAD(,lookup_rr) mxrecords;
	unsigned nmxrecords;

	LIST_HEAD(,lookup_rr) nsrecords;
	unsigned nnsrecords;

	LIST_HEAD(,lookup_rr) srvrecords;
	unsigned nsrvrecords;

	SLIST_HEAD(, lookup_query_frame) frames;

	LIST_ENTRY(lookup_query) le;
} lookup_query_initializer = {
	.state			= { .exec = LOOKUP_QUERY_INITIATE },
	.status			= LOOKUP_ESUCCESS,
	.live_queries_made	= 0,
};


static void lookup_query_init(struct lookup_query *lq) {
	*(lq)	= LOOKUP_QUERY_INITIALIZER;

	LIST_INIT(&(lq)->records);
	LIST_INIT(&(lq)->answers);
	LIST_INIT(&(lq)->additional);
	LIST_INIT(&(lq)->mxrecords);
	LIST_INIT(&(lq)->nsrecords);
	LIST_INIT(&(lq)->srvrecords);

	SLIST_INIT(&(lq)->frames);

	return /* void */;
} /* lookup_query_init() */


/* -- LOOKUP RESOURCE RECORD DEFINITIONS ---------------------------------- */

#define LOOKUP_RR_INITIALIZER	(lookup_rr_initializer)

static const struct lookup_rr lookup_rr_initializer = {
	.next		= NULL,
	.resolved	= 0,
	.followed	= 0,
	.alias		= NULL,
};


/* -- LOOKUP RESULT DEFINITIONS ------------------------------------------- */

#define LOOKUP_RESULT_INITIALIZER	(lookup_result_initializer)
#define LOOKUP_RESULT_INIT(r)		lookup_result_init(r)

static const struct lookup_result lookup_result_initializer;

static const struct lookup_result lookup_result_syserr = {
	.l_errno	= LOOKUP_ESYSTEM,
};

void lookup_result_init(struct lookup_result *r) {
	*r	= LOOKUP_RESULT_INITIALIZER;

	LIST_INIT(&r->l_answers);
	LIST_INIT(&r->l_additional);

	return /* void */;
} /* lookup_result_init() */


/* -- HOSTS(5) DATABASE DEFINITIONS --------------------------------------- */

#define HOSTS_INITIALIZER	(hosts_initializer)
#define HOSTS_INIT(h)		hosts_init(h)

struct hosts_entry {
	char host[NI_MAXHOST];
	size_t hostlen;

	int family;
	int alias;

	char address[64];
	size_t addresslen;

	LIST_ENTRY(hosts_entry) le;	
}; /* struct hosts_entry */


static const struct hosts {
	FILE *fp;

	time_t last_stat;

	struct stat st;

	LIST_HEAD(, hosts_entry) entries;
	unsigned int nentries;
} hosts_initializer;


static void hosts_init(struct hosts *h) {
	*h	= HOSTS_INITIALIZER;

	LIST_INIT(&h->entries);

	return /* void */;
} /* hosts_init() */


/* -- LOOKUP DEFINITIONS -------------------------------------------------- */

const struct lookup_options lookup_defaults = {
	.resolv		= STRINGIFY(LOOKUP_RESOLV_PATH),
	.hosts		= STRINGIFY(LOOKUP_HOSTS_PATH),
	.order		= { LOOKUP_METHOD_FILE, LOOKUP_METHOD_BIND },
#if LOOKUP_CACHE_IMPLEMENTED
	.cache_max	= LOOKUP_CACHE_MAX,
	.cache_map	= STRINGIFY(LOOKUP_CACHE_MAP),
#endif
	.chain_max	= LOOKUP_CHAIN_MAX,
	.query_max	= LOOKUP_QUERY_MAX,
	.nchannels	= LOOKUP_NCHANNELS,
};


#define LOOKUP_INIT(l)	do {						\
	*(l)	= lookup_initializer;					\
	CIRCLEQ_INIT(&(l)->ares.channels);				\
	LIST_INIT(&(l)->queries);					\
	SLIST_INIT(&(l)->frames);					\
} while(0)

static struct lookup {
	struct lookup_options opts;

	struct event_base *ev_base;

	const struct arena_prototype *ap;

	int sys_errno;
	int ares_errno;
	int lookup_errno;

	struct {	
		CIRCLEQ_HEAD(, ares_channel) channels;
		unsigned pending;
	} ares;

	struct hosts *hosts;

	LIST_HEAD(, lookup_query) queries;

	SLIST_HEAD(, lookup_frame) frames;
} lookup_initializer;

static struct lookup *lookup_local;


static int lookup_rtype2arpa(int arpa) {
	switch (arpa) {
	case LOOKUP_IN_PTR:
		return T_PTR;
	case LOOKUP_IN_A:
		return T_A;
	case LOOKUP_IN_AAAA:
		return T_AAAA;
	case LOOKUP_IN_CNAME:
		return T_CNAME;
	case LOOKUP_IN_NS:
		return T_NS;
	case LOOKUP_IN_MX:
		return T_MX;
	case LOOKUP_IN_TXT:
		return T_TXT;
	case LOOKUP_IN_SOA:
		return T_SOA;
	case LOOKUP_IN_SRV:
		return T_SRV;
	default:
		return -1;
	}

	/* NOT REACHED */
} /* lookup_rtype2arpa() */


/* -- ARES INTERFACE DEFINITIONS ------------------------------------------ */

/*
 * Borrow some raw packet accessors from ares_dns.h.
 */
#define DNS__16BIT(p)		(((p)[0] << 8) | (p)[1])
#define DNS__32BIT(p)		(((p)[0] << 24) | ((p)[1] << 16) | \
				 ((p)[2] << 8) | (p)[3])

#define DNS_HEADER_QDCOUNT(h)	DNS__16BIT((h) + 4)
#define DNS_HEADER_ANCOUNT(h)	DNS__16BIT((h) + 6)
#define DNS_HEADER_NSCOUNT(h)	DNS__16BIT((h) + 8)
#define DNS_HEADER_ARCOUNT(h)	DNS__16BIT((h) + 10)

#define DNS_RR_TYPE(r)		DNS__16BIT(r)
#define DNS_RR_CLASS(r)		DNS__16BIT((r) + 2)
#define DNS_RR_TTL(r)		DNS__32BIT((r) + 4)
#define DNS_RR_LEN(r)		DNS__16BIT((r) + 8)


struct ares_event {
	struct event ev;
	int fd;		/* EVENT_FD macro is broken. Keep own copy of fd. */
	short events;	/* Likewise for the events. */

	LIST_ENTRY(ares_event) le;
}; /* struct ares_event */

static const struct ares_channel {
	ares_channel channel;

	pid_t pid;

	struct lookup *lookup;

	unsigned pending;

	struct event timer;		/* Timer for lookup timeouts. */
	struct timeval timeout;		/* Our current timeout value. */

	LIST_HEAD(, ares_event) events;

	CIRCLEQ_ENTRY(ares_channel) cqe;
} ares_channel_initializer;

struct ares_thread {
	CIRCLEQ_HEAD(, ares_channel) live;
	CIRCLEQ_HEAD(, ares_channel) free;
}; /* struct ares_thread */

#ifdef USE_PTHREADS
static pthread_once_t ares_thread_once	= PTHREAD_ONCE_INIT;
static int ares_thread_errno;
static pthread_key_t ares_thread_key;
#else
static struct ares_thread ares_thread_local;
#endif /* USE_PTHREADS */

static struct ares_thread *ares_thread_fetch(void) {
#ifdef USE_PTHREADS
	struct ares_thread *t;

	if (0 == (t = pthread_getspecific(ares_thread_key))) {
		if (0 == (t = malloc(sizeof *t)))
			return 0;

		CIRCLEQ_INIT(&t->live);
		CIRCLEQ_INIT(&t->free);

		if (0 != pthread_setspecific(ares_thread_key, t)) {
			free(t);

			return 0;
		}
	}

	return t;
#else
	static struct ares_thread *t	= 0;

	if (t == 0) {
		t	= &ares_thread_local;

		CIRCLEQ_INIT(&t->live);
		CIRCLEQ_INIT(&t->free);
	}

	return t;
#endif /* USE_PTHREADS */
} /* ares_thread_fetch() */

static void ares_channel_reap(void) {
	static fd_set fds;
	struct ares_thread *t;
	struct ares_channel *c;

	if (!(t = ares_thread_fetch()))
		return /* void */;

	while (CIRCLEQ_END(&t->live) != (c = CIRCLEQ_FIRST(&t->live))) {
		CIRCLEQ_REMOVE(&t->live, c, cqe);

		assert(LIST_EMPTY(&c->events));
		assert(0 == ares_fds(c->channel, &fds, &fds));

		CIRCLEQ_INSERT_HEAD(&t->free, c, cqe);
	}

	return /* void */;	
} /* ares_channel_reap() */

static void ares_channel_init(struct ares_channel *c) {
	*c	= ares_channel_initializer;

	c->pid	= getpid();

	timerclear(&c->timeout);

	LIST_INIT(&c->events);

	return /* void */;
} /* ares_channel_init() */

static void ares_thread_init(void) {
#ifdef USE_PTHREADS
	if (0 != pthread_key_create(&ares_thread_key, 0))
		ares_thread_errno	= errno;
#endif

	return /* void */;	
} /* ares_thread_init() */

static struct ares_channel *ares_channel_open(struct lookup *l) {
	pid_t pid	= getpid();
	struct ares_thread *t;
	struct ares_channel *c;
	int sys_errno;

#ifdef USE_PTHREADS
	if (0 != pthread_once(&ares_thread_once, &ares_thread_init)) {
		return 0;
	} else if (ares_thread_errno != 0) {
		errno	= ares_thread_errno;

		return 0;
	}
#endif /* USE_PTHREADS */
	
	if (!(t = ares_thread_fetch()))
		return 0;

	/*
	 * Discard channels created in another process because we risk
	 * sharing socket descriptors and all manner of accompanying wierd
	 * behavior.
	 */
	while (CIRCLEQ_END(&t->free) != (c = CIRCLEQ_FIRST(&t->free))) {
		if (c->pid != pid) {
			CIRCLEQ_REMOVE(&t->free, c, cqe);

			ares_destroy(c->channel);

			free(c);
		} else
			break;
	}

	if (c == CIRCLEQ_END(&t->free)) {
		if (0 == (c = malloc(sizeof *c)))
			return 0;

		ares_channel_init(c);

		if (ARES_SUCCESS != ares_init(&c->channel)) {
			sys_errno	= errno;

			free(c);

			errno		= sys_errno;

			return 0;
		}
	} else
		CIRCLEQ_REMOVE(&t->free, c, cqe);

	c->lookup	= l;

	return c;
} /* ares_channel_open() */

static void ares_channel_close(struct ares_channel *c) {
	struct ares_event *e;
	struct ares_thread *t;

	while (LIST_END(&c->events) != (e = LIST_FIRST(&c->events))) {
		LIST_REMOVE(e, le);

		if (e->events)
			event_del(&e->ev), e->events = 0;

		c->lookup->ap->free(c->lookup->ap, e);
	}

	if (!(t = ares_thread_fetch()))	/* Oops! We'll leak this guy. */
		return /* void */;

	/*
	 * Place this guy on the live queue. We won't reap him until we
	 * fallback into the event loop, a moment where we can be relatively
	 * sure that no libares routines have live call frames.
	 */
	CIRCLEQ_INSERT_HEAD(&t->live, c, cqe);

	return /* void */; 
} /* ares_channel_close() */


/* -- HOSTS(5) DATABASE ROUTINES ------------------------------------------ */

static int lookup_name_isequal(const char *a, size_t alen, const char *b, size_t blen) {
	/* Trim trailing dots. */
	while (alen && a[alen] == '.')
		alen--;

	while (blen && b[blen] == '.')
		blen--;

	return (0 == strlcasecmp(a,alen,b,blen));
} /* lookup_name_isequal() */


static char *hosts_getline(FILE *fp, char **linep, size_t *linesz, char *comments) {
	int comment	= 0;
	struct { char *top, *pos, *end; } buf;
	int ch, eof;

	buf.top	= *linep;
	buf.pos	= buf.top;
	buf.end	= buf.top + *linesz;

	while (!(eof = (EOF == (ch = getc(fp))))) {
		if (ch == '\n')
			break;

		if (!comment && strchr(comments, ch))
			comment	= 1;

		if (comment)
			continue;
copyout:
		if (buf.pos >= buf.end) {
			size_t off	= buf.pos - buf.top;
			size_t siz	= 2 * MAX(*linesz, 1);

			if (!(buf.top = realloc(*linep, siz)))
				return 0;

			*linep	= buf.top;
			*linesz	= siz;
			buf.pos	= buf.top + off;
			buf.end	= buf.top + siz;
		}

		*(buf.pos++)	= ch;

		if (ch == '\0')
			return (**linep || !eof)? *linep : (char *)0;
	}

	ch	= '\0';

	goto copyout;
} /* hosts_getline() */


static struct hosts_entry *hosts_entry_init(struct hosts_entry *he, char *host, char *address, int alias) {
	he->host[sizeof he->host - 1]	= '\0';
	he->hostlen	= strlen(strncpy(he->host, host, sizeof he->host - 1));

	if (NULL == ip_canonname(he->address, sizeof he->address, &he->family, address))
		return 0;

	he->addresslen	= strlen(he->address);

	he->alias	= alias;

	return he;
} /* hosts_entry_init() */


static void hosts_close(struct lookup *l, struct hosts *h) {
	struct hosts_entry *he;

	if (!h)
		return /* void */;

	while ((he = LIST_FIRST(&h->entries))) {
		LIST_REMOVE(he, le);

		l->ap->free(l->ap, he);
	}

	if (h->fp)
		(void)fclose(h->fp);

	l->ap->free(l->ap, h);

	return /* void */;
} /* hosts_close() */


static struct hosts *hosts_reopen(struct lookup *l, const char *path, struct hosts *oh) {
	struct hosts *h	= 0;
	struct stat st;
	char *line	= 0;
	size_t linesiz	= 0;
	int sys_errno;
	time_t now;

	time(&now);

	if (oh && now - oh->last_stat < LOOKUP_HOSTS_CHECK_INTERVAL)
		return oh;

	if (0 != stat(path, &st))
		goto sysfail;

	if (oh != NULL
	&&  st.st_dev == oh->st.st_dev
	&&  st.st_ino == oh->st.st_ino
	&&  st.st_mtime == oh->st.st_mtime)
		return oh;

	if (!(h = l->ap->malloc(l->ap, sizeof *h, 0)))
		goto sysfail;

	HOSTS_INIT(h);

	if (!(h->fp = fopen(path, "r")))
		goto sysfail;

	if (0 != fstat(fileno(h->fp), &h->st))
		goto sysfail;

	h->last_stat	= now;

	while (hosts_getline(h->fp, &line, &linesiz, "#")) {
		char *argv[LOOKUP_HOSTS_ALIAS_MAX];
		int argc	= 0;

		split(&argc, argv, sizeof argv / sizeof *argv, line, " \t");

		if (argc >= 2) {
			struct hosts_entry *he;
			int i;

			for (i = 1; i < argc; i++) {
				if (!(he = l->ap->malloc(l->ap, sizeof *he, 0)))
					goto sysfail;

				if (hosts_entry_init(he, argv[i], argv[0], (i == 1))) {
					LIST_INSERT_HEAD(&h->entries, he, le);

					h->nentries++;
				} else { /* bad entry */
					l->ap->free(l->ap, he);
				}
			}
		}
	}

	free(line);

	hosts_close(l, oh);

	return h;
sysfail:
	sys_errno	= errno;

	free(line);

	hosts_close(l, h);

	errno	= sys_errno;

	return oh;
} /* hosts_reopen() */


static struct hosts *hosts_open(struct lookup *l, const char *path) {
	return hosts_reopen(l, path, NULL);
} /* hosts_open() */


static struct lookup_rr *hosts_lookup(struct lookup *l, char *qname, size_t qnamelen, int rtype) {
	LIST_HEAD(, lookup_rr ) answers	= LIST_HEAD_INITIALIZER(&answers);
	struct lookup_rr *r;
	struct hosts_entry *ent;
	char *octets[8];	/* TODO: 36 for ip6.arpa. */
	int noctets;
	char ptr[128];
	char ip[128];

	if (!l->hosts)
		return 0;

	switch (rtype) {
	case LOOKUP_IN_A:
		/* FALL THROUGH */
	case LOOKUP_IN_AAAA:
		LIST_FOREACH(ent, &l->hosts->entries, le) {
			if (!lookup_name_isequal(qname, qnamelen, ent->host, ent->hostlen)
			||  (ent->family == AF_INET6 && !(rtype & LOOKUP_IN_AAAA))
			||  (ent->family == AF_INET && !(rtype & LOOKUP_IN_A)))
				continue;

			if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
				goto memfail;

			*r		= LOOKUP_RR_INITIALIZER;
			r->type		= rtype;
			r->qnamelen	= strlcpy(r->qname, qname, sizeof r->qname);
			
			(void)memset(&r->rr.ip.sa.ss, '\0', sizeof r->rr.ip.sa.ss);

			if (ent->family == AF_INET6 && 0 < inet_pton(AF_INET6, ent->address, &r->rr.ip.sa.sin6.sin6_addr.s6_addr)) {
				r->rr.ip.sa.sa.sa_family	= AF_INET6;
				r->rr.ip.salen			= sizeof r->rr.ip.sa.sin6;
			} else if (ent->family == AF_INET && 0 < inet_pton(AF_INET, ent->address, &r->rr.ip.sa.sin.sin_addr.s_addr)) {
				r->rr.ip.sa.sa.sa_family	= AF_INET;
				r->rr.ip.salen			= sizeof r->rr.ip.sa.sin;
			} else {
				l->ap->free(l->ap, r);

				continue; /* bad entry? how'd that happen? */
			}

			LIST_INSERT_HEAD(&answers, r, le);
			r->next	= LIST_NEXT(r, le);
		} /* LIST_FOREACH(&l->hosts->entries) */

		break;
	case LOOKUP_IN_PTR:
		(void)strlcpy(ptr, qname, sizeof ptr);

		split(&noctets, octets, sizeof octets / sizeof *octets, ptr, ".");

		/* TODO: Support ip6.arpa lookups. */
		if (noctets != 6)
			break;

		(void)snprintf(ip, sizeof ip, "%s.%s.%s.%s", octets[3], octets[2], octets[1], octets[0]);

		LIST_FOREACH(ent, &l->hosts->entries, le) {
			if (ent->family != AF_INET || 0 != strcmp(ip, ent->address))
				continue;

			/* Skip the aliases, we only want the canonical names. */
			if (ent->alias)
				continue;

			if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
				goto memfail;

			*r	= LOOKUP_RR_INITIALIZER;
			r->type			= rtype;
			r->qnamelen		= strlcpy(r->qname, qname, sizeof r->qname);
			r->rr.ptr.hostlen	= strlcpy(r->rr.ptr.host, ent->host, sizeof r->rr.ptr.host);

			LIST_INSERT_HEAD(&answers, r, le);
			r->next	= LIST_NEXT(r, le);
		} /* LIST_FOREACH(&l->hosts->entries) */

		break;
	default:
		assert(0);
	}

	return (LIST_EMPTY(&answers))? NULL : LIST_FIRST(&answers);
memfail:
	/* FALL THROUGH */
/*sysfail:*/
	while (LIST_END(&answers) != (r = LIST_FIRST(&answers))) {
		LIST_REMOVE(r, le);

		l->ap->free(l->ap, r);
	}	

	return NULL;
} /* hosts_lookup() */


/* -- RESOLV.CONF(5) SETTINGS ROUTINES ------------------------------------ */

static int resolv_order(struct lookup *l, const char *path) {
	FILE *fp	= fopen(path, "r");
	char *line	= 0;
	size_t linesz	= 0;
	char *argv[8];
	int argc, i;

	if (!fp)
		return -1;

	while (hosts_getline(fp, &line, &linesz, "#;")) {
		split(&argc, argv, sizeof argv / sizeof *argv, line, " \t");

		if (!argc || 0 != strcasecmp(argv[0], "lookup"))
			continue;

		for (i = 1; i < argc; i++) {
			if (*argv[i] == 'f') {
				l->opts.order[0]	= LOOKUP_METHOD_FILE;
				l->opts.order[1]	= LOOKUP_METHOD_BIND;

				break;
			} else if (*argv[i] == 'b') {
				l->opts.order[0]	= LOOKUP_METHOD_BIND;
				l->opts.order[1]	= LOOKUP_METHOD_FILE;

				break;
			}
		}

		break;
	}

	free(line);

	return 0;
} /* resolv_order() */


/* -- C-ARES INTERFACE ROUTINES ------------------------------------------- */

/*
 * How do you interface Ares with libevent?
 *
 * Ares provides an interface, ares_fds(), to determine what sockets it is
 * interested in. This takes two fd_sets, in one it sets all the sockets it
 * is waiting to read from, and in the other all the sockets it is waiting
 * to write to. Also, ares_timeout() tells us when Ares wants to be woken to
 * timeout any outstanding requests.
 *
 * ares_catch_event() simply catches each socket event from the libevent
 * dispatcher and notifies Ares--using ares_process()--that a particular
 * socket is ready.
 *
 * ares_reset_events() is the meat of the interface, though. When invoked it
 * calls ares_fds(). While looping over over those sets there is an
 * inner-loop over an internal list of pending events. If there's a match
 * the pending event is put onto the live list. If there's no match then a
 * new event struct is created, set, inserted into the event loop and placed
 * on the live list. At the end of this process, we loop again over our
 * pending list to reap dead Ares events. The live list is then copied over
 * to the pending list.
 *
 * Zeroing and then [possibly] scanning 1K+ fd_sets on every lookup request
 * and I/O readiness event may turn out to be dreadfully slow. It would
 * probably be worth it to 1) teach Ares to deal with struct pollfd's (such
 * as ADNS does) or 2) teach the libevent interface to Ares.
 *
 */
static void ares_catch_event(int fd, short events, void *null);	/* Declare early */

static void ares_reset_events(struct lookup *l, struct ares_channel *c) {
	fd_set rd, wr;
	int fd, max;
	struct ares_event *ev, *nxt;
	int events;
	struct timeval timeout;
	LIST_HEAD(, ares_event) live;
	LIST_HEAD(, ares_event) dead;


	FD_ZERO(&rd);
	FD_ZERO(&wr);

	LIST_INIT(&live);
	LIST_INIT(&dead);

	max	= ares_fds(c->channel, &rd, &wr);

	fd	= -1;
next_fd:
	for (fd++; fd < max; fd++) {
		events	= 0;

		if (FD_ISSET(fd, &rd))
			events	|= EV_READ;
		
		if (FD_ISSET(fd, &wr))
			events	|= EV_WRITE;
			
		if (!events)
			continue;

		/* Check to see if we are already doing something w/ the
		 * descriptor. If so modify it appropriately
		 */
		for (ev = LIST_FIRST(&c->events); ev; ev = nxt) {
			nxt	= LIST_NEXT(ev, le);

			if (ev->fd == fd) {
				LIST_REMOVE(ev, le);

				if (ev->events == events) {
					LIST_INSERT_HEAD(&live, ev, le);

					goto next_fd;
				} else if (events) {
					#if 0
					assert(0 == event_del(&ev->ev));
					#else
					if (0 != event_del(&ev->ev) && errno != EBADF)
						warn("event_del");
					#endif

					LIST_INSERT_HEAD(&dead, ev, le);
				}

				break;
			}
		} /* LIST_FOREACH() */


		/*
		 * Apparently there were no previous events pending for this
		 * descriptor so we'll allocate new event for Ares and
		 * insert into the event loop.
		 */

		ev = LIST_FIRST(&dead);

		if (!ev) {
			ev	= l->ap->malloc(l->ap, sizeof *ev, 0);

			if (!ev) {
				warn("unable to allocate event");
				break;
				/*
				 * XXX: If we just silently forget the event
				 * for now, presumably when memory becomes
				 * available we'll pick it up.
				 */
			}
		} else
			LIST_REMOVE(ev, le);

		ev->fd		= fd;
		ev->events	= events;

		event_set(&ev->ev, ev->fd, ev->events | EV_PERSIST, ares_catch_event, c);

		if (l->ev_base)
			event_base_set(l->ev_base, &ev->ev);

		if (0 != event_add(&ev->ev, NULL)) {
			warn("error adding lookup event");
			LIST_INSERT_HEAD(&dead, ev, le);
		} else
			LIST_INSERT_HEAD(&live, ev, le);

	} /* for() */

	for (ev = LIST_FIRST(&c->events); ev; ev = nxt) {
		nxt	= LIST_NEXT(ev, le);

		#if 0
		assert(0 == event_del(&ev->ev));
		#else
		if (0 != event_del(&ev->ev) && errno != EBADF)
			warn("event_del");
		#endif

		LIST_REMOVE(ev, le);
		LIST_INSERT_HEAD(&dead, ev, le);
	}
	/*
	 * Anything left on the pending queue was dead.
	 */

	for (ev = LIST_FIRST(&live); ev; ev = nxt) {
		nxt	= LIST_NEXT(ev, le);
		
		LIST_REMOVE(ev, le);
		LIST_INSERT_HEAD(&c->events, ev, le);
	}
	/*LIST_FIRST(&lookups.ares.events)	= LIST_FIRST(&live); <-- Not correct */
	/* Copy live queue to pending queue */

	for (ev = LIST_FIRST(&dead); ev; ev = nxt) {
		nxt	= LIST_NEXT(ev, le);
	
		LIST_REMOVE(ev, le);

		l->ap->free(l->ap, ev);
	}
	/* Bury dead event struct's */

	timerclear(&timeout);

	(void)ares_timeout(c->channel, NULL, &timeout);
	/* Ask ares what timeout event it wants, but first zero
	 * the timeout struct because Ares won't set it to zero
	 * if it doesn't want a timeout
	 */

	#if 0	/* Make the timer more likely to fire prematurely */
	if (timerisset(&timeout))
		timeout.tv_usec = timeout.tv_sec * 100, timeout.tv_sec = 0;
	#endif

	if (!timercmp(&timeout, &c->timeout, ==)) {
		/* Ares wants a different timeout than previously set */

		if (timerisset(&c->timeout)) {
			/* We have an outstanding timeout. */

			assert(0 == evtimer_del(&c->timer));

			timerclear(&c->timeout);

			/* Removed outstanding timeout */
		}

		if (timerisset(&timeout)) {
			/* Ares wants a real timeout (not no timeout) */

			evtimer_set(&c->timer, ares_catch_event, c);

			assert (0 == evtimer_add(&c->timer, &timeout));

			c->timeout	= timeout;
		}
	}

	return /* void */;
} /* ares_reset_events() */


/*
 * Called from libevent. The event was submitted by ares_reset_events().
 *
 * fd		the descriptor with a waiting event
 * events	the waiting events. EV_READ, EV_WRITE or EV_TIMEOUT
 * null		reset_events() didn't pass a token to event_set()
 */
static void ares_catch_event(int fd, short events, void *arg) {
	struct lookup_frame f;
	struct ares_channel *c	= arg;
	struct lookup *l	= c->lookup;
	fd_set rd, wr;

	FD_ZERO(&rd);
	FD_ZERO(&wr);

	if (events & EV_READ)
		FD_SET(fd, &rd);

	if (events & EV_WRITE)
		FD_SET(fd, &wr);

	if (events & EV_TIMEOUT)
		timerclear(&c->timeout);

	LOOKUP_FRAME_PUSH(l, &f);

	ares_process(c->channel, &rd, &wr);

	if (LOOKUP_FRAME_OKAY(&f))
		ares_reset_events(c->lookup, c);

	LOOKUP_FRAME_POP(l, &f);

	/* Before we fall back into the event loop we can safely
	 * move closed ares channels from the live queue to the free
	 * queue. This is a hack to get around the fact that
	 * libares will access it's objects after a callback, ignoring
	 * the possibility that ares_destroy() could have been called.
	 *
	 * Similarly, we don't want to automcatically re-use such
	 * an object.
	 *
	 * So, we keep closed channels on the sidelines until we're fairly
	 * sure that there are no more live call frames running libares
	 * code.
	 */
	ares_channel_reap();

	return /* void */;
} /* ares_catch_event() */


static enum lookup_errno ares_errno_tr(int err) {
	switch (err) {
	case ARES_ETIMEOUT:
		return LOOKUP_ETIMEDOUT;
	case ARES_ENODATA:
		return LOOKUP_ENODATA;
	case ARES_ENOTFOUND:
		return LOOKUP_ENOTFOUND;
	default:	/* Catch all */
		return LOOKUP_EARES;
	}
} /* ares_errno_tr() */

/*
 * End interface to Ares library.
 */


static struct lookup_query *lookup_query_open(struct lookup *l, const char *qname, size_t qnamelen, int type, int flags, lookup_return cb, void *arg, struct timeval *timeout) {
	struct lookup_query *q	= NULL;
	int sys_errno;

	if (!(q = l->ap->malloc(l->ap, sizeof *q, 0)))
		return NULL;

	LOOKUP_QUERY_INIT(q);

	q->lookup	= l;

	if (qnamelen >= sizeof q->qname) {
		errno	= EINVAL;
		goto sysfail;
	}

	(void)memcpy(q->qname, qname, qnamelen);
	q->qnamelen		= qnamelen;
	q->qname[qnamelen]	= '\0';

	q->type		= type;
	q->flags	= flags;
	q->cb		= cb;
	q->arg		= arg;

	if (timeout)
		q->timeout	= *timeout;

	return q;
sysfail:
	sys_errno	= errno;

	l->ap->free(l->ap, q);

	errno		= sys_errno;

	return NULL;
} /* lookup_query_open() */


static void lookup_query_close(struct lookup *l, struct lookup_query *q, enum lookup_errno err) {
	struct { struct lookup_frame l; struct lookup_query_frame q; } f;
	struct { lookup_return fn; void *arg; } cb;
	struct lookup_rr *rr, *ans, *add;

	cb.fn	= q->cb;
	cb.arg	= q->arg;

	q->cb	= 0;	/* Make sure we _never_ return twice. */
	q->arg	= 0;

	LOOKUP_FRAME_PUSH(l, &f.l);
	LOOKUP_FRAME_PUSH(q, &f.q);

	LIST_FOREACH(rr, &q->answers, le)
		rr->next	= LIST_NEXT(rr, le);

	LIST_FOREACH(rr, &q->additional, le)
		rr->next	= LIST_NEXT(rr, le);

	if (cb.fn) {
		ans	= LIST_FIRST(&q->answers);
		add	= LIST_FIRST(&q->additional);

		/*
		 * If we're loaning them the memory, lose our references so
		 * we don't accidentally try to free it later.
		 */
		if (l->opts.loan_answers) {
			LIST_INIT(&q->answers);
			LIST_INIT(&q->additional);
		}

		cb.fn(q->nanswers, ans, q->nadditional, add, err, cb.arg);

		if (!LOOKUP_FRAME_OKAY(&f.l))
			return /* void */;
	}

	if (LOOKUP_FRAME_OKAY(&f.q)) {
		/* Remove ourselves from &l->queries */
		LIST_REMOVE(q, le);

		lookup_rr_free(l, LIST_FIRST(&q->answers));
		lookup_rr_free(l, LIST_FIRST(&q->additional));

		LOOKUP_FRAME_POP(q, &f.q);

		l->ap->free(l->ap, q);
	}

	LOOKUP_FRAME_POP(l, &f.l);

	return /* void */;
} /* lookup_query_close() */


static enum lookup_errno lookup_rr_push(struct lookup *l, struct lookup_query *lq, enum lookup_section section, unsigned char **apos, unsigned char *abuf, size_t alen) {
	struct lookup_rr *r	= l->ap->malloc(l->ap, sizeof *r, 0);
	char *qname		= NULL;	/* free(3)d at end. */
	size_t qnamelen		= 0;
	char *text		= NULL;	/* free(3)d at end. */
	size_t textlen		= 0;
	int lookup_errno	= LOOKUP_ESUCCESS;
	int type, class, ttl;
	long dlen, plen;
	int ares_errno;
	struct lookup_rr *ri;
	unsigned char *p;


	/* Initialize RR object. */
	if (!r)
		return l->sys_errno = errno, LOOKUP_ESYSTEM;

	*r		= lookup_rr_initializer;
	r->section	= section;

	/* Expand question. */
	ares_errno	= ares_expand_name(*apos, abuf, alen, &qname, &dlen);

	if (ares_errno != ARES_SUCCESS)
		goto ares_fail;

	qnamelen	= strlen(qname);

	if (qnamelen >= sizeof r->qname) {
		lookup_errno	= LOOKUP_WBAD_RESPONSE;
		goto fail;
	}

	(void)memcpy(r->qname, qname, qnamelen);
	r->qnamelen		= qnamelen;
	r->qname[qnamelen]	= '\0';

	*apos	+= dlen;


	/* Parse generic RR data. */
	if (*apos + RRFIXEDSZ > abuf + alen) {
		lookup_errno	= LOOKUP_EBAD_RESPONSE;
		goto fail;
	}

	type	= DNS_RR_TYPE(*apos);
	class	= DNS_RR_CLASS(*apos);
	ttl	= DNS_RR_TTL(*apos);
	dlen	= DNS_RR_LEN(*apos);

	*apos	+= RRFIXEDSZ;

	if (*apos + dlen > abuf + alen) {
		lookup_errno	= LOOKUP_EBAD_RESPONSE;
		goto fail;
	}

	r->ttl	= MAX(0, ttl);

	/* Parse RR specific data. */
	switch (type) {
	case T_PTR:
		r->type	= LOOKUP_IN_PTR;

		ares_errno	= ares_expand_name(*apos, abuf, alen, &text, &(long){ 0 });

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.ptr.host) {
			lookup_errno	= LOOKUP_WBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.ptr.host, text, textlen);
		r->rr.ptr.hostlen	= textlen;
		r->rr.ptr.host[textlen]	= '\0';

		break;
	case T_MX:
		r->type	= LOOKUP_IN_MX;

		if (dlen < 2) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		r->rr.mx.preference	= ntohs(*(unsigned short *)memcpy(&r->rr.mx.preference, *apos, 2));

		ares_errno	= ares_expand_name(*apos + 2, abuf, alen, &text, &(long){ 0 });

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.mx.host) {
			lookup_errno	= LOOKUP_WBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.mx.host, text, textlen);
		r->rr.mx.hostlen	= textlen;
		r->rr.mx.host[textlen]	= '\0';
		
		break;
	case T_A:
		r->type	= LOOKUP_IN_A;

		if (dlen != sizeof r->rr.ip.sa.sin.sin_addr.s_addr) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		(void)memset(&r->rr.ip.sa.sin, '\0', sizeof r->rr.ip.sa.sin);

		r->rr.ip.sa.sin.sin_family	= AF_INET;

		(void)memcpy(&r->rr.ip.sa.sin.sin_addr.s_addr, *apos, dlen);

		r->rr.ip.salen	= sizeof r->rr.ip.sa.sin;

		break;
	case T_AAAA:
		r->type	= LOOKUP_IN_AAAA;

		if (dlen != sizeof r->rr.ip.sa.sin6.sin6_addr.s6_addr) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		(void)memset(&r->rr.ip.sa.sin6, '\0', sizeof r->rr.ip.sa.sin6);

		r->rr.ip.sa.sin6.sin6_family	= AF_INET6;

		(void)memcpy(&r->rr.ip.sa.sin6.sin6_addr.s6_addr, *apos, dlen);

		r->rr.ip.salen	= sizeof r->rr.ip.sa.sin6;

		break;
	case T_CNAME:
		r->type	= LOOKUP_IN_CNAME;

		ares_errno	= ares_expand_name(*apos, abuf, alen, &text, &(long){ 0 });

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.cname.host) {
			lookup_errno	= LOOKUP_WBAD_RESPONSE;
			goto fail;
		}

		LIST_FOREACH(ri, &lq->records, le) {
			if (ri->type == r->type
			&&  ri->rr.cname.hostlen == textlen
			&&  0 == strncasecmp(ri->rr.cname.host, text, textlen)) {
				lookup_errno	= LOOKUP_WDUPLICATE_RESPONSE;

				goto skip;
			}
		}

		(void)memcpy(r->rr.cname.host, text, textlen);
		r->rr.cname.hostlen		= textlen;
		r->rr.cname.host[textlen]	= '\0';

		break;
	case T_TXT:
		r->type	= LOOKUP_IN_TXT;

		r->rr.txt.data[0]	= '\0';
		r->rr.txt.datalen	= 0;

		p	= *apos;

		while (p < *apos + dlen) {
			textlen	= *p;

			p++;

			if (p + textlen > *apos + dlen) {
				lookup_errno	= LOOKUP_EBAD_RESPONSE;
				goto fail;
			}

			/* FIXME: TXT data should be dynamically allocated. */
			if (r->rr.txt.datalen + textlen >= sizeof r->rr.txt.data) {
				lookup_errno	= LOOKUP_WBAD_RESPONSE;
				goto fail;
			}

			/* XXX: Should we insert spaces between TXT strings? */

			(void)memcpy(r->rr.txt.data + r->rr.txt.datalen, p, textlen);
			r->rr.txt.datalen			+= textlen;
			r->rr.txt.data[r->rr.txt.datalen]	= '\0';

			p	+= textlen;
		}

		break;
	case T_NS:
		r->type	= LOOKUP_IN_NS;

		ares_errno	= ares_expand_name(*apos, abuf, alen, &text, &(long){ 0 });

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.ns.host) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.ns.host, text, textlen);
		r->rr.ns.hostlen	= textlen;
		r->rr.ns.host[textlen]	= '\0';

		break;
	case T_SOA:
		r->type	= LOOKUP_IN_SOA;

		p	= *apos;

		ares_errno	= ares_expand_name(p, abuf, alen, &text, &plen);

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.soa.host) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.soa.host, text, textlen);
		r->rr.soa.hostlen	= textlen;
		r->rr.soa.host[textlen]	= '\0';

		free(text);
		text	= NULL;

		p	+= plen;

		if (!(p < *apos + dlen)) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		ares_errno	= ares_expand_name(p, abuf, alen, &text, &plen);

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.soa.mbox) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.soa.mbox, text, textlen);
		r->rr.soa.mboxlen	= textlen;
		r->rr.soa.mbox[textlen]	= '\0';

		free(text);
		text	= NULL;

		p	+= plen;

		if (p + 20 > *apos + dlen) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		r->rr.soa.serial	= (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
		r->rr.soa.refresh	= (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
		r->rr.soa.retry		= (p[8] << 24) | (p[9] << 16) | (p[10] << 8) | p[11];
		r->rr.soa.expire	= (p[12] << 24) | (p[13] << 16) | (p[14] << 8) | p[15];
		r->rr.soa.minimum	= (p[16] << 24) | (p[17] << 16) | (p[18] << 8) | p[19];

		break;
	case T_SRV:
		r->type	= LOOKUP_IN_SRV;

		if (dlen < 6) {
			lookup_errno	= LOOKUP_EBAD_RESPONSE;
			goto fail;
		}

		r->rr.srv.priority	= ntohs(*(unsigned short *)memcpy(&r->rr.srv.priority, *apos, 2));
		r->rr.srv.weight	= ntohs(*(unsigned short *)memcpy(&r->rr.srv.weight, *apos + 2, 2));
		r->rr.srv.port		= ntohs(*(unsigned short *)memcpy(&r->rr.srv.port, *apos + 4, 2));

		ares_errno	= ares_expand_name(*apos + 6, abuf, alen, &text, &(long){ 0 });

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= strlen(text);

		if (textlen >= sizeof r->rr.srv.host) {
			lookup_errno	= LOOKUP_WBAD_RESPONSE;
			goto fail;
		}

		(void)memcpy(r->rr.srv.host, text, textlen);
		r->rr.srv.hostlen	= textlen;
		r->rr.srv.host[textlen]	= '\0';

		break;
	default:
		lookup_errno	= LOOKUP_WUNKNOWN_RESPONSE;

		goto skip;
	}

	LIST_INSERT_HEAD(&lq->records, r, le);
	lq->nrecords++;

	r	= NULL;
skip:
	if (r)
		l->ap->free(l->ap, r);

	free(qname);
	free(text);

	*apos	+= dlen;

	return lookup_errno;
ares_fail:
	l->ares_errno	= ares_errno;
	lookup_errno	= ares_errno_tr(ares_errno);
fail:
	l->sys_errno	= errno;

	if (r)
		l->ap->free(l->ap, r);

	free(qname);
	free(text);

	return lookup_errno;
} /* lookup_rr_push() */


/*
 * Iterate over the raw DNS response, using lookup_rr_push() to parse the
 * RR's from each section.
 */
static enum lookup_errno lookup_parse_reply(struct lookup *l, struct lookup_query *lq, unsigned char *abuf, int alen) {
	int lookup_errno	= LOOKUP_ESUCCESS;
	unsigned char *apos	= abuf;
	char *text		= NULL;
	size_t textlen		= 0;
	int qdc, anc, nsc, arc;
	int ares_errno;


	qdc	= DNS_HEADER_QDCOUNT(apos);
	anc	= DNS_HEADER_ANCOUNT(apos);
	nsc	= DNS_HEADER_NSCOUNT(apos);
	arc	= DNS_HEADER_ARCOUNT(apos);

	/* Continue even if anc == 0 so the caller can see if any
	 * authority/nameserver or additional records were returned.
	 */
	if (!qdc || 0 == anc + nsc + arc)
		return LOOKUP_WEMPTY_RESPONSE;
	
	apos	+= HFIXEDSZ;	/* Skip over DNS header. */


	/* Skip over question section. We need to expand the name and free
	 * it to get at the length of each RR in the section.
	 */
	for (; qdc; qdc--) {
		long len;

		ares_errno	= ares_expand_name(apos, abuf, alen, &text, &len);

		if (ares_errno != ARES_SUCCESS)
			goto ares_fail;

		textlen	= len;

		free(text), text = NULL;

		apos	+= textlen + QFIXEDSZ;

		if (apos > abuf + alen) {
			lookup_errno	= LOOKUP_WBAD_RESPONSE;
			goto fail;
		}
	} /* Question */


	/* Iterate over answer section. */
	for (; anc; anc--) {
		lookup_errno	= lookup_rr_push(l, lq, LOOKUP_SECTION_ANSWERS, &apos, abuf, alen);

		if (LOOKUP_FAILURE(lookup_errno))
			goto fail;
	} /* Answers */


	/* Iterate over NS section. */
	for (; nsc; nsc--) {
		lookup_errno	= lookup_rr_push(l, lq, LOOKUP_SECTION_AUTHORITY, &apos, abuf, alen);

		if (LOOKUP_FAILURE(lookup_errno))
			goto fail;
	}


	/* Iterate over additional "glue" section. */
	for (; arc; arc--) {
		lookup_errno	= lookup_rr_push(l, lq, LOOKUP_SECTION_ADDITIONAL, &apos, abuf, alen);

		if (LOOKUP_FAILURE(lookup_errno))
			goto fail;
	}

	free(text);

	return lookup_errno;
ares_fail:
	l->ares_errno	= ares_errno;
	lookup_errno	= LOOKUP_E2WARNING(ares_errno_tr(ares_errno));
fail:
	free(text);

	return lookup_errno;
} /* lookup_parse_reply() */


static void lookup_chain_cname(struct lookup *l, struct lookup_query *lq, int rtype) {
	struct lookup_rr *c, *r;

	LIST_FOREACH(c, &lq->records, le) {
		if (c->type != LOOKUP_IN_CNAME || c->resolved)
			continue;

		/* Check our available records first for terminal RR type. */
		LIST_FOREACH(r, &lq->records, le) {
			if ((r->type & rtype)
			&&  lookup_name_isequal(r->qname, r->qnamelen, c->rr.cname.host, c->rr.cname.hostlen)) {
				r->alias	= c;
				c->resolved	= 1;
				/*break;*/ /* Can have multiple RR's to answers a CNAME. */
			}
		}

		/* Then check our available records for a CNAME. */
		LIST_FOREACH(r, &lq->records, le) {
			if (r->type == LOOKUP_IN_CNAME
			&&  lookup_name_isequal(r->qname, r->qnamelen, c->rr.cname.host, c->rr.cname.hostlen)) {
				r->alias	= c;
				c->resolved	= 1;
				/*break;*/ /* Can we have multiple CNAME's to answers a CNAME? */
			}
		}
	}

	return /* void */;
} /* lookup_chain_cname() */


static void lookup_resolve_answers(struct lookup *l, struct lookup_query *q, int rtype) {
	struct lookup_rr *r, *c, *n;

	for (r = LIST_FIRST(&q->records); r; r = n) {
		n	= LIST_NEXT(r, le);

		if (!(r->type & rtype)) {
			LIST_REMOVE(r, le);
			q->nrecords--;
			
			LIST_INSERT_HEAD(&q->additional, r, le);
			q->nadditional++;

			continue;
		}

		/* An easy match? */
		if (!lookup_name_isequal(q->qname, q->qnamelen, r->qname, r->qnamelen)) {
			/* Nope. Try to trace CNAME to original query. */
			for (c = r; c->alias; c = c->alias)
				;;

			if (c == r || !lookup_name_isequal(q->qname, q->qnamelen, c->qname, c->qnamelen)) {
				LIST_REMOVE(r, le);
				q->nrecords--;
			
				LIST_INSERT_HEAD(&q->additional, r, le);
				q->nadditional++;

				continue;	/* Keep looking for answers. */
			}
		}

		LIST_REMOVE(r, le);
		q->nrecords--;

		LIST_INSERT_HEAD(&q->answers, r, le);
		q->nanswers++;
	}

#if 0
	/* Remove any duplicates from additional. */
	LIST_FOREACH(r, &l->additional, le) {
		for (c = LIST_NEXT(r, le); c; c = n) {
			n	= LIST_NEXT(c, le);

			if (c->type == r->type && lookup_name_isequal(c->qname, c->qnamelen, r->qname, r->qnamelen)) {
				LIST_REMOVE(c, le);
				q->nadditional--;

				l->ap->free(l->ap, c);
			}
		}
	}
#endif 	
	return /* void */;
} /* lookup_resolve_answers() */


static int lookup_additional_query(struct lookup *l, struct lookup_query *q, char *qname, size_t qnamelen, enum lookup_type rtype) {
	int n	= 0;
	struct lookup_rr *i, *r;

	/* Find matching answers. */
	LIST_FOREACH(i, &q->additional, le) {
		if (i->type == rtype && lookup_name_isequal(qname, qnamelen, i->qname, i->qnamelen)) {
			if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
				return -1;

			*r		= *i;
			r->resolved	= 0;
			r->followed	= 0;
			r->alias	= 0;

			LIST_INSERT_HEAD(&q->records, r, le);
			q->nrecords++;
			n++;
		}
	}

	if (n)
		return n;

	/* If no matching record, look for CNAME. */
	LIST_FOREACH(i, &q->additional, le) {
		if (i->type == LOOKUP_IN_CNAME && lookup_name_isequal(qname, qnamelen, i->qname, i->qnamelen)) {
			if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
				return -1;

			*r		= *i;
			r->resolved	= 0;
			r->followed	= 0;
			r->alias	= 0;

			LIST_INSERT_HEAD(&q->records, r, le);
			q->nrecords++;
			n++;

		}
	}

	return n;
} /* lookup_additional_query() */


static void lookup_catch_ares(void *, int, unsigned char *, int);

static void lookup_issue_query(struct lookup *l, struct lookup_query *q, const char *qname, size_t qnamelen, int rtype) {
	struct ares_channel *c;

	assert(c = CIRCLEQ_FIRST(&l->ares.channels));

	CIRCLEQ_REMOVE(&l->ares.channels, c, cqe);
	CIRCLEQ_INSERT_TAIL(&l->ares.channels, c, cqe);

	l->ares.pending++;
	c->pending++;
	q->channel	= c;

	if (l->opts.query_max != 0 && q->live_queries_made >= l->opts.query_max) {
		lookup_catch_ares(q, ARES_ENODATA, NULL, 0);
	} else {
		++q->live_queries_made;
		ares_query(c->channel, qname, C_IN, lookup_rtype2arpa(rtype), lookup_catch_ares, q);
	}

	ares_reset_events(l, c);

#if 0
	if (l->ares.pending > 25)
		event_loop(EVLOOP_ONCE);
#endif

	return /* void */;
} /* lookup_issue_query() */


static void lookup_process(struct lookup *l, struct lookup_query *q) {
	struct lookup_rr *c, *r, *n;

nextstate:
	switch (q->state.exec) {
	case LOOKUP_QUERY_INITIATE:
		if (q->type & LOOKUP_IN_PTR)
			q->state.exec	= LOOKUP_QUERY_PTR;
		else if (q->type & LOOKUP_IN_MX)
			q->state.exec	= LOOKUP_QUERY_MX;
		else if (q->type & LOOKUP_IN_TXT)
			q->state.exec	= LOOKUP_QUERY_TXT;
		else if (q->type & LOOKUP_IN_SOA)
			q->state.exec	= LOOKUP_QUERY_SOA;
		else if (q->type & LOOKUP_IN_SRV)
			q->state.exec	= LOOKUP_QUERY_SRV;
		else if (q->type & LOOKUP_IN_CNAME)
			q->state.exec	= LOOKUP_QUERY_CNAME;
		else if (q->type & LOOKUP_IN_NS)
			q->state.exec	= LOOKUP_QUERY_NS;
		else if (q->type & (LOOKUP_IN_A | LOOKUP_IN_AAAA)) {
			if (q->type & LOOKUP_IN_A)
				q->state.exec	|= LOOKUP_QUERY_A;
			if (q->type & LOOKUP_IN_AAAA)
				q->state.exec	|= LOOKUP_QUERY_AAAA;
		} else
			assert(0);

		goto nextstate;

		/* NOT REACHED */
	case LOOKUP_QUERY_NS:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			if (0 < lookup_additional_query(l, q, q->qname, q->qnamelen, q->type))
				goto nextstate;

			lookup_issue_query(l, q, q->qname, q->qnamelen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			break;
		}

		lookup_chain_cname(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		LIST_FOREACH(c, &q->records, le) {
			if (c->type != LOOKUP_IN_CNAME || c->resolved
			||  c->followed || q->nchains > l->opts.chain_max)
				continue;

			q->nchains++;
			c->followed	= 1;

			lookup_issue_query(l, q, c->rr.cname.host, c->rr.cname.hostlen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			goto break0;
		}

		lookup_resolve_answers(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		if (q->nanswers) {
			if ((q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))) {
				q->state.exec	= LOOKUP_QUERY_NS_IP;
				goto nextstate;
			}
		} else if (!q->nanswers) {
			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_NS_IP:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			/* Move NS records to another queue. */
			for (r = LIST_FIRST(&q->answers); r; r = n) {
				n	= LIST_NEXT(r, le);

				LIST_REMOVE(r, le);
				q->nanswers--;

				LIST_INSERT_HEAD(&q->nsrecords, r, le);
				q->nnsrecords++;
			}
		}

		while ((r = LIST_FIRST(&q->nsrecords))) {
			if (!r->resolved) {
				r->resolved	= 1;
				q->qnamelen	= strlcpy(q->qname, r->rr.ns.host, sizeof q->qname);
				q->state.next	= q->state.exec;
				q->state.exec	= 0;

				if (q->type & LOOKUP_IN_A)
					q->state.exec	|= LOOKUP_QUERY_A;
				if (q->type & LOOKUP_IN_AAAA)
					q->state.exec	|= LOOKUP_QUERY_AAAA;

				q->state.done	&= ~q->state.exec;

				goto nextstate;
			}

			LIST_REMOVE(r, le);
			q->nnsrecords--;

			LIST_INSERT_HEAD(&q->answers, r, le);
			q->nanswers++;
		}

		if (!q->nanswers) {
			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_TXT:
		/* FALL THROUGH */
	case LOOKUP_QUERY_SOA:
		/* FALL THROUGH */
	case LOOKUP_QUERY_PTR:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			if (q->type == LOOKUP_IN_PTR && l->opts.order[0] == LOOKUP_METHOD_FILE
			&& (r = hosts_lookup(l, q->qname, q->qnamelen, q->type))) {
				do {
					LIST_INSERT_HEAD(&q->records, r, le);
					q->nrecords++;
				} while ((r = r->next));
			} else if (0 < lookup_additional_query(l, q, q->qname, q->qnamelen, q->type)) {
				goto nextstate;
			} else {
				lookup_issue_query(l, q, q->qname, q->qnamelen, q->type);

				break;
			}
		} else {
			/*
			 * Follow/link CNAME's. A CNAME chain for PTR's looks like
			 * "3.4.1.2.in-addr.arpa IN CNAME 1.2.3.4.in-addr.arpa"
			 */
			lookup_chain_cname(l,q, q->type);

			/* Query any unresolved chains. */
			LIST_FOREACH(c, &q->records, le) {
				if (c->type != LOOKUP_IN_CNAME || c->resolved
				||  c->followed || q->nchains > l->opts.chain_max)
					continue;

				/* Do a PTR query on the CNAME. */
				q->nchains++;
				c->followed	= 1;

				lookup_issue_query(l, q, c->rr.cname.host, c->rr.cname.hostlen, q->type);

				goto break0;
			}
		}

		/*
		 * Attempt to resolve our answer, following aliases
		 * backwards to verify any CNAME chains.
		 */
		lookup_resolve_answers(l, q, q->type);
		
		if (!q->nanswers) {
			if (q->type == LOOKUP_IN_PTR && l->opts.order[1] == LOOKUP_METHOD_FILE
			&& (r = hosts_lookup(l, q->qname, q->qnamelen, q->type))) {
				do {
					LIST_INSERT_HEAD(&q->records, r, le);
					q->nrecords++;
				} while ((r = r->next));

				if (LOOKUP_FAILURE(q->status)) {
					q->status	= LOOKUP_E2WARNING(q->status);
				}
			} else if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_A|LOOKUP_QUERY_AAAA:
		/* Since answers get pushed onto a stack, for A/AAAA
		 * queries issue them in reverse.
		 */
		if (q->flags & LOOKUP_PREFER_A)
			q->state.exec	= LOOKUP_QUERY_AAAA;
		else /* LOOKUP_PREFER_AAAA */
			q->state.exec	= LOOKUP_QUERY_A;

		/* FALL THROUGH */
	case LOOKUP_QUERY_A:
		/* FALL THROUGH */
	case LOOKUP_QUERY_AAAA:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			if (l->opts.order[0] == LOOKUP_METHOD_FILE
			&& (r = hosts_lookup(l, q->qname, q->qnamelen, (q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA))) {
				do {
					LIST_INSERT_HEAD(&q->records, r, le);
					q->nrecords++;
				} while ((r = r->next));
			} else if (0 < lookup_additional_query(l, q, q->qname, q->qnamelen, (q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA)) {
				goto nextstate;
			} else {
				lookup_issue_query(l, q, q->qname, q->qnamelen, (q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA);

				break;
			}
		} else {
			lookup_chain_cname(l, q, (q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA);

			LIST_FOREACH(c, &q->records, le) {
				if (c->type != LOOKUP_IN_CNAME || c->resolved
				||  c->followed || q->nchains > l->opts.chain_max)
					continue;

				q->nchains++;
				c->followed	= 1;

				lookup_issue_query(l, q, c->rr.cname.host, c->rr.cname.hostlen,(q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA);

				goto break0;
			}
		}

		lookup_resolve_answers(l, q, (q->state.exec == LOOKUP_QUERY_A)? LOOKUP_IN_A : LOOKUP_IN_AAAA);


		/* Switch to next A/AAAA query type if both requested. */
		if ((LOOKUP_IN_A|LOOKUP_IN_AAAA) == (q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))
		&&  (LOOKUP_QUERY_A|LOOKUP_QUERY_AAAA) != (q->state.done & (LOOKUP_QUERY_A|LOOKUP_QUERY_AAAA))) {
			q->state.exec	= (LOOKUP_QUERY_A|LOOKUP_QUERY_AAAA) & ~q->state.exec;
			goto nextstate;
		}

		if (!q->nanswers) {
			int found	= 0;

			if (l->opts.order[1] == LOOKUP_METHOD_FILE) {
				int rtype	= q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA);
				int qtype;
	
				while (rtype && !found) {
					if (rtype == (LOOKUP_IN_A|LOOKUP_IN_AAAA))
						qtype	= (q->flags & LOOKUP_PREFER_A)
							? LOOKUP_IN_A
							: LOOKUP_IN_AAAA;
					else
						qtype	= rtype;

					r	= hosts_lookup(l, q->qname, q->qnamelen, qtype);

					for (found = !!r; r != 0; r = r->next) {
						LIST_INSERT_HEAD(&q->answers, r, le);
						q->nanswers++;
					}

					rtype &= ~qtype;
				}
			}

			if (found) {
				if (LOOKUP_FAILURE(q->status)) {
					q->status	= LOOKUP_E2WARNING(q->status);
				}
			} else if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		if (q->state.next) {
			q->state.exec	= q->state.next;
			goto nextstate;		
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_CNAME:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			lookup_issue_query(l, q, q->qname, q->qnamelen, q->type);

			break;
		}

		lookup_resolve_answers(l, q, q->type);
		
		if (!q->nanswers) {
			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_MX:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			lookup_issue_query(l, q, q->qname, q->qnamelen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			break;
		}

		lookup_chain_cname(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		LIST_FOREACH(c, &q->records, le) {
			if (c->type != LOOKUP_IN_CNAME || c->resolved
			||  c->followed || q->nchains > l->opts.chain_max)
				continue;

			q->nchains++;
			c->followed	= 1;

			lookup_issue_query(l, q, c->rr.cname.host, c->rr.cname.hostlen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			goto break0;
		}

		lookup_resolve_answers(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		if (q->nanswers) {
			int swapped;

			/* Bubble Sort MX by preference. */
			do {
				swapped	= 0;

				LIST_FOREACH(r, &q->answers, le) {
					n	= LIST_NEXT(r, le);

					if (n && n->rr.mx.preference < r->rr.mx.preference) {
						LIST_REMOVE(n, le);
						LIST_INSERT_BEFORE(r, n, le);

						swapped	= 1;
					}
				}
			} while (swapped);

			if ((q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))) {
				q->state.exec	= LOOKUP_QUERY_MX_IP;
				goto nextstate;
			}
		} else {
			/* If no MX records where found, but the user wants
			 * to automagically drop back to A/AAAA, jump to an
			 * A/AAAA query.
			 */
			if ((q->flags & LOOKUP_FALLBACK)
			&&  (q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))) {
				q->state.exec	= 0;

				if (q->type & LOOKUP_IN_A)
					q->state.exec	|= LOOKUP_QUERY_A;
				if (q->type & LOOKUP_IN_AAAA)
					q->state.exec	|= LOOKUP_QUERY_AAAA;

				goto nextstate;
			}

			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_MX_IP:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			/* Move MX records to another queue. */
			for (r = LIST_FIRST(&q->answers); r; r = n) {
				n	= LIST_NEXT(r, le);

				LIST_REMOVE(r, le);
				q->nanswers--;

				LIST_INSERT_HEAD(&q->mxrecords, r, le);
				q->nmxrecords++;
			}
		}

		while ((r = LIST_FIRST(&q->mxrecords))) {
			if (!r->resolved) {
				r->resolved	= 1;
				q->qnamelen	= strlcpy(q->qname, r->rr.mx.host, sizeof q->qname);
				q->state.next	= q->state.exec;
				q->state.exec	= 0;

				if (q->type & LOOKUP_IN_A)
					q->state.exec	|= LOOKUP_QUERY_A;
				if (q->type & LOOKUP_IN_AAAA)
					q->state.exec	|= LOOKUP_QUERY_AAAA;

				q->state.done	&= ~q->state.exec;

				goto nextstate;
			}

			LIST_REMOVE(r, le);
			q->nmxrecords--;

			LIST_INSERT_HEAD(&q->answers, r, le);
			q->nanswers++;
		}

		if (!q->nanswers) {
			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_SRV:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			lookup_issue_query(l, q, q->qname, q->qnamelen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			break;
		}

		lookup_chain_cname(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		LIST_FOREACH(c, &q->records, le) {
			if (c->type != LOOKUP_IN_CNAME || c->resolved
			||  c->followed || q->nchains > l->opts.chain_max)
				continue;

			q->nchains++;
			c->followed	= 1;

			lookup_issue_query(l, q, c->rr.cname.host, c->rr.cname.hostlen, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

			goto break0;
		}

		lookup_resolve_answers(l, q, q->type & ~(LOOKUP_IN_A|LOOKUP_IN_AAAA));

		if (q->nanswers) {
			int swapped;
			unsigned int seed;
			unsigned long sum, low; 

			/* Bubble Sort SRV by priority according to RFC 2782 S. 5. */
			do {
				swapped	= 0;

				LIST_FOREACH(r, &q->answers, le) {
					if (!(n = LIST_NEXT(r, le)))
						continue;

					/* "...except that all those with
					 * weight 0 are placed at the
					 * beginning of the list."
					 */
					if (n->rr.srv.priority < r->rr.srv.priority
					||  (n->rr.srv.priority == r->rr.srv.priority && n->rr.srv.weight == 0 && r->rr.srv.weight != 0)) {
						LIST_REMOVE(n, le);
						LIST_INSERT_BEFORE(r, n, le);

						swapped	= 1;
					}
				}
			} while (swapped);

			/* Sort SRV by weight according to RFC 2782 S. 5. */
			seed	= time(NULL);

			LIST_FOREACH(r, &q->answers, le) {
				sum	= 0;

				/* "Compute the sum of the weights of those
				 * RRs, and with each RR associate the
				 * running sum in the selected order."
				 */
				for (n = r; n && n->rr.srv.priority == r->rr.srv.priority; n = LIST_NEXT(n, le)) {
					sum		+= n->rr.srv.weight;
					n->srvsum	= sum;
				}

				/* If the sum is zero then move on because
				 * everything has a weight of zero and also
				 * because we'll get a floating point
				 * exception with the modulo operation
				 * below.
				 */
				if (!sum)
					continue;

				/* "Then choose a uniform random number
				 * between 0 and the sum computed
				 * (inclusive)..."
				 */
				low	= rand_r(&seed) % sum;

				/* "...and select the RR whose running sum
				 * value is the first in the selected order
				 * which is greater than or equal to the
				 * random number selected."
				 */
				for (n = r; n && n->rr.srv.priority == r->rr.srv.priority && n->srvsum < low; n = LIST_NEXT(n, le))
					;;

				if (n != r && n->rr.srv.priority == r->rr.srv.priority) {
					LIST_REMOVE(n, le);
					LIST_INSERT_BEFORE(r, n, le);
					r	= n;
				}
			}

			if ((q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))) {
				q->state.exec	= LOOKUP_QUERY_SRV_IP;
				goto nextstate;
			}
		} else {
			/* If no SRV records where found, but the user wants
			 * to automagically drop back to A/AAAA, jump to an
			 * A/AAAA query.
			 */
			if ((q->flags & LOOKUP_FALLBACK)
			&&  (q->type & (LOOKUP_IN_A|LOOKUP_IN_AAAA))) {
				char *nxt;

				/* Try to strip the leading _service._proto. */
				if (*q->qname == '_' && (nxt = strchr(q->qname, '.'))
				&&  *++nxt == '_' && (nxt = strchr(nxt, '.')) && *++nxt && *nxt != '.') {
					q->qnamelen	= q->qnamelen - (nxt - q->qname);
					(void)memmove(q->qname, nxt, q->qnamelen + 1);

					q->state.exec	= 0;

					if (q->type & LOOKUP_IN_A)
						q->state.exec	|= LOOKUP_QUERY_A;
					if (q->type & LOOKUP_IN_AAAA)
						q->state.exec	|= LOOKUP_QUERY_AAAA;

					goto nextstate;
				} else
					q->status	= LOOKUP_EBAD_REQUEST;
			}

			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	case LOOKUP_QUERY_SRV_IP:
		if (!(q->state.done & q->state.exec)) {
			q->state.done	|= q->state.exec;

			/* Move SRV records to another queue. */
			for (r = LIST_FIRST(&q->answers); r; r = n) {
				n	= LIST_NEXT(r, le);

				LIST_REMOVE(r, le);
				q->nanswers--;

				LIST_INSERT_HEAD(&q->srvrecords, r, le);
				q->nsrvrecords++;
			}
		}

		while ((r = LIST_FIRST(&q->srvrecords))) {
			if (!r->resolved && !lookup_name_isequal(r->rr.srv.host, r->rr.srv.hostlen, ".", 1)) {
				r->resolved	= 1;
				q->qnamelen	= strlcpy(q->qname, r->rr.srv.host, sizeof q->qname);
				q->state.next	= q->state.exec;
				q->state.exec	= 0;

				if (q->type & LOOKUP_IN_A)
					q->state.exec	|= LOOKUP_QUERY_A;
				if (q->type & LOOKUP_IN_AAAA)
					q->state.exec	|= LOOKUP_QUERY_AAAA;

				q->state.done	&= ~q->state.exec;

				goto nextstate;
			}

			LIST_REMOVE(r, le);
			q->nsrvrecords--;

			LIST_INSERT_HEAD(&q->answers, r, le);
			q->nanswers++;
		}

		if (!q->nanswers) {
			if (LOOKUP_WARNING(q->status)) {
				/* Convert to error. */
				q->status	= LOOKUP_W2ERROR(q->status);
			}
		}

		lookup_query_close(l, q, q->status);

		break;
	default:
		assert(0);
	}

break0:

	return /* void */;
} /* lookup_process() */


static void lookup_catch_ares(void *l_, int ares_errno, unsigned char *abuf, int alen) {
	int lookup_errno	= LOOKUP_ESUCCESS;
	struct lookup_query *q;
	struct lookup *l;

	/*
	 * XXX: A callback should NEVER be pending when we call
	 * ares_destroy().  This will be fixed when ares is taught to be
	 * careful about which pointers it accesses after a callback
	 * returns.
	 */
	assert(ares_errno != ARES_EDESTRUCTION);

	q	= l_;
	l	= q->lookup;

	if (ares_errno == ARES_SUCCESS) {
		lookup_errno	= lookup_parse_reply(l, q, abuf, alen);
	} else {
		if (abuf && alen > 0)
			(void)lookup_parse_reply(l, q, abuf, alen);

		l->ares_errno	= ares_errno;
		lookup_errno	= LOOKUP_E2WARNING(ares_errno_tr(ares_errno));
	}

	q->status	= lookup_errno;
	q->channel->pending--;
	l->ares.pending--;

	lookup_process(l, q);

	/* NOTE: abuf from automatic storage in ares (see ares_process.c). */

	return /* void */;
} /* lookup_catch_ares() */


/*
 * Synchronous lookup helper.
 *
 * This function catches the asynchronous request and creates a copy of all
 * the records (since we don't own them). It flags the synchronous loop
 * (within lookup_s() or rlookup_s()) so it can exit.
 */
static void lookup_s_catch(int nans, struct lookup_rr *ans, int nadd, struct lookup_rr *add, enum lookup_errno errnum, void *arg) {
	struct lookup_result *r	= arg;
	struct lookup *l	= r->lookup;
	struct lookup_rr *rr, *nr, *pr;

	r->done		= 1;
	r->l_errno	= errnum;
	r->sys_errno	= errno;

	if (0 < (r->nanswers = nans)) {
		pr	= 0;

		for (rr = ans; rr != 0; rr = rr->next) {
			if (!(nr = l->ap->malloc(l->ap, sizeof *nr, 0)))
				goto sysfail;

			(void)memcpy(nr, rr, sizeof *nr);
			nr->next	= 0;

			if (pr) {
				LIST_INSERT_AFTER(pr, nr, le);

				pr->next	= nr;
			} else {
				LIST_INSERT_HEAD(&r->l_answers, nr, le);

				r->answers	= nr;
			}

			pr	= nr;
		}
	}

	if (0 < (r->nadditional = nadd)) {
		pr	= 0;

		for (rr = add; rr != 0; rr = rr->next) {
			if (!(nr = l->ap->malloc(l->ap, sizeof *nr, 0)))
				goto sysfail;

			(void)memcpy(nr, rr, sizeof *nr);
			nr->next	= 0;

			if (pr) {
				LIST_INSERT_AFTER(pr, nr, le);

				pr->next	= nr;
			} else {
				LIST_INSERT_HEAD(&r->l_additional, nr, le);

				r->additional	= nr;
			}

			pr	= nr;
		}
	}

	return /* void */;
sysfail:
	r->l_errno	= LOOKUP_ESYSTEM;
	r->sys_errno	= errno;

	lookup_rr_free(l, r->answers);
	r->nanswers	= 0;
	r->answers	= 0;

	lookup_rr_free(l, r->additional);
	r->nadditional	= 0;
	r->additional	= 0;

	return /* void */;
} /* lookup_s_catch() */


struct lookup_result *lookup_rr(struct lookup *l, const char *host, size_t hostlen, int type, int flags, lookup_return cb, void *arg, struct timeval *timeout) {
	struct lookup_query *q	= 0;
	struct lookup_result *r	= 0;
	int sys_errno;

	if (!cb) {
		if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
			goto fail;

		LOOKUP_RESULT_INIT(r);

		r->lookup	= l;
		cb		= &lookup_s_catch;
		arg		= r;
	}

	if (!(q = lookup_query_open(l, host, hostlen, type, flags, cb, arg, timeout)))
		goto fail;

	LIST_INSERT_HEAD(&l->queries, q, le);

	lookup_process(l, q);

	while (r && !r->done) {
		if (l->ev_base)
			event_base_loop(l->ev_base, EVLOOP_ONCE);
		else
			event_loop(EVLOOP_ONCE);
	}

	return r;
fail:
	sys_errno	= errno;

	if (!q) {
		if (cb)
			cb(0, NULL, 0, NULL, LOOKUP_ESYSTEM, arg);
		else /* looks like very early failure of synchronous call. */
			r	= (struct lookup_result *)&lookup_result_syserr;
	} else
		lookup_query_close(l, q, LOOKUP_ESYSTEM);

	errno	= sys_errno;

	return r;
} /* lookup_rr() */


struct lookup_result *lookup_ptr(struct lookup *l, struct sockaddr *sa, socklen_t salen, int flags, lookup_return cb, void *arg, struct timeval *timeout) {
	struct lookup_query *q	= 0;
	struct lookup_result *r	= 0;
	int lookup_errno	= LOOKUP_ESYSTEM;
	char qname[sizeof q->qname];
	int qnamelen;
	struct sockaddr_in *sin;
	struct sockaddr_in6 *sin6;
	int sys_errno;

	if (!cb) {
		if (!(r = l->ap->malloc(l->ap, sizeof *r, 0)))
			goto fail;

		LOOKUP_RESULT_INIT(r);

		r->lookup	= l;
		cb		= &lookup_s_catch;
		arg		= r;
	}

	switch(sa->sa_family) {
	case AF_INET:
		sin	= (struct sockaddr_in *)sa;

		qnamelen = snprintf(qname, sizeof qname,
			"%d.%d.%d.%d.in-addr.arpa",
			ntohl(sin->sin_addr.s_addr) & 0xff,
			(ntohl(sin->sin_addr.s_addr) >> 8) & 0xff,
			(ntohl(sin->sin_addr.s_addr) >> 16) & 0xff,
			(ntohl(sin->sin_addr.s_addr) >> 24) & 0xff
		);

		break;
	case AF_INET6:
		sin6	= (struct sockaddr_in6 *)sa;

		if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
			qnamelen = snprintf(qname, sizeof qname,
				"%d.%d.%d.%d.in-addr.arpa",
				(int)sin6->sin6_addr.s6_addr[15],
				(int)sin6->sin6_addr.s6_addr[14],
				(int)sin6->sin6_addr.s6_addr[13],
				(int)sin6->sin6_addr.s6_addr[12]
			);
		} else {
			qnamelen = snprintf(qname, sizeof qname,
				"%x.%x.%x.%x.%x.%x.%x.%x."
				"%x.%x.%x.%x.%x.%x.%x.%x."
				"%x.%x.%x.%x.%x.%x.%x.%x."
				"%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa",
				sin6->sin6_addr.s6_addr[15] & 0x0f, sin6->sin6_addr.s6_addr[15] >> 4,
				sin6->sin6_addr.s6_addr[14] & 0x0f, sin6->sin6_addr.s6_addr[14] >> 4,
				sin6->sin6_addr.s6_addr[13] & 0x0f, sin6->sin6_addr.s6_addr[13] >> 4,
				sin6->sin6_addr.s6_addr[12] & 0x0f, sin6->sin6_addr.s6_addr[12] >> 4,
				sin6->sin6_addr.s6_addr[11] & 0x0f, sin6->sin6_addr.s6_addr[11] >> 4,
				sin6->sin6_addr.s6_addr[10] & 0x0f, sin6->sin6_addr.s6_addr[10] >> 4,
				sin6->sin6_addr.s6_addr[9] & 0x0f, sin6->sin6_addr.s6_addr[9] >> 4,
				sin6->sin6_addr.s6_addr[8] & 0x0f, sin6->sin6_addr.s6_addr[8] >> 4,
				sin6->sin6_addr.s6_addr[7] & 0x0f, sin6->sin6_addr.s6_addr[7] >> 4,
				sin6->sin6_addr.s6_addr[6] & 0x0f, sin6->sin6_addr.s6_addr[6] >> 4,
				sin6->sin6_addr.s6_addr[5] & 0x0f, sin6->sin6_addr.s6_addr[5] >> 4,
				sin6->sin6_addr.s6_addr[4] & 0x0f, sin6->sin6_addr.s6_addr[4] >> 4,
				sin6->sin6_addr.s6_addr[3] & 0x0f, sin6->sin6_addr.s6_addr[3] >> 4,
				sin6->sin6_addr.s6_addr[2] & 0x0f, sin6->sin6_addr.s6_addr[2] >> 4,
				sin6->sin6_addr.s6_addr[1] & 0x0f, sin6->sin6_addr.s6_addr[1] >> 4,
				sin6->sin6_addr.s6_addr[0] & 0x0f, sin6->sin6_addr.s6_addr[0] >> 4
			);
		}

		break;
	default:
		lookup_errno	= LOOKUP_EBAD_FAMILY;
		goto fail;
	}

	if (qnamelen < 0)
		goto fail;

	if (qnamelen >= (int)sizeof qname) {
		errno	= EINVAL;
		goto fail;
	}

	if (!(q = lookup_query_open(l, qname, qnamelen, LOOKUP_IN_PTR, flags, cb, arg, timeout)))
		goto fail;

	LIST_INSERT_HEAD(&l->queries, q, le);

	lookup_process(l, q);

	while (r && !r->done) {
		if (l->ev_base)
			event_base_loop(l->ev_base, EVLOOP_ONCE);
		else
			event_loop(EVLOOP_ONCE);
	}

	return r;
fail:
	sys_errno	= errno;

	if (!q) {
		if (cb)
			cb(0, NULL, 0, NULL, lookup_errno, arg);
		else /* looks like very early failure of synchronous call. */
			r	= (struct lookup_result *)&lookup_result_syserr;
	} else
		lookup_query_close(l, q, lookup_errno);

	errno	= sys_errno;

	return r;
} /* lookup_ptr() */


struct lookup_result *lookup(const char *host, size_t hostlen, int type, int flags, lookup_return cb, void *arg, struct timeval *timeout) {
	assert(NULL != lookup_local);

	return lookup_rr(lookup_local, host, hostlen, type, flags, cb, arg, timeout);
} /* lookup() */


struct lookup_result *rlookup(struct sockaddr *sa, socklen_t salen, int flags, lookup_return cb, void *arg, struct timeval *timeout) {
	assert(NULL != lookup_local);

	return lookup_ptr(lookup_local, sa, salen, flags, cb, arg, timeout);
} /* rlookup() */


void lookup_rr_free(struct lookup *l, struct lookup_rr *rr) {
	struct lookup_rr *nr;

	if (!l) {
		assert(NULL != lookup_local);

		l	= lookup_local;	
	}

	for (; rr != 0; rr = nr) {
		nr	= rr->next;

		l->ap->free(l->ap, rr);
	}

	return /* void */;
} /* lookup_rr_free() */


void lookup_result_free(struct lookup *l, struct lookup_result *r) {
	if (!l) {
		assert(NULL != lookup_local);

		l	= lookup_local;	
	}

	if (r == &lookup_result_syserr)
		return /* void */;

	lookup_rr_free(l, r->answers);
	lookup_rr_free(l, r->additional);

	l->ap->free(l->ap, r);

	return /* void */;
} /* lookup_result_free() */


struct lookup *lookup_open(const struct lookup_options *opts, struct event_base *ev_base, const struct arena_prototype *ap) {
	struct lookup *l	= 0;
	struct ares_channel *c;
	unsigned nchannels;
	int lookup_errno	= LOOKUP_ESYSTEM;
	int sys_errno;

	if (!opts)
		opts	= &lookup_defaults;

	if (!ap)
		ap	= ARENA_STDLIB;

	if (!(l = ap->malloc(ap, sizeof *l, 0)))
		return 0;

	LOOKUP_INIT(l);

	l->opts		= *opts;
	l->ev_base	= ev_base;
	l->ap		= ap;

	l->opts.resolv	= 0;	/* Make sure we don't store a pointer to */
	l->opts.hosts	= 0;	/* any string literals and free them. */

	nchannels	= l->opts.nchannels;

	for (; nchannels > 0; nchannels--) {
		if (!(c = ares_channel_open(l)))
			goto sysfail;

		CIRCLEQ_INSERT_HEAD(&l->ares.channels, c, cqe);		
	} /* while (nchannels > 0) */

	if (opts->resolv) {
		if (!(l->opts.resolv = arena_util_strdup(l->ap, opts->resolv)))
			goto sysfail;

		if (0 != resolv_order(l, l->opts.resolv))
			goto sysfail;
	}

	if (opts->hosts) {
		if (!(l->opts.hosts = arena_util_strdup(l->ap, opts->hosts)))
			goto sysfail;

		if (!(l->hosts = hosts_open(l, l->opts.hosts)))
			goto sysfail;
	}

	return l;
aresfail:
	/* FALL THROUGH */
sysfail:
	sys_errno	= errno;

	lookup_close(l);

	errno	= sys_errno;

	return 0;
} /* lookup_open() */


void lookup_close(struct lookup *l) {
	struct lookup_query *q;
	struct { struct lookup_frame *l; struct lookup_query_frame *q; } f;
	struct ares_channel *c;

	if (!l)
		return /* void */;

	while (LIST_END(&l->queries) != (q = LIST_FIRST(&l->queries))) {
		/*
		 * We don't support cancelletions yet, so if a callback is
		 * pending something is horribly wrong.
		 */
		assert(0 == q->cb);

		lookup_rr_free(l, LIST_FIRST(&q->answers));
		lookup_rr_free(l, LIST_FIRST(&q->additional));

		LIST_REMOVE(q, le);

		SLIST_FOREACH(f.q, &q->frames, sle)
			LOOKUP_FRAME_KILL(f.q);

		l->ap->free(l->ap, q);
	}

	if (l->hosts)
		hosts_close(l, l->hosts);

	l->ap->free(l->ap, l->opts.hosts);
	l->ap->free(l->ap, l->opts.resolv);

	while (CIRCLEQ_END(&l->ares.channels) != (c = CIRCLEQ_FIRST(&l->ares.channels))) {
		CIRCLEQ_REMOVE(&l->ares.channels, c, cqe);

		ares_channel_close(c);
	}

	SLIST_FOREACH(f.l, &l->frames, sle)
		LOOKUP_FRAME_KILL(f.l);

	l->ap->free(l->ap, l);

	return /* void */;
} /* lookup_close() */


const char *lookup_strerror(struct lookup *l) {
#if 0
	char *errstr;
#endif

	switch (l->lookup_errno) {
	case LOOKUP_ESYSTEM:
		return strerror(errno);
	case LOOKUP_EARES:
		/* NOTE: The Ares API requires calling ares_free_errmem() on
		 * the returned string, however the implementation as of
		 * this message returns a static const string.
		 */
#if 0
		return ares_strerror(l->ares_errno, &errstr);
#else
		return ares_strerror(l->ares_errno);
#endif
	default:
		if (l->lookup_errno >= 0 && l->lookup_errno < LOOKUP_NERR)
			return lookup_errlist[l->lookup_errno];
		else
			return "Unknown";
	}
} /* lookup_strerror() */


enum lookup_errno lookup_reset(const struct lookup_options *opts, struct event_base *ev_base, const struct arena_prototype *ap) {
	if (lookup_local)
		lookup_close(lookup_local);

	if (!(lookup_local = lookup_open(opts, ev_base, ap)))
		return LOOKUP_ESYSTEM;

	return LOOKUP_ESUCCESS;
} /* lookup_reset() */


enum lookup_errno lookup_init(const struct lookup_options *opts, struct event_base *ev_base, const struct arena_prototype *ap) {
	return lookup_reset(opts, ev_base, ap);
} /* lookup_init() */

#else

#include <sys/param/MIN.h>

#if _WIN32
#include <winsock2.h>
#else
#include <sys/time.h>
#include <sys/socket.h>
#endif

#include <event.h>

#include <arena/proto.h>

#include "lookup.h"


const char *lookup_errlist[1]	= { 0 };

const struct lookup_options lookup_defaults;

struct lookup *lookup_open(const struct lookup_options *opts, struct event_base *ev_base, const struct arena_prototype *ap) {
	return 0;
} /* lookup_open() */

void lookup_close(struct lookup *l) {
	return /* void */;
} /* lookup_close() */

void lookup_result_free(struct lookup *l, struct lookup_result *r) {
	return /* void */;
} /* lookup_result_free() */

void lookup_rr_free(struct lookup *l, struct lookup_rr *rr) {
	return /* void */;
} /* lookup_rr_free() */

struct lookup_result *lookup_rr(struct lookup *l, const char *a, size_t b, int c, int d, lookup_return cb, void *arg, struct timeval *e) {
	if (cb == 0)
		cb(0, 0, 0, 0, 0, arg);

	return 0;
} /* lookup_rr() */

struct lookup_result *lookup_ptr(struct lookup *l, struct sockaddr *a, socklen_t b, int c, lookup_return cb, void *arg, struct timeval *d) {
	if (cb == 0)
		cb(0, 0, 0, 0, 0, arg);

	return 0;
} /* lookup_ptr() */

enum lookup_errno lookup_errno(struct lookup *l) {
	return 0;
} /* lookup_errno() */

const char *lookup_strerror(struct lookup *l) {
	return "Unknown";
} /* lookup_strerror() */

enum lookup_errno lookup_init(const struct lookup_options *a, struct event_base *b, const struct arena_prototype *c) {
	return 0;
} /* lookup_init() */

enum lookup_errno lookup_reset(const struct lookup_options *a, struct event_base *b, const struct arena_prototype *c) {
	return 0;
} /* lookup_reset() */

struct lookup_result *lookup(const char *a, size_t b, int c, int d, lookup_return cb, void *arg, struct timeval *e) {
	if (cb == 0)
		cb(0, 0, 0, 0, 0, arg);

	return 0;
} /* lookup() */

struct lookup_result *rlookup(struct sockaddr *a, socklen_t b, int c, lookup_return cb, void *arg, struct timeval *d) {
	if (cb == 0)
		cb(0, 0, 0, 0, 0, arg);

	return 0;
} /* rlookup() */


#endif /* USE_CARES */


syntax highlighted by Code2HTML, v. 0.9.1