/* * 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. */ /* $OpenBSD: getaddrinfo.c,v 1.50 2004/06/07 21:11:23 marc Exp $ */ /* $KAME: getaddrinfo.c,v 1.31 2000/08/31 17:36:43 itojun Exp $ */ /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * 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 project 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 PROJECT 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 PROJECT 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. */ /* * Issues to be discussed: * - Thread safe-ness must be checked. * - Return values. There are nonstandard return values defined and used * in the source code. This is because RFC2553 is silent about which error * code must be returned for which situation. * - IPv4 classful (shortened) form. RFC2553 is silent about it. XNET 5.2 * says to use inet_aton() to convert IPv4 numeric to binary (alows * classful form as a result). * current code - disallow classful form for IPv4 (due to use of inet_pton). * - freeaddrinfo(NULL). RFC2553 is silent about it. XNET 5.2 says it is * invalid. * current code - SEGV on freeaddrinfo(NULL) * Note: * - We use getipnodebyname() just for thread-safeness. There's no intent * to let it do PF_UNSPEC (actually we never pass PF_UNSPEC to * getipnodebyname(). * - The code filters out AFs that are not supported by the kernel, * when globbing NULL hostname (to loopback, or wildcard). Is it the right * thing to do? What is the relationship with post-RFC2553 AI_ADDRCONFIG * in ai_flags? * - (post-2553) semantics of AI_ADDRCONFIG itself is too vague. * (1) what should we do against numeric hostname (2) what should we do * against NULL hostname (3) what is AI_ADDRCONFIG itself. AF not ready? * non-loopback address configured? global address configured? * - To avoid search order issue, we have a big amount of code duplicate * from gethnamaddr.c and some other places. The issues that there's no * lower layer function to lookup "IPv4 or IPv6" record. Calling * gethostbyname2 from getaddrinfo will end up in wrong search order, as * follows: * - The code makes use of following calls when asked to resolver with * ai_family = PF_UNSPEC: * getipnodebyname(host, AF_INET6); * getipnodebyname(host, AF_INET); * This will result in the following queries if the node is configure to * prefer /etc/hosts than DNS: * lookup /etc/hosts for IPv6 address * lookup DNS for IPv6 address * lookup /etc/hosts for IPv4 address * lookup DNS for IPv4 address * which may not meet people's requirement. * The right thing to happen is to have underlying layer which does * PF_UNSPEC lookup (lookup both) and return chain of addrinfos. * This would result in a bit of code duplicate with _dns_ghbyname() and * friends. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dnsres.h" #include "dnsres-internal.h" #include "resolv.h" #define SUCCESS 0 #define ANY 0 #define YES 1 #define NO 0 static const char in_addrany[] = { 0, 0, 0, 0 }; static const char in6_addrany[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const char in_loopback[] = { 127, 0, 0, 1 }; static const char in6_loopback[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; static const struct afd { int a_af; int a_addrlen; int a_socklen; int a_off; const char *a_addrany; const char *a_loopback; int a_scoped; } afdl [] = { {PF_INET6, sizeof(struct in6_addr), sizeof(struct sockaddr_in6), offsetof(struct sockaddr_in6, sin6_addr), in6_addrany, in6_loopback, 1}, {PF_INET, sizeof(struct in_addr), sizeof(struct sockaddr_in), offsetof(struct sockaddr_in, sin_addr), in_addrany, in_loopback, 0}, {0, 0, 0, 0, NULL, NULL, 0}, }; struct explore { int e_af; int e_socktype; int e_protocol; const char *e_protostr; int e_wild; #define WILD_AF(ex) ((ex)->e_wild & 0x01) #define WILD_SOCKTYPE(ex) ((ex)->e_wild & 0x02) #define WILD_PROTOCOL(ex) ((ex)->e_wild & 0x04) }; static const struct explore explore[] = { { PF_INET6, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 }, { PF_INET6, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 }, { PF_INET6, SOCK_RAW, ANY, NULL, 0x05 }, { PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 }, { PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 }, { PF_INET, SOCK_RAW, ANY, NULL, 0x05 }, { PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 }, { PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 }, { PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05 }, { -1, 0, 0, NULL, 0 }, }; #define PTON_MAX 16 struct dnsres_getaddrstate; static int str2number(const char *); static void explore_fqdn(struct dnsres *, const struct addrinfo *, const char *, const char *, struct addrinfo **, void (*cb)(int, struct dnsres_getaddrstate *), struct dnsres_getaddrstate *); static int explore_null(struct dnsres_servent_state *, const struct addrinfo *, const char *, struct addrinfo **); static int explore_numeric(struct dnsres_servent_state *, const struct addrinfo *, const char *, const char *, struct addrinfo **, const char *); static int explore_numeric_scope(struct dnsres_servent_state *, const struct addrinfo *, const char *, const char *, struct addrinfo **); static int get_canonname(const struct addrinfo *, struct addrinfo *, const char *); static struct addrinfo *get_ai(const struct addrinfo *, const struct afd *, const char *); static int get_portmatch(struct dnsres_servent_state *, const struct addrinfo *, const char *); static int get_port(struct dnsres_servent_state *, struct addrinfo *, const char *, int); static const struct afd *find_afd(int); static int ip6_str2scopeid(char *, struct sockaddr_in6 *, u_int32_t *); static void _sethtent(struct dnsres_hostent_state *); static void _endhtent(struct dnsres_hostent_state *); static struct addrinfo * _gethtent(struct dnsres *, const char *, const struct addrinfo *); static struct addrinfo *_files_getaddrinfo(struct dnsres *, const char *, const struct addrinfo *); static struct addrinfo *getanswer(struct dnsres *, const querybuf *, int, const char *, int, const struct addrinfo *); static void _dns_getaddrinfo(struct dnsres_getaddrstate *); /* XXX macros that make external reference is BAD. */ #define GET_AI(ai, afd, addr) \ do { \ /* external reference: pai, error, and label free */ \ (ai) = get_ai(pai, (afd), (addr)); \ if ((ai) == NULL) { \ error = DNSRES_EAI_MEMORY; \ goto free; \ } \ } while (/*CONSTCOND*/0) #define GET_PORT(state, ai, serv) \ do { \ /* external reference: error and label free */ \ error = get_port(state, (ai), (serv), 0); \ if (error != 0) \ goto free; \ } while (/*CONSTCOND*/0) #define GET_CANONNAME(ai, str) \ do { \ /* external reference: pai, error and label free */ \ error = get_canonname(pai, (ai), (str)); \ if (error != 0) \ goto free; \ } while (/*CONSTCOND*/0) #define ERR(err) \ do { \ /* external reference: error, and label bad */ \ error = (err); \ goto bad; \ /*NOTREACHED*/ \ } while (/*CONSTCOND*/0) #define MATCH_FAMILY(x, y, w) \ ((x) == (y) || (/*CONSTCOND*/(w) && ((x) == PF_UNSPEC || (y) == PF_UNSPEC))) #define MATCH(x, y, w) \ ((x) == (y) || (/*CONSTCOND*/(w) && ((x) == ANY || (y) == ANY))) static int str2number(p) const char *p; { char *ep; unsigned long v; if (*p == '\0') return -1; ep = NULL; errno = 0; v = strtoul(p, &ep, 10); if (errno == 0 && ep && *ep == '\0' && v <= UINT_MAX) return v; else return -1; } struct dnsres_getaddrstate { struct dnsres *_resp; struct addrinfo *res_ai; int error; const char *hostname; const char *servname; void (*cb)(struct addrinfo *, int, void *); void (*cb_arg); char lookups[MAXDNSLUS]; int lookup_index; /* explore fqdn state */ struct addrinfo sentinel; struct addrinfo ai; struct addrinfo ai0; struct addrinfo *cur; const struct explore *ex; const struct addrinfo *pai; struct addrinfo *result; struct addrinfo **res; void (*explore_cb)(int, struct dnsres_getaddrstate *); /* _dns_getaddrinfo state */ struct dnsres_target *q_current; struct dnsres_target q; struct dnsres_target q2; querybuf *buf; querybuf *buf2; }; struct dnsres_getaddrstate * dnsres_getaddrstate_new( struct dnsres *_resp, const char *hostname, const char *servname, void (*cb)(struct addrinfo *, int, void *), void (*cb_arg)) { struct dnsres_getaddrstate *state; if ((state = calloc(1, sizeof(struct dnsres_getaddrstate))) == NULL) err(1, "%s: calloc", __func__); state->hostname = hostname; state->servname = servname; state->_resp = _resp; state->cb = cb; state->cb_arg = cb_arg; return (state); } void dnsres_getaddrstate_free(struct dnsres_getaddrstate *state) { free(state); } static void dnsres_usercb(int fd, short what, void *arg) { struct dnsres_getaddrstate *state = arg; (*state->cb)(state->res_ai, state->error, state->cb_arg); /* Free the dnsres callback state */ dnsres_getaddrstate_free(state); } void dnsres_return_state(struct dnsres_getaddrstate *state, int error); void dnsres_getaddrinfo_loop(struct dnsres_getaddrstate *state); void dnsres_getaddrinfo_loopcb(int error, struct dnsres_getaddrstate *state); void dnsres_getaddrinfo_loopend(struct dnsres_getaddrstate *state); void dnsres_getaddrinfo(struct dnsres *_resp, const char *hostname, const char *servname, const struct addrinfo *hints, void (*cb)(struct addrinfo *, int, void *arg), void *cb_arg) { struct dnsres_getaddrstate *state; struct addrinfo *pai; const struct explore *ex; int error = 0; state = dnsres_getaddrstate_new(_resp, hostname, servname, cb, cb_arg); memset(&state->sentinel, 0, sizeof(state->sentinel)); state->cur = &state->sentinel; pai = &state->ai; pai->ai_flags = 0; pai->ai_family = PF_UNSPEC; pai->ai_socktype = ANY; pai->ai_protocol = ANY; pai->ai_addrlen = 0; pai->ai_canonname = NULL; pai->ai_addr = NULL; pai->ai_next = NULL; if (hostname == NULL && servname == NULL) { state->res_ai = NULL; state->error = DNSRES_EAI_NONAME; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return; } if (hints) { /* error check for hints */ if (hints->ai_addrlen || hints->ai_canonname || hints->ai_addr || hints->ai_next) { dnsres_return_state(state, DNSRES_EAI_BADHINTS); return; } if (hints->ai_flags & ~DNSRES_AI_MASK) { dnsres_return_state(state, DNSRES_EAI_BADFLAGS); return; } switch (hints->ai_family) { case PF_UNSPEC: case PF_INET: case PF_INET6: break; default: dnsres_return_state(state, DNSRES_EAI_FAMILY); return; } memcpy(pai, hints, sizeof(*pai)); /* * if both socktype/protocol are specified, check if they * are meaningful combination. */ if (pai->ai_socktype != ANY && pai->ai_protocol != ANY) { for (ex = explore; ex->e_af >= 0; ex++) { if (pai->ai_family != ex->e_af) continue; if (ex->e_socktype == ANY) continue; if (ex->e_protocol == ANY) continue; if (pai->ai_socktype == ex->e_socktype && pai->ai_protocol != ex->e_protocol) { dnsres_return_state(state, DNSRES_EAI_BADHINTS); return; } } } } /* * check for special cases. (1) numeric servname is disallowed if * socktype/protocol are left unspecified. (2) servname is disallowed * for raw and other inet{,6} sockets. */ if (MATCH_FAMILY(pai->ai_family, PF_INET, 1) || MATCH_FAMILY(pai->ai_family, PF_INET6, 1)) { state->ai0 = *pai; /* backup *pai */ if (pai->ai_family == PF_UNSPEC) { #ifdef PF_INET6 pai->ai_family = PF_INET6; #else pai->ai_family = PF_INET; #endif } error = get_portmatch(&_resp->servent_state, pai, servname); if (error) { dnsres_return_state(state, error); return; } *pai = state->ai0; } state->ai0 = *pai; /* NULL hostname, or numeric hostname */ for (ex = explore; ex->e_af >= 0; ex++) { *pai = state->ai0; /* PF_UNSPEC entries are prepared for DNS queries only */ if (ex->e_af == PF_UNSPEC) continue; if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex))) continue; if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) continue; if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) continue; if (pai->ai_family == PF_UNSPEC) pai->ai_family = ex->e_af; if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype; if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol; if (hostname == NULL) error = explore_null(&_resp->servent_state, pai, servname, &state->cur->ai_next); else error = explore_numeric_scope(&_resp->servent_state, pai, hostname, servname, &state->cur->ai_next); if (error) { dnsres_return_state(state, error); return; } while (state->cur && state->cur->ai_next) state->cur = state->cur->ai_next; } /* * XXX * If numeric representation of AF1 can be interpreted as FQDN * representation of AF2, we need to think again about the code below. */ if (state->sentinel.ai_next) { state->res_ai = state->sentinel.ai_next; state->error = SUCCESS; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return; } if (hostname == NULL) { dnsres_return_state(state, DNSRES_EAI_NODATA); return; } if (pai->ai_flags & DNSRES_AI_NUMERICHOST) { dnsres_return_state(state, DNSRES_EAI_NONAME); return; } /* Set up loop variable */ state->ex = explore; dnsres_getaddrinfo_loop(state); return; } void dnsres_getaddrinfo_loop(struct dnsres_getaddrstate *state) { struct dnsres *_resp = state->_resp; struct addrinfo *pai = &state->ai; const struct explore *ex; /* * hostname as alphabetical name. * we would like to prefer AF_INET6 than AF_INET, so we'll make a * outer loop by AFs. */ again: ex = state->ex; if (ex->e_af < 0) { dnsres_getaddrinfo_loopend(state); return; } *pai = state->ai0; /* require exact match for family field */ if (pai->ai_family != ex->e_af) { state->ex++; goto again; } if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex))) { state->ex++; goto again; } if (!MATCH(pai->ai_protocol, ex->e_protocol, WILD_PROTOCOL(ex))) { state->ex++; goto again; } if (pai->ai_socktype == ANY && ex->e_socktype != ANY) pai->ai_socktype = ex->e_socktype; if (pai->ai_protocol == ANY && ex->e_protocol != ANY) pai->ai_protocol = ex->e_protocol; explore_fqdn(_resp, pai, state->hostname, state->servname, &state->cur->ai_next, dnsres_getaddrinfo_loopcb, state); } void dnsres_getaddrinfo_loopcb(int error, struct dnsres_getaddrstate *state) { state->error = error; while (state->cur && state->cur->ai_next) state->cur = state->cur->ai_next; /* Next loop iteration */ state->ex++; dnsres_getaddrinfo_loop(state); } void dnsres_getaddrinfo_loopend(struct dnsres_getaddrstate *state) { int error; /* XXX */ if (state->sentinel.ai_next) error = 0; if (error) goto free; if (error == 0) { if (state->sentinel.ai_next) { state->res_ai = state->sentinel.ai_next; state->error = SUCCESS; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); return; } else error = DNSRES_EAI_FAIL; } free: dnsres_return_state(state, error); return; } void dnsres_return_state(struct dnsres_getaddrstate *state, int error) { if (state->sentinel.ai_next) freeaddrinfo(state->sentinel.ai_next); state->res_ai = NULL; state->error = error; event_once(-1, EV_TIMEOUT, dnsres_usercb, state, NULL); } /* * FQDN hostname, DNS lookup */ static void explore_fqdn_loop(struct dnsres_getaddrstate *state); static void explore_fqdn_loopend(struct dnsres_getaddrstate *state); static void explore_fqdn( struct dnsres *_resp, const struct addrinfo *pai, const char *hostname, const char *servname, struct addrinfo **res, void (*cb)(int, struct dnsres_getaddrstate *), struct dnsres_getaddrstate *state ) { /* * if the servname does not match socktype/protocol, ignore it. */ if (get_portmatch(&_resp->servent_state, pai, servname) != 0) { (*cb)(0, state); return; } bcopy(_resp->lookups, state->lookups, sizeof(state->lookups)); if (state->lookups[0] == '\0') strlcpy(state->lookups, "bf", sizeof(state->lookups)); state->pai = pai; state->result = NULL; state->res = res; state->explore_cb = cb; state->lookup_index = 0; explore_fqdn_loop(state); } static void explore_fqdn_loop(struct dnsres_getaddrstate *state) { struct dnsres *_resp = state->_resp; again: if (state->lookup_index == MAXDNSLUS || state->result != NULL || !state->lookups[state->lookup_index]) { explore_fqdn_loopend(state); return; } /* * The yp/dns/files getaddrinfo functions are not thread safe. * Protect them with a mutex. */ switch (state->lookups[state->lookup_index++]) { case 'b': _dns_getaddrinfo(state); break; case 'f': state->result = _files_getaddrinfo(_resp, state->hostname, state->pai); goto again; } } static void explore_fqdn_loopend(struct dnsres_getaddrstate *state) { int error; struct dnsres *_resp = state->_resp; if (state->result) { struct addrinfo *cur; for (cur = state->result; cur; cur = cur->ai_next) { error = get_port(&_resp->servent_state, cur, state->servname, 0); if (error != 0) goto free; /* canonname should be filled already */ } *state->res = state->result; (*state->explore_cb)(0, state); return; } else { /* translate error code */ switch (_resp->dr_errno) { case DNSRES_NETDB_SUCCESS: error = DNSRES_EAI_FAIL; /*XXX strange */ break; case DNSRES_HOST_NOT_FOUND: error = DNSRES_EAI_NODATA; break; case DNSRES_TRY_AGAIN: error = DNSRES_EAI_AGAIN; break; case DNSRES_NO_RECOVERY: error = DNSRES_EAI_FAIL; break; case DNSRES_NO_DATA: #if DNSRES_NO_ADDRESS != DNSRES_NO_DATA case DNSRES_NO_ADDRESS: #endif error = DNSRES_EAI_NODATA; break; default: /* unknown ones */ error = DNSRES_EAI_FAIL; break; } } free: if (state->result) { freeaddrinfo(state->result); state->result = NULL; } (*state->explore_cb)(error, state); return; } /* * hostname == NULL. * passive socket -> anyaddr (0.0.0.0 or ::) * non-passive socket -> localhost (127.0.0.1 or ::1) */ static int explore_null( struct dnsres_servent_state *state, const struct addrinfo *pai, const char *servname, struct addrinfo **res ) { int s; const struct afd *afd; struct addrinfo *cur; struct addrinfo sentinel; int error; *res = NULL; sentinel.ai_next = NULL; cur = &sentinel; /* * filter out AFs that are not supported by the kernel * XXX errno? */ s = socket(pai->ai_family, SOCK_DGRAM, 0); if (s < 0) { if (errno != EMFILE) return 0; } else close(s); /* * if the servname does not match socktype/protocol, ignore it. */ if (get_portmatch(state, pai, servname) != 0) return 0; afd = find_afd(pai->ai_family); if (afd == NULL) return 0; if (pai->ai_flags & DNSRES_AI_PASSIVE) { GET_AI(cur->ai_next, afd, afd->a_addrany); /* xxx meaningless? * GET_CANONNAME(cur->ai_next, "anyaddr"); */ GET_PORT(state, cur->ai_next, servname); } else { GET_AI(cur->ai_next, afd, afd->a_loopback); /* xxx meaningless? * GET_CANONNAME(cur->ai_next, "localhost"); */ GET_PORT(state, cur->ai_next, servname); } cur = cur->ai_next; *res = sentinel.ai_next; return 0; free: if (sentinel.ai_next) freeaddrinfo(sentinel.ai_next); return error; } /* * numeric hostname */ static int explore_numeric( struct dnsres_servent_state *state, const struct addrinfo *pai, const char *hostname, const char *servname, struct addrinfo **res, const char *canonname ) { const struct afd *afd; struct addrinfo *cur; struct addrinfo sentinel; int error; char pton[PTON_MAX]; *res = NULL; sentinel.ai_next = NULL; cur = &sentinel; /* * if the servname does not match socktype/protocol, ignore it. */ if (get_portmatch(state, pai, servname) != 0) return 0; afd = find_afd(pai->ai_family); if (afd == NULL) return 0; switch (afd->a_af) { #if 0 /*X/Open spec*/ case AF_INET: if (inet_aton(hostname, (struct in_addr *)pton) == 1) { if (pai->ai_family == afd->a_af || pai->ai_family == PF_UNSPEC /*?*/) { GET_AI(cur->ai_next, afd, pton); GET_PORT(cur->ai_next, servname); if ((pai->ai_flags & DNSRES_AI_CANONNAME)) { /* * Set the numeric address itself as * the canonical name, based on a * clarification in rfc2553bis-03. */ GET_CANONNAME(cur->ai_next, canonname); } while (cur && cur->ai_next) cur = cur->ai_next; } else ERR(DNSRES_EAI_FAMILY); /*xxx*/ } break; #endif default: if (inet_pton(afd->a_af, hostname, pton) == 1) { if (pai->ai_family == afd->a_af || pai->ai_family == PF_UNSPEC /*?*/) { GET_AI(cur->ai_next, afd, pton); GET_PORT(state, cur->ai_next, servname); if ((pai->ai_flags & DNSRES_AI_CANONNAME)) { /* * Set the numeric address itself as * the canonical name, based on a * clarification in rfc2553bis-03. */ GET_CANONNAME(cur->ai_next, canonname); } while (cur && cur->ai_next) cur = cur->ai_next; } else ERR(DNSRES_EAI_FAMILY); /*xxx*/ } break; } *res = sentinel.ai_next; return 0; free: bad: if (sentinel.ai_next) freeaddrinfo(sentinel.ai_next); return error; } /* * numeric hostname with scope */ static int explore_numeric_scope( struct dnsres_servent_state *state, const struct addrinfo *pai, const char *hostname, const char *servname, struct addrinfo **res ) { #if !defined(DNSRES_SCOPE_DELIMITER) return explore_numeric(state, pai, hostname, servname, res, hostname); #else const struct afd *afd; struct addrinfo *cur; int error; char *cp, *hostname2 = NULL, *scope, *addr; struct sockaddr_in6 *sin6; /* * if the servname does not match socktype/protocol, ignore it. */ if (get_portmatch(state, pai, servname) != 0) return 0; afd = find_afd(pai->ai_family); if (afd == NULL) return 0; if (!afd->a_scoped) return explore_numeric(state, pai, hostname, servname, res, hostname); cp = strchr(hostname, DNSRES_SCOPE_DELIMITER); if (cp == NULL) return explore_numeric(state, pai, hostname, servname, res, hostname); /* * Handle special case of */ hostname2 = strdup(hostname); if (hostname2 == NULL) return DNSRES_EAI_MEMORY; /* terminate at the delimiter */ hostname2[cp - hostname] = '\0'; addr = hostname2; scope = cp + 1; error = explore_numeric(state, pai, addr, servname, res, hostname); if (error == 0) { u_int32_t scopeid; for (cur = *res; cur; cur = cur->ai_next) { if (cur->ai_family != AF_INET6) continue; sin6 = (struct sockaddr_in6 *)(void *)cur->ai_addr; if (ip6_str2scopeid(scope, sin6, &scopeid) == -1) { free(hostname2); return(DNSRES_EAI_NODATA); /* XXX: is return OK? */ } sin6->sin6_scope_id = scopeid; } } free(hostname2); return error; #endif } static int get_canonname(pai, ai, str) const struct addrinfo *pai; struct addrinfo *ai; const char *str; { if ((pai->ai_flags & DNSRES_AI_CANONNAME) != 0) { ai->ai_canonname = strdup(str); if (ai->ai_canonname == NULL) return DNSRES_EAI_MEMORY; } return 0; } static struct addrinfo * get_ai(pai, afd, addr) const struct addrinfo *pai; const struct afd *afd; const char *addr; { char *p; struct addrinfo *ai; ai = (struct addrinfo *)malloc(sizeof(struct addrinfo) + (afd->a_socklen)); if (ai == NULL) return NULL; memcpy(ai, pai, sizeof(struct addrinfo)); ai->ai_addr = (struct sockaddr *)(void *)(ai + 1); memset(ai->ai_addr, 0, (size_t)afd->a_socklen); #ifdef HAVE_SIN_LEN ai->ai_addr->sa_len = afd->a_socklen; #endif ai->ai_addrlen = afd->a_socklen; ai->ai_addr->sa_family = ai->ai_family = afd->a_af; p = (char *)(void *)(ai->ai_addr); memcpy(p + afd->a_off, addr, (size_t)afd->a_addrlen); return ai; } static int get_portmatch( struct dnsres_servent_state *state, const struct addrinfo *ai, const char *servname ) { /* get_port does not touch first argument. when matchonly == 1. */ /* LINTED const cast */ return get_port(state, (struct addrinfo *)ai, servname, 1); } static int get_port( struct dnsres_servent_state *state, struct addrinfo *ai, const char *servname, int matchonly ) { char buf[1024]; const char *proto; struct dnsres_servent *sp; int port; int allownumeric; if (servname == NULL) return 0; switch (ai->ai_family) { case AF_INET: #ifdef AF_INET6 case AF_INET6: #endif break; default: return 0; } switch (ai->ai_socktype) { case SOCK_RAW: return DNSRES_EAI_SERVICE; case SOCK_DGRAM: case SOCK_STREAM: allownumeric = 1; break; case ANY: allownumeric = 0; break; default: return DNSRES_EAI_SOCKTYPE; } port = str2number(servname); if (port >= 0) { if (!allownumeric) return DNSRES_EAI_SERVICE; if (port < 0 || port > 65535) return DNSRES_EAI_SERVICE; port = htons(port); } else { if (ai->ai_flags & DNSRES_AI_NUMERICSERV) return DNSRES_EAI_NONAME; switch (ai->ai_socktype) { case SOCK_DGRAM: proto = "udp"; break; case SOCK_STREAM: proto = "tcp"; break; default: proto = NULL; break; } sp = dnsres_getservbyname(state, servname, proto, (struct dnsres_servent*) buf, buf + sizeof(struct dnsres_servent), sizeof(buf) - sizeof(struct dnsres_servent)); if (sp == NULL) return DNSRES_EAI_SERVICE; port = sp->s_port; } if (!matchonly) { switch (ai->ai_family) { case AF_INET: ((struct sockaddr_in *)(void *) ai->ai_addr)->sin_port = port; break; case AF_INET6: ((struct sockaddr_in6 *)(void *) ai->ai_addr)->sin6_port = port; break; } } return 0; } static const struct afd * find_afd(af) int af; { const struct afd *afd; if (af == PF_UNSPEC) return NULL; for (afd = afdl; afd->a_af; afd++) { if (afd->a_af == af) return afd; } return NULL; } /* convert a string to a scope identifier. XXX: IPv6 specific */ static int ip6_str2scopeid(scope, sin6, scopeid) char *scope; struct sockaddr_in6 *sin6; u_int32_t *scopeid; { u_long lscopeid; struct in6_addr *a6 = &sin6->sin6_addr; char *ep; /* empty scopeid portion is invalid */ if (*scope == '\0') return -1; if (IN6_IS_ADDR_LINKLOCAL(a6) || IN6_IS_ADDR_MC_LINKLOCAL(a6)) { /* * We currently assume a one-to-one mapping between links * and interfaces, so we simply use interface indices for * like-local scopes. */ *scopeid = if_nametoindex(scope); if (*scopeid == 0) goto trynumeric; return 0; } /* still unclear about literal, allow numeric only - placeholder */ if (IN6_IS_ADDR_SITELOCAL(a6) || IN6_IS_ADDR_MC_SITELOCAL(a6)) goto trynumeric; if (IN6_IS_ADDR_MC_ORGLOCAL(a6)) goto trynumeric; else goto trynumeric; /* global */ /* try to convert to a numeric id as a last resort */ trynumeric: errno = 0; lscopeid = strtoul(scope, &ep, 10); *scopeid = (u_int32_t)(lscopeid & 0xffffffffUL); if (errno == 0 && ep && *ep == '\0' && *scopeid == lscopeid) return 0; else return -1; } /* code duplicate with gethnamaddr.c */ static const char AskedForGot[] = "gethostby*.getanswer: asked for \"%s\", got \"%s\""; static struct addrinfo * getanswer( struct dnsres *_resp, const querybuf *answer, int anslen, const char *qname, int qtype, const struct addrinfo *pai ) { struct addrinfo sentinel, *cur; struct addrinfo ai; const struct afd *afd; char *canonname; const DNSRES_HEADER *hp; const u_char *cp; int n; const u_char *eom; char *bp, *ep; int type, class, ancount, qdcount; int haveanswer, had_error; char tbuf[DNSRES_MAXDNAME]; int (*name_ok)(const char *); char hostbuf[8*1024]; memset(&sentinel, 0, sizeof(sentinel)); cur = &sentinel; canonname = NULL; eom = answer->buf + anslen; switch (qtype) { case DNSRES_T_A: case DNSRES_T_AAAA: case DNSRES_T_ANY: /*use DNSRES_T_ANY only for T_A/T_AAAA lookup*/ name_ok = res_hnok; break; default: return (NULL); /* XXX should be abort() -- but that is illegal */ } /* * find first satisfactory answer */ hp = &answer->hdr; ancount = ntohs(hp->ancount); qdcount = ntohs(hp->qdcount); bp = hostbuf; ep = hostbuf + sizeof 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 || qtype == DNSRES_T_ANY) { /* 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 */ if (n >= MAXHOSTNAMELEN) { _resp->dr_errno = DNSRES_NO_RECOVERY; return (NULL); } canonname = bp; bp += n; /* The qname can be abbreviated, but h_name is now absolute. */ qname = canonname; } 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 */ type = getshort(cp); cp += DNSRES_INT16SZ; /* type */ class = getshort(cp); cp += DNSRES_INT16SZ + DNSRES_INT32SZ; /* class, TTL */ n = getshort(cp); cp += DNSRES_INT16SZ; /* len */ if (class != DNSRES_C_IN) { /* XXX - debug? syslog? */ cp += n; continue; /* XXX - had_error++ ? */ } if ((qtype == DNSRES_T_A || qtype == DNSRES_T_AAAA || qtype == DNSRES_T_ANY) && type == DNSRES_T_CNAME) { n = dn_expand(answer->buf, eom, cp, tbuf, sizeof tbuf); if ((n < 0) || !(*name_ok)(tbuf)) { had_error++; continue; } cp += n; /* Get canonical name. */ n = strlen(tbuf) + 1; /* for the \0 */ if (n > ep - bp || n >= MAXHOSTNAMELEN) { had_error++; continue; } strlcpy(bp, tbuf, ep - bp); canonname = bp; bp += n; continue; } if (qtype == DNSRES_T_ANY) { if (!(type == DNSRES_T_A || type == DNSRES_T_AAAA)) { cp += n; continue; } } else if (type != qtype) { if (type != DNSRES_T_KEY && type != DNSRES_T_SIG) 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_A: case DNSRES_T_AAAA: if (strcasecmp(canonname, bp) != 0) { syslog(LOG_NOTICE|LOG_AUTH, AskedForGot, canonname, bp); cp += n; continue; /* XXX - had_error++ ? */ } if (type == DNSRES_T_A && n != INADDRSZ) { cp += n; continue; } if (type == DNSRES_T_AAAA && n != IN6ADDRSZ) { 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; canonname = bp; nn = strlen(bp) + 1; /* for the \0 */ bp += nn; } /* don't overwrite pai */ ai = *pai; ai.ai_family = (type == DNSRES_T_A) ? AF_INET : AF_INET6; afd = find_afd(ai.ai_family); if (afd == NULL) { cp += n; continue; } cur->ai_next = get_ai(&ai, afd, (const char *)cp); if (cur->ai_next == NULL) had_error++; while (cur && cur->ai_next) cur = cur->ai_next; cp += n; break; default: abort(); /* XXX abort illegal in library */ } if (!had_error) haveanswer++; } if (haveanswer) { if (!canonname) (void)get_canonname(pai, sentinel.ai_next, qname); else (void)get_canonname(pai, sentinel.ai_next, canonname); _resp->dr_errno = DNSRES_NETDB_SUCCESS; return sentinel.ai_next; } _resp->dr_errno = DNSRES_NO_RECOVERY; return NULL; } /* called only from explore_fqdn */ static void _dns_getaddrinfo_cb(int, void *); /*ARGSUSED*/ static void _dns_getaddrinfo(struct dnsres_getaddrstate *state) { struct dnsres *_resp = state->_resp; const char *name = state->hostname; const struct addrinfo *pai = state->pai; memset(&state->q, 0, sizeof(state->q2)); memset(&state->q2, 0, sizeof(state->q2)); state->buf = malloc(sizeof(*state->buf)); if (state->buf == NULL) { _resp->dr_errno = DNSRES_NETDB_INTERNAL; state->result = NULL; explore_fqdn_loop(state); return; } state->buf2 = malloc(sizeof(*state->buf2)); if (state->buf2 == NULL) { free(state->buf); _resp->dr_errno = DNSRES_NETDB_INTERNAL; state->result = NULL; explore_fqdn_loop(state); return; } switch (pai->ai_family) { case AF_UNSPEC: /* prefer IPv6 */ 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); state->q.next = &state->q2; state->q2.qclass = DNSRES_C_IN; state->q2.qtype = DNSRES_T_A; state->q2.answer = state->buf2->buf; state->q2.anslen = sizeof(state->buf2->buf); break; case AF_INET: 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->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: free(state->buf); free(state->buf2); state->result = NULL; explore_fqdn_loop(state); return; } state->q_current = &state->q; res_search(_resp, name, &state->q, _dns_getaddrinfo_cb, state); } static void _dns_getaddrinfo_cb(int ret, void *arg) { struct dnsres_getaddrstate *state = arg; struct dnsres *_resp = state->_resp; struct addrinfo sentinel, *cur, *ai; memset(&sentinel, 0, sizeof(sentinel)); cur = &sentinel; if (ret < 0) { free(state->buf); free(state->buf2); state->result = NULL; explore_fqdn_loop(state); return; } ai = getanswer(_resp, state->buf, state->q.n, state->q.name, state->q.qtype, state->pai); if (ai) { cur->ai_next = ai; while (cur && cur->ai_next) cur = cur->ai_next; } if (state->q.next) { ai = getanswer(_resp, state->buf2, state->q2.n, state->q2.name, state->q2.qtype, state->pai); if (ai) cur->ai_next = ai; } free(state->buf); free(state->buf2); state->result = sentinel.ai_next; explore_fqdn_loop(state); } static void _sethtent(struct dnsres_hostent_state *state) { if (!state->hostf) state->hostf = fopen(DNSRES_PATH_HOSTS, "r" ); else rewind(state->hostf); } static void _endhtent(struct dnsres_hostent_state *state) { if (state->hostf) { (void) fclose(state->hostf); state->hostf = NULL; } } static struct addrinfo * _gethtent( struct dnsres *_resp, const char *name, const struct addrinfo *pai ) { struct dnsres_hostent_state *state = &_resp->hostent_state; char *p; char *cp, *tname, *cname; struct addrinfo hints, *res0, *res; int error; const char *addr; char hostbuf[8*1024]; if (!state->hostf && !(state->hostf = fopen(DNSRES_PATH_HOSTS, "r" ))) return (NULL); again: if (!(p = fgets(hostbuf, sizeof hostbuf, state->hostf))) return (NULL); if (*p == '#') goto again; if (!(cp = strpbrk(p, "#\n"))) goto again; *cp = '\0'; if (!(cp = strpbrk(p, " \t"))) goto again; *cp++ = '\0'; addr = p; /* if this is not something we're looking for, skip it. */ cname = NULL; while (cp && *cp) { if (*cp == ' ' || *cp == '\t') { cp++; continue; } if (!cname) cname = cp; tname = cp; if ((cp = strpbrk(cp, " \t")) != NULL) *cp++ = '\0'; if (strcasecmp(name, tname) == 0) goto found; } goto again; found: hints = *pai; hints.ai_flags = DNSRES_AI_NUMERICHOST; error = getaddrinfo(addr, NULL, &hints, &res0); if (error) goto again; for (res = res0; res; res = res->ai_next) { /* cover it up */ res->ai_flags = pai->ai_flags; if (pai->ai_flags & DNSRES_AI_CANONNAME) { if (get_canonname(pai, res, cname) != 0) { freeaddrinfo(res0); goto again; } } } return res0; } /*ARGSUSED*/ static struct addrinfo * _files_getaddrinfo( struct dnsres *_resp, const char *name, const struct addrinfo *pai ) { struct addrinfo sentinel, *cur; struct addrinfo *p; memset(&sentinel, 0, sizeof(sentinel)); cur = &sentinel; _sethtent(&_resp->hostent_state); while ((p = _gethtent(_resp, name, pai)) != NULL) { cur->ai_next = p; while (cur && cur->ai_next) cur = cur->ai_next; } _endhtent(&_resp->hostent_state); return sentinel.ai_next; }