/* * Copyright 2005 Niels Provos * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Niels Provos. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*- * Copyright (c) 1985, 1988, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * Portions Copyright (c) 1993 by Digital Equipment Corporation. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies, and that * the name of Digital Equipment Corporation not be used in advertising or * publicity pertaining to distribution of the document or software without * specific, written prior permission. * * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. * - * --Copyright-- */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnsres.h" #include "dnsres-internal.h" #include "resolv.h" static void dnsres_gethostbyname2_internal(struct dnsres *_resp, void (*cb)(struct dnsres_hostent *, struct dnsres_cbstate *), struct dnsres_cbstate *state); static void dnsres_map_v4v6_address(const char *src, char *dst); static void dnsres_map_v4v6_hostent(struct dnsres_hostent *hp, char **bp, char *); struct dnsres_hostent *dnsres_gethtbyname2(struct dnsres *, struct dnsres_cbstate *, const char *, int); struct dnsres_hostent *dnsres_gethtbyaddr(struct dnsres *, struct dnsres_cbstate *, const char *, int, int); #ifdef RESOLVSORT static void dnsres_addrsort(struct dnsres *, char **, int); #endif int _hokchar(const char *); static const char AskedForGot[] = "gethostby*.getanswer: asked for \"%s\", got \"%s\""; typedef union { int32_t al; char ac; } align; static struct dnsres_hostent *getanswer(struct dnsres *, struct dnsres_cbstate *, const querybuf *, int, const char *, int); int _hokchar(const char *p) { char c; /* * Many people do not obey RFC 822 and 1035. The valid * characters are a-z, A-Z, 0-9, '-' and . But the others * tested for below can happen, and we must be more permissive * than the resolver until those idiots clean up their act. * We let '/' through, but not '..' */ while ((c = *p++)) { if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) continue; if (strchr("-_/", c)) continue; if (c == '.' && *p != '.') continue; return 0; } return 1; } static struct dnsres_hostent * getanswer(struct dnsres *_resp, struct dnsres_cbstate *state, const querybuf *answer, int anslen, const char *qname, int qtype) { const DNSRES_HEADER *hp; const u_char *cp, *eom; char tbuf[DNSRES_MAXDNAME]; char *bp, **ap, **hap, *ep; int type, class, ancount, qdcount, n; int haveanswer, had_error, toobig = 0; const char *tname; int (*name_ok)(const char *); tname = qname; state->host.h_name = NULL; eom = answer->buf + anslen; switch (qtype) { case DNSRES_T_A: case DNSRES_T_AAAA: #ifdef USE_RESOLV_NAME_OK name_ok = res_hnok; break; #endif case DNSRES_T_PTR: #ifdef USE_RESOLV_NAME_OK name_ok = res_dnok; #else name_ok = _hokchar; #endif break; default: return (NULL); } /* * find first satisfactory answer */ hp = &answer->hdr; ancount = ntohs(hp->ancount); qdcount = ntohs(hp->qdcount); bp = state->hostbuf; ep = state->hostbuf + sizeof(state->hostbuf); cp = answer->buf + DNSRES_HFIXEDSZ; if (qdcount != 1) { _resp->dr_errno = DNSRES_NO_RECOVERY; return (NULL); } n = dn_expand(answer->buf, eom, cp, bp, ep - bp); if ((n < 0) || !(*name_ok)(bp)) { _resp->dr_errno = DNSRES_NO_RECOVERY; return (NULL); } cp += n + DNSRES_QFIXEDSZ; if (qtype == DNSRES_T_A || qtype == DNSRES_T_AAAA) { /* res_send() has already verified that the query name is the * same as the one we sent; this just gets the expanded name * (i.e., with the succeeding search-domain tacked on). */ n = strlen(bp) + 1; /* for the \0 */ state->host.h_name = bp; bp += n; /* The qname can be abbreviated, but h_name is now absolute. */ qname = state->host.h_name; } ap = state->host_aliases; *ap = NULL; state->host.h_aliases = state->host_aliases; hap = state->h_addr_ptrs; *hap = NULL; state->host.h_addr_list = state->h_addr_ptrs; haveanswer = 0; had_error = 0; while (ancount-- > 0 && cp < eom && !had_error) { n = dn_expand(answer->buf, eom, cp, bp, ep - bp); if ((n < 0) || !(*name_ok)(bp)) { had_error++; continue; } cp += n; /* name */ if (cp >= eom) break; type = getshort(cp); cp += DNSRES_INT16SZ; /* type */ if (cp >= eom) break; class = getshort(cp); cp += DNSRES_INT16SZ + DNSRES_INT32SZ; /* class, TTL */ if (cp >= eom) break; n = getshort(cp); cp += DNSRES_INT16SZ; /* len */ if (cp >= eom) break; if (type == DNSRES_T_SIG) { /* XXX - ignore signatures as we don't use them yet */ cp += n; continue; } if (class != DNSRES_C_IN) { /* XXX - debug? syslog? */ cp += n; continue; /* XXX - had_error++ ? */ } if ((qtype == DNSRES_T_A || qtype == DNSRES_T_AAAA) && type == DNSRES_T_CNAME) { if (ap >= &state->host_aliases[MAXALIASES-1]) continue; n = dn_expand(answer->buf, eom, cp, tbuf, sizeof tbuf); if ((n < 0) || !(*name_ok)(tbuf)) { had_error++; continue; } cp += n; /* Store alias. */ *ap++ = bp; n = strlen(bp) + 1; /* for the \0 */ bp += n; /* Get canonical name. */ n = strlen(tbuf) + 1; /* for the \0 */ if (n > ep - bp) { had_error++; continue; } strlcpy(bp, tbuf, ep - bp); state->host.h_name = bp; bp += n; continue; } if (qtype == DNSRES_T_PTR && type == DNSRES_T_CNAME) { n = dn_expand(answer->buf, eom, cp, tbuf, sizeof tbuf); #ifdef USE_RESOLV_NAME_OK if ((n < 0) || !res_hnok(tbuf)) { #else if ((n < 0) || !_hokchar(tbuf)) { #endif had_error++; continue; } cp += n; /* Get canonical name. */ n = strlen(tbuf) + 1; /* for the \0 */ if (n > ep - bp) { had_error++; continue; } strlcpy(bp, tbuf, ep - bp); tname = bp; bp += n; continue; } if (type != qtype) { syslog(LOG_NOTICE|LOG_AUTH, "gethostby*.getanswer: asked for \"%s %s %s\", got type \"%s\"", qname, p_class(DNSRES_C_IN), p_type(qtype), p_type(type)); cp += n; continue; /* XXX - had_error++ ? */ } switch (type) { case DNSRES_T_PTR: if (strcasecmp(tname, bp) != 0) { syslog(LOG_NOTICE|LOG_AUTH, AskedForGot, qname, bp); cp += n; continue; /* XXX - had_error++ ? */ } n = dn_expand(answer->buf, eom, cp, bp, ep - bp); #ifdef USE_RESOLV_NAME_OK if ((n < 0) || !res_hnok(bp)) { #else if ((n < 0) || !_hokchar(bp)) { #endif had_error++; break; } #if MULTI_PTRS_ARE_ALIASES cp += n; if (!haveanswer) state->host.h_name = bp; else if (ap < &state->host_aliases[MAXALIASES-1]) *ap++ = bp; else n = -1; if (n != -1) { n = strlen(bp) + 1; /* for the \0 */ bp += n; } break; #else state->host.h_name = bp; if (_resp->options & RES_USE_INET6) { n = strlen(bp) + 1; /* for the \0 */ bp += n; dnsres_map_v4v6_hostent(&host, &bp, ep); } _resp->dr_errno = DNSRES_NETDB_SUCCESS; return (&host); #endif case DNSRES_T_A: case DNSRES_T_AAAA: if (strcasecmp(state->host.h_name, bp) != 0) { syslog(LOG_NOTICE|LOG_AUTH, AskedForGot, state->host.h_name, bp); cp += n; continue; /* XXX - had_error++ ? */ } if (n != state->host.h_length) { cp += n; continue; } if (type == DNSRES_T_AAAA) { struct in6_addr in6; memcpy(&in6, cp, IN6ADDRSZ); if (IN6_IS_ADDR_V4MAPPED(&in6)) { cp += n; continue; } } if (!haveanswer) { int nn; state->host.h_name = bp; nn = strlen(bp) + 1; /* for the \0 */ bp += nn; } bp += sizeof(align) - ((u_long)bp % sizeof(align)); if (bp + n >= &state->hostbuf[sizeof(state->hostbuf)]){ #ifdef DEBUG if (_resp->options & RES_DEBUG) printf("size (%d) too big\n", n); #endif had_error++; continue; } if (hap >= &state->h_addr_ptrs[MAXADDRS-1]) { if (!toobig++) #ifdef DEBUG if (_resp->options & RES_DEBUG) printf("Too many addresses (%d)\n", MAXADDRS); #endif cp += n; continue; } bcopy(cp, *hap++ = bp, n); bp += n; cp += n; break; } if (!had_error) haveanswer++; } if (haveanswer) { *ap = NULL; *hap = NULL; # if defined(RESOLVSORT) /* * Note: we sort even if host can take only one address * in its return structures - should give it the "best" * address in that case, not some random one */ if (_resp->nsort && haveanswer > 1 && qtype == DNSRES_T_A) dnsres_addrsort(_resp, state->h_addr_ptrs, haveanswer); # endif /*RESOLVSORT*/ if (!state->host.h_name) { n = strlen(qname) + 1; /* for the \0 */ if (n > ep - bp) goto try_again; strlcpy(bp, qname, ep - bp); state->host.h_name = bp; bp += n; } if (_resp->options & RES_USE_INET6) dnsres_map_v4v6_hostent(&state->host, &bp, ep); _resp->dr_errno = DNSRES_NETDB_SUCCESS; return (&state->host); } try_again: _resp->dr_errno = DNSRES_TRY_AGAIN; return (NULL); } struct dnsres_cbstate * dnsres_cbstate_new(struct dnsres *_resp, const char *name, size_t len, void (*cb)(struct dnsres_hostent *, int, void *), void *arg) { struct dnsres_cbstate *state; state = calloc(1, sizeof(struct dnsres_cbstate)); if (state == NULL) err(1, "%s: calloc", __func__); /* Set up all of our call back state */ state->cb = cb; state->cb_arg = arg; state->_resp = _resp; state->name = malloc(len); if (state->name == NULL) err(1, "%s: strdup", __func__); memcpy(state->name, name, len); state->name_len = len; state->buf = malloc(sizeof(querybuf)); if (state->buf == NULL) err(1, "%s: malloc", __func__); return (state); } void dnsres_cbstate_free(struct dnsres_cbstate *state) { free(state->buf); state->buf = NULL; free(state->name); free(state); } void dnsres_cancel_lookup(void *arg) { struct dnsres_cbstate *state = arg; /* Indicates that the callback has been cancelled */ state->cb = NULL; } static void dnsres_usercb(int fd, short what, void *arg) { struct dnsres_cbstate *state = arg; struct dnsres_hostent *hp = state->hp; if (state->cb != NULL) (*state->cb)(hp, state->_resp->dr_errno, state->cb_arg); /* Free the dnsres callback state */ dnsres_cbstate_free(state); } void dnsres_gethostbyname_cb(struct dnsres_hostent *hp, struct dnsres_cbstate *state) { if (hp == NULL && state->af == AF_INET6) { /* Retry by looking for IPv4 addresses */ state->af = AF_INET; dnsres_gethostbyname2_internal(state->_resp, dnsres_gethostbyname_cb, state); return; } /* Callout to libevent */ state->hp = hp; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); } /* BLOCKING */ void * dnsres_gethostbyname(struct dnsres *_resp, const char *name, void (*cb)(struct dnsres_hostent *, int, void *), void *arg) { struct dnsres_cbstate *state; state = dnsres_cbstate_new(_resp, name, strlen(name) + 1, cb, arg); if (_resp->options & RES_USE_INET6) { state->af = AF_INET6; /* retry AF_INET on failure */ dnsres_gethostbyname2_internal(_resp, dnsres_gethostbyname_cb, state); } else { state->af = AF_INET; dnsres_gethostbyname2_internal(_resp, dnsres_gethostbyname_cb, state); } return (state); } void dnsres_reset_state(struct dnsres_cbstate *state, const char *name) { strlcpy(state->hostbuf, name, MAXHOSTNAMELEN); state->host.h_name = state->hostbuf; state->host.h_aliases = state->host_aliases; state->host_aliases[0] = NULL; state->h_addr_ptrs[0] = (char *)state->host_addr; state->h_addr_ptrs[1] = NULL; state->host.h_addr_list = state->h_addr_ptrs; } /* * Callback that res_search is going to use to get back to us * n contains the length of the result and is negative on error. */ void dnsres_search_caller(struct dnsres_cbstate *state); void dnsres_search_cb(int n, void *arg) { struct dnsres_cbstate *state = arg; struct dnsres_hostent *hp = NULL; struct dnsres *_resp = state->_resp; if ( n >= 0 ) { hp = getanswer(_resp, state, state->buf, n, state->name, state->q.qtype); } #ifdef DEBUG else { if (_resp->options & RES_DEBUG) printf("res_search failed\n"); } #endif state->hp = hp; /* continue on to next iteration */ dnsres_search_caller(state); } /* * This callback fakes the iteration through the different lookup * methods. */ void dnsres_search_caller(struct dnsres_cbstate *state) { struct dnsres_hostent *hp = NULL; struct dnsres *_resp = state->_resp; const char *name = state->name; again: if (state->hp != NULL || state->lookup_index == MAXDNSLUS) { /* We either got a result or we have reached our end of live */ (*state->internal_cb)(state->hp, state); return; } switch (state->lookups[state->lookup_index++]) { case 'f': hp = dnsres_gethtbyname2(_resp, state, name, state->af); if (hp == NULL) dnsres_search_caller(state); else (*state->internal_cb)(hp, state); break; case 'b': /* BLOCKING */ res_search(_resp, name, &state->q, dnsres_search_cb, state); break; default: /* Try the loop again */ goto again; } } void dnsres_gethostbyname2_cb(struct dnsres_hostent *hp, struct dnsres_cbstate *state) { /* Callout to libevent */ state->hp = hp; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); } void * dnsres_gethostbyname2(struct dnsres *_resp, const char *name, int af, void (*cb)(struct dnsres_hostent *, int, void *), void *cb_arg) { struct dnsres_cbstate *state; state = dnsres_cbstate_new(_resp, name, strlen(name) + 1, cb, cb_arg); state->af = af; dnsres_gethostbyname2_internal(_resp, dnsres_gethostbyname2_cb, state); return (state); } /* BLOCKING */ static void dnsres_gethostbyname2_internal(struct dnsres *_resp, void (*cb)(struct dnsres_hostent *, struct dnsres_cbstate *), struct dnsres_cbstate *state) { unsigned char *name = state->name; int af = state->af; const unsigned char *cp; char *bp, *ep; switch (af) { case AF_INET: state->size = INADDRSZ; state->q.qclass = DNSRES_C_IN; state->q.qtype = DNSRES_T_A; state->q.answer = state->buf->buf; state->q.anslen = sizeof(state->buf->buf); break; case AF_INET6: state->size = IN6ADDRSZ; state->q.qclass = DNSRES_C_IN; state->q.qtype = DNSRES_T_AAAA; state->q.answer = state->buf->buf; state->q.anslen = sizeof(state->buf->buf); break; default: _resp->dr_errno = DNSRES_NETDB_INTERNAL; errno = EAFNOSUPPORT; /* How do deal with this? */ (*cb)(NULL, state); return; } state->host.h_addrtype = af; state->host.h_length = state->size; state->internal_cb = cb; state->hp = NULL; /* * if there aren't any dots, it could be a user-level alias. * this is also done in res_query() since we are not the only * function that looks up host names. */ if (!strchr(name, '.') && (cp = hostalias(_resp, name))) { /* Update the name that we use for the lookup */ state->name_len = strlen(cp) + 1; state->name = strdup(cp); if (state->name == NULL) err(1, "%s: strdup", __func__); free(name); name = state->name; } /* * disallow names consisting only of digits/dots, unless * they end in a dot. */ if (isdigit(name[0])) for (cp = name;; ++cp) { if (!*cp) { if (*--cp == '.') break; /* * All-numeric, no dot at the end. * Fake up a hostent as if we'd actually * done a lookup. */ if (inet_pton(af, name, state->host_addr) <= 0) { _resp->dr_errno = DNSRES_HOST_NOT_FOUND; (*cb)(NULL, state); return; } dnsres_reset_state(state, name); bp = state->hostbuf + MAXHOSTNAMELEN; ep = state->hostbuf + sizeof(state->hostbuf); if (_resp->options & RES_USE_INET6) dnsres_map_v4v6_hostent(&state->host, &bp, ep); _resp->dr_errno = DNSRES_NETDB_SUCCESS; (*cb)(&state->host, state); return; } if (!isdigit(*cp) && *cp != '.') break; } if ((isxdigit(name[0]) && strchr(name, ':') != NULL) || name[0] == ':') for (cp = name;; ++cp) { if (!*cp) { if (*--cp == '.') break; /* * All-IPv6-legal, no dot at the end. * Fake up a hostent as if we'd actually * done a lookup. */ if (inet_pton(af, name, state->host_addr) <= 0) { _resp->dr_errno = DNSRES_HOST_NOT_FOUND; (*cb)(NULL, state); return; } dnsres_reset_state(state, name); bp = state->hostbuf + MAXHOSTNAMELEN; ep = state->hostbuf + sizeof(state->hostbuf); _resp->dr_errno = DNSRES_NETDB_SUCCESS; (*cb)(&state->host, state); return; } if (!isxdigit(*cp) && *cp != ':' && *cp != '.') break; } bcopy(_resp->lookups, state->lookups, sizeof(state->lookups)); if (state->lookups[0] == '\0') strlcpy(state->lookups, "bf", sizeof(state->lookups)); state->lookup_index = 0; /* This used to be a loop, now the callback keeps it's own logic */ dnsres_search_caller(state); return; } /* This call is BLOCKING */ void dnsres_gethostbyaddr_loop_cb(int n, void *state); void dnsres_gethostbyaddr_loop(struct dnsres_cbstate *state); void * dnsres_gethostbyaddr(struct dnsres *_resp, const char *addr, int len, int af, void (*cb)(struct dnsres_hostent *, int, void *), void *arg) { struct dnsres_cbstate *state; const u_char *uaddr = (const u_char *)addr; int n, size, i; char *qp, *ep; state = dnsres_cbstate_new(_resp, addr, len, cb, arg); state->af = af; if (af == AF_INET6 && len == IN6ADDRSZ && (IN6_IS_ADDR_LINKLOCAL((struct in6_addr *)uaddr) || IN6_IS_ADDR_SITELOCAL((struct in6_addr *)uaddr))) { _resp->dr_errno = DNSRES_HOST_NOT_FOUND; state->hp = NULL; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return (state); } if (af == AF_INET6 && len == IN6ADDRSZ && (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)uaddr) || IN6_IS_ADDR_V4COMPAT((struct in6_addr *)uaddr))) { /* Unmap. */ addr += IN6ADDRSZ - INADDRSZ; uaddr += IN6ADDRSZ - INADDRSZ; af = AF_INET; len = INADDRSZ; } switch (af) { case AF_INET: size = INADDRSZ; state->q.qclass = DNSRES_C_IN; state->q.qtype = DNSRES_T_PTR; state->q.answer = state->buf->buf; state->q.anslen = sizeof(state->buf->buf); break; case AF_INET6: size = IN6ADDRSZ; state->q.qclass = DNSRES_C_IN; state->q.qtype = DNSRES_T_PTR; state->q.answer = state->buf->buf; state->q.anslen = sizeof(state->buf->buf); break; default: errno = EAFNOSUPPORT; _resp->dr_errno = DNSRES_NETDB_INTERNAL; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return (state); } if (size != len) { errno = EINVAL; _resp->dr_errno = DNSRES_NETDB_INTERNAL; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return (state); } ep = state->qbuf + sizeof(state->qbuf); switch (af) { case AF_INET: (void) snprintf(state->qbuf, sizeof state->qbuf, "%u.%u.%u.%u.in-addr.arpa", (uaddr[3] & 0xff), (uaddr[2] & 0xff), (uaddr[1] & 0xff), (uaddr[0] & 0xff)); break; case AF_INET6: qp = state->qbuf; for (n = IN6ADDRSZ - 1; n >= 0; n--) { i = snprintf(qp, ep - qp, "%x.%x.", uaddr[n] & 0xf, (uaddr[n] >> 4) & 0xf); if (i <= 0) { errno = EINVAL; _resp->dr_errno = DNSRES_NETDB_INTERNAL; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return (state); } qp += i; } strlcpy(qp, "ip6.arpa", ep - qp); break; } bcopy(_resp->lookups, state->lookups, sizeof(state->lookups)); if (state->lookups[0] == '\0') strlcpy(state->lookups, "bf", sizeof(state->lookups)); state->hp = NULL; state->lookup_index = 0; dnsres_gethostbyaddr_loop(state); return (state); } void dnsres_gethostbyaddr_loop(struct dnsres_cbstate *state) { struct dnsres *_resp = state->_resp; /* Check look termination */ if (state->lookup_index >= MAXDNSLUS || state->hp != NULL || !state->lookups[state->lookup_index]) { /* XXX dr_errno not correct in all cases... */ event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return; } switch (state->lookups[state->lookup_index++]) { case 'b': /* BLOCKING */ res_query(_resp, state->qbuf, &state->q, dnsres_gethostbyaddr_loop_cb, state); return; case 'f': state->hp = dnsres_gethtbyaddr(_resp, state, state->name, state->name_len, state->af); break; } /* We are done or not */ dnsres_gethostbyaddr_loop(state); } void dnsres_gethostbyaddr_loop_cb(int n, void *arg) { struct dnsres_cbstate *state = arg; struct dnsres* _resp = state->_resp; if (n < 0) { #ifdef DEBUG if (_resp->options & RES_DEBUG) printf("res_query failed\n"); #endif } else { struct dnsres_hostent *hp; hp = state->hp = getanswer(_resp, state, state->buf, n, state->qbuf, DNSRES_T_PTR); if (hp != NULL ) { hp->h_addrtype = state->af; hp->h_length = state->name_len; bcopy(state->name, state->host_addr, state->name_len); state->h_addr_ptrs[0] = (char *)state->host_addr; state->h_addr_ptrs[1] = NULL; if (state->af == AF_INET && (_resp->options & RES_USE_INET6)) { dnsres_map_v4v6_address( (char*)state->host_addr, (char*)state->host_addr); hp->h_addrtype = AF_INET6; hp->h_length = IN6ADDRSZ; } _resp->dr_errno = DNSRES_NETDB_SUCCESS; } } /* Continue in the loop if we still can */ dnsres_gethostbyaddr_loop(state); } void dnsres_sethtent(struct dnsres_hostent_state *state, int f) { if (state->hostf == NULL) state->hostf = fopen(DNSRES_PATH_HOSTS, "r" ); else rewind(state->hostf); state->stayopen = f; } void dnsres_endhtent(struct dnsres_hostent_state *state) { if (state->hostf && !state->stayopen) { (void) fclose(state->hostf); state->hostf = NULL; } } struct dnsres_hostent * dnsres_gethtent(struct dnsres *_resp, struct dnsres_cbstate *state) { struct dnsres_hostent_state *hostent = &_resp->hostent_state; char *p, *cp, **q; int af; size_t len; if (!hostent->hostf && !(hostent->hostf = fopen(DNSRES_PATH_HOSTS, "r" ))) { _resp->dr_errno = DNSRES_NETDB_INTERNAL; return (NULL); } again: if ((p = fgetln(hostent->hostf, &len)) == NULL) { _resp->dr_errno = DNSRES_HOST_NOT_FOUND; return (NULL); } if (p[len-1] == '\n') len--; if (len >= sizeof(state->hostbuf) || len == 0) goto again; p = memcpy(state->hostbuf, p, len); state->hostbuf[len] = '\0'; if (*p == '#') goto again; if ((cp = strchr(p, '#'))) *cp = '\0'; if (!(cp = strpbrk(p, " \t"))) goto again; *cp++ = '\0'; if (inet_pton(AF_INET6, p, state->host_addr) > 0) { af = AF_INET6; len = IN6ADDRSZ; } else if (inet_pton(AF_INET, p, state->host_addr) > 0) { if (_resp->options & RES_USE_INET6) { dnsres_map_v4v6_address( (char*)state->host_addr, (char*)state->host_addr); af = AF_INET6; len = IN6ADDRSZ; } else { af = AF_INET; len = INADDRSZ; } } else { goto again; } /* if this is not something we're looking for, skip it. */ if (state->host.h_addrtype != af) goto again; if (state->host.h_length != len) goto again; state->h_addr_ptrs[0] = (char *)state->host_addr; state->h_addr_ptrs[1] = NULL; state->host.h_addr_list = state->h_addr_ptrs; state->host.h_length = len; state->host.h_addrtype = af; while (*cp == ' ' || *cp == '\t') cp++; state->host.h_name = cp; q = state->host.h_aliases = state->host_aliases; if ((cp = strpbrk(cp, " \t"))) *cp++ = '\0'; while (cp && *cp) { if (*cp == ' ' || *cp == '\t') { cp++; continue; } if (q < &state->host_aliases[MAXALIASES - 1]) *q++ = cp; if ((cp = strpbrk(cp, " \t"))) *cp++ = '\0'; } *q = NULL; if (_resp->options & RES_USE_INET6) { char *bp = state->hostbuf; char *ep = state->hostbuf + sizeof(state->hostbuf); dnsres_map_v4v6_hostent(&state->host, &bp, ep); } _resp->dr_errno = DNSRES_NETDB_SUCCESS; return (&state->host); } struct dnsres_hostent * dnsres_gethtbyname(struct dnsres *_resp, struct dnsres_cbstate *state, const char *name) { struct dnsres_hostent *hp; if (_resp->options & RES_USE_INET6) { hp = dnsres_gethtbyname2(_resp, state, name, AF_INET6); if (hp) return (hp); } return (dnsres_gethtbyname2(_resp, state, name, AF_INET)); } struct dnsres_hostent * dnsres_gethtbyname2(struct dnsres *_resp, struct dnsres_cbstate *state, const char *name, int af) { struct dnsres_hostent *p; char **cp; dnsres_sethtent(&_resp->hostent_state, 0); while ((p = dnsres_gethtent(_resp, state))) { if (p->h_addrtype != af) continue; if (strcasecmp(p->h_name, name) == 0) break; for (cp = p->h_aliases; *cp != 0; cp++) if (strcasecmp(*cp, name) == 0) goto found; } found: dnsres_endhtent(&_resp->hostent_state); return (p); } struct dnsres_hostent * dnsres_gethtbyaddr(struct dnsres *_resp, struct dnsres_cbstate *state, const char *addr, int len, int af) { struct dnsres_hostent *p; state->host.h_length = len; state->host.h_addrtype = af; dnsres_sethtent(&_resp->hostent_state, 0); while ((p = dnsres_gethtent(_resp, state))) if (p->h_addrtype == af && !bcmp(p->h_addr_list[0], addr, len)) break; dnsres_endhtent(&_resp->hostent_state); return (p); } static void dnsres_map_v4v6_address(const char *src, char *dst) { u_char *p = (u_char *)dst; char tmp[INADDRSZ]; int i; /* Stash a temporary copy so our caller can update in place. */ bcopy(src, tmp, INADDRSZ); /* Mark this ipv6 addr as a mapped ipv4. */ for (i = 0; i < 10; i++) *p++ = 0x00; *p++ = 0xff; *p++ = 0xff; /* Retrieve the saved copy and we're done. */ bcopy(tmp, (void*)p, INADDRSZ); } static void dnsres_map_v4v6_hostent(struct dnsres_hostent *hp, char **bpp, char *ep) { char **ap; if (hp->h_addrtype != AF_INET || hp->h_length != INADDRSZ) return; hp->h_addrtype = AF_INET6; hp->h_length = IN6ADDRSZ; for (ap = hp->h_addr_list; *ap; ap++) { int i = sizeof(align) - ((u_long)*bpp % sizeof(align)); if (ep - *bpp < (i + IN6ADDRSZ)) { /* Out of memory. Truncate address list here. XXX */ *ap = NULL; return; } *bpp += i; dnsres_map_v4v6_address(*ap, *bpp); *ap = *bpp; *bpp += IN6ADDRSZ; } } struct dnsres_hostent * dnsres_gethostent(struct dnsres *_resp, struct dnsres_cbstate *state) { return (dnsres_gethtent(_resp, state)); } #ifdef RESOLVSORT static void dnsres_addrsort(struct dnsres *_resp, char **ap, int num) { int i, j; char **p; short aval[MAXADDRS]; int needsort = 0; p = ap; for (i = 0; i < num; i++, p++) { for (j = 0 ; (unsigned)j < _resp->nsort; j++) if (_resp->sort_list[j].addr.s_addr == (((struct in_addr *)(*p))->s_addr & _resp->sort_list[j].mask)) break; aval[i] = j; if (needsort == 0 && i > 0 && j < aval[i-1]) needsort = i; } if (!needsort) return; while (needsort < num) { for (j = needsort - 1; j >= 0; j--) { if (aval[j] > aval[j+1]) { char *hp; i = aval[j]; aval[j] = aval[j+1]; aval[j+1] = i; hp = ap[j]; ap[j] = ap[j+1]; ap[j+1] = hp; } else break; } needsort++; } } #endif