/* ==========================================================================
* 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