/* ========================================================================== * lookup.c - Simple DNS Query Interface * -------------------------------------------------------------------------- * Copyright (c) 2004, 2005, 2006 Barracuda Networks, Inc. * Copyright (c) 2006 William Ahern * * 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 /* FILE fopen(3) fread(3) fclose(3) fileno(3) */ #include /* NULL free(3) */ #include /* time_t time(2) */ #include /* memmove(3) strsep(3) strlen(3) * strcasecmp(3) strncpy(3) strcspn(3) * strerror(3) */ #include /* strlcpy(3) */ #include #include #include /* dev_t ino_t */ #include /* struct stat */ #include /* struct timeval timerclear */ #ifndef WIN32 #include /* FD_ZERO FD_SET fd_set */ #else #include #endif #include /* socklen_t struct sockaddr_storage */ #include /* MIN MAX */ #include /* SLIST LIST CIRCLEQ */ #include /* fstat(2) */ #include /* inet_pton(3) */ #include /* 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 /* IN6_IS_ADDR_V4MAPPED */ #include /* NI_MAXHOST */ #ifndef NI_MAXHOST #define NI_MAXHOST 1024 #endif #ifdef USE_PTHREADS #include /* 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 #include /* warn(3) */ #include #if 0 #include /* DNS_HEADER_* DNS_RR_* */ #endif #include #include #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 #if _WIN32 #include #else #include #include #endif #include #include #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 */