diff options
Diffstat (limited to 'lib/irs/getaddrinfo.c')
-rw-r--r-- | lib/irs/getaddrinfo.c | 1365 |
1 files changed, 1365 insertions, 0 deletions
diff --git a/lib/irs/getaddrinfo.c b/lib/irs/getaddrinfo.c new file mode 100644 index 0000000..f450a55 --- /dev/null +++ b/lib/irs/getaddrinfo.c @@ -0,0 +1,1365 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +/** + * getaddrinfo() is used to get a list of IP addresses and port + * numbers for host hostname and service servname as defined in RFC3493. + * hostname and servname are pointers to null-terminated strings + * or NULL. hostname is either a host name or a numeric host address + * string: a dotted decimal IPv4 address or an IPv6 address. servname is + * either a decimal port number or a service name as listed in + * /etc/services. + * + * If the operating system does not provide a struct addrinfo, the + * following structure is used: + * + * \code + * struct addrinfo { + * int ai_flags; // AI_PASSIVE, AI_CANONNAME + * int ai_family; // PF_xxx + * int ai_socktype; // SOCK_xxx + * int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6 + * size_t ai_addrlen; // length of ai_addr + * char *ai_canonname; // canonical name for hostname + * struct sockaddr *ai_addr; // binary address + * struct addrinfo *ai_next; // next structure in linked list + * }; + * \endcode + * + * + * hints is an optional pointer to a struct addrinfo. This structure can + * be used to provide hints concerning the type of socket that the caller + * supports or wishes to use. The caller can supply the following + * structure elements in *hints: + * + * <ul> + * <li>ai_family: + * The protocol family that should be used. When ai_family is set + * to PF_UNSPEC, it means the caller will accept any protocol + * family supported by the operating system.</li> + * + * <li>ai_socktype: + * denotes the type of socket -- SOCK_STREAM, SOCK_DGRAM or + * SOCK_RAW -- that is wanted. When ai_socktype is zero the caller + * will accept any socket type.</li> + * + * <li>ai_protocol: + * indicates which transport protocol is wanted: IPPROTO_UDP or + * IPPROTO_TCP. If ai_protocol is zero the caller will accept any + * protocol.</li> + * + * <li>ai_flags: + * Flag bits. If the AI_CANONNAME bit is set, a successful call to + * getaddrinfo() will return a null-terminated string + * containing the canonical name of the specified hostname in + * ai_canonname of the first addrinfo structure returned. Setting + * the AI_PASSIVE bit indicates that the returned socket address + * structure is intended for used in a call to bind(2). In this + * case, if the hostname argument is a NULL pointer, then the IP + * address portion of the socket address structure will be set to + * INADDR_ANY for an IPv4 address or IN6ADDR_ANY_INIT for an IPv6 + * address.<br /><br /> + * + * When ai_flags does not set the AI_PASSIVE bit, the returned + * socket address structure will be ready for use in a call to + * connect(2) for a connection-oriented protocol or connect(2), + * sendto(2), or sendmsg(2) if a connectionless protocol was + * chosen. The IP address portion of the socket address structure + * will be set to the loopback address if hostname is a NULL + * pointer and AI_PASSIVE is not set in ai_flags.<br /><br /> + * + * If ai_flags is set to AI_NUMERICHOST it indicates that hostname + * should be treated as a numeric string defining an IPv4 or IPv6 + * address and no name resolution should be attempted. + * </li></ul> + * + * All other elements of the struct addrinfo passed via hints must be + * zero. + * + * A hints of NULL is treated as if the caller provided a struct addrinfo + * initialized to zero with ai_familyset to PF_UNSPEC. + * + * After a successful call to getaddrinfo(), *res is a pointer to a + * linked list of one or more addrinfo structures. Each struct addrinfo + * in this list cn be processed by following the ai_next pointer, until a + * NULL pointer is encountered. The three members ai_family, ai_socktype, + * and ai_protocol in each returned addrinfo structure contain the + * corresponding arguments for a call to socket(2). For each addrinfo + * structure in the list, the ai_addr member points to a filled-in socket + * address structure of length ai_addrlen. + * + * All of the information returned by getaddrinfo() is dynamically + * allocated: the addrinfo structures, and the socket address structures + * and canonical host name strings pointed to by the addrinfostructures. + * Memory allocated for the dynamically allocated structures created by a + * successful call to getaddrinfo() is released by freeaddrinfo(). + * ai is a pointer to a struct addrinfo created by a call to getaddrinfo(). + * + * \section irsreturn RETURN VALUES + * + * getaddrinfo() returns zero on success or one of the error codes + * listed in gai_strerror() if an error occurs. If both hostname and + * servname are NULL getaddrinfo() returns #EAI_NONAME. + * + * \section irssee SEE ALSO + * + * getaddrinfo(), freeaddrinfo(), + * gai_strerror(), RFC3493, getservbyname(3), connect(2), + * sendto(2), sendmsg(2), socket(2). + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#include <windows.h> +#include <winsock2.h> +#include <ws2tcpip.h> +#endif /* ifdef _WIN32 */ + +#include <isc/app.h> +#include <isc/buffer.h> +#include <isc/lib.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/print.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <dns/client.h> +#include <dns/fixedname.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatastruct.h> +#include <dns/rdatatype.h> +#include <dns/result.h> + +#include <irs/context.h> +#include <irs/netdb.h> +#include <irs/resconf.h> + +#define SA(addr) ((struct sockaddr *)(addr)) +#define SIN(addr) ((struct sockaddr_in *)(addr)) +#define SIN6(addr) ((struct sockaddr_in6 *)(addr)) +#define SLOCAL(addr) ((struct sockaddr_un *)(addr)) + +/*! \struct addrinfo + */ +static struct addrinfo * +ai_concat(struct addrinfo *ai1, struct addrinfo *ai2), + *ai_reverse(struct addrinfo *oai), + *ai_clone(struct addrinfo *oai, int family), + *ai_alloc(int family, int addrlen); +#ifdef AF_LOCAL +static int +get_local(const char *name, int socktype, struct addrinfo **res); +#endif /* ifdef AF_LOCAL */ + +static int +resolve_name(int family, const char *hostname, int flags, struct addrinfo **aip, + int socktype, int port); + +static int +add_ipv4(const char *hostname, int flags, struct addrinfo **aip, int socktype, + int port); +static int +add_ipv6(const char *hostname, int flags, struct addrinfo **aip, int socktype, + int port); +static void +set_order(int, int (**)(const char *, int, struct addrinfo **, int, int)); +static void +_freeaddrinfo(struct addrinfo *ai); + +#define FOUND_IPV4 0x1 +#define FOUND_IPV6 0x2 +#define FOUND_MAX 2 + +/*% + * Try converting the scope identifier in 'src' to a network interface index. + * Upon success, return true and store the resulting index in 'dst'. Upon + * failure, return false. + */ +static bool +parse_scopeid(const char *src, uint32_t *dst) { + uint32_t scopeid = 0; + + REQUIRE(src != NULL); + REQUIRE(dst != NULL); + +#ifdef HAVE_IF_NAMETOINDEX + /* + * Try using if_nametoindex() first if it is available. As it does not + * handle numeric scopes, we do not simply return if it fails. + */ + scopeid = (uint32_t)if_nametoindex(src); +#endif /* ifdef HAVE_IF_NAMETOINDEX */ + + /* + * Fall back to numeric scope processing if if_nametoindex() either + * fails or is unavailable. + */ + if (scopeid == 0) { + char *endptr = NULL; + scopeid = (uint32_t)strtoul(src, &endptr, 10); + /* + * The scope identifier must not be empty and no trailing + * characters are allowed after it. + */ + if (src == endptr || endptr == NULL || *endptr != '\0') { + return (false); + } + } + + *dst = scopeid; + + return (true); +} + +#define ISC_AI_MASK (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST) +/*% + * Get a list of IP addresses and port numbers for host hostname and + * service servname. + */ +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) { + struct servent *sp; + const char *proto; + int family, socktype, flags, protocol; + struct addrinfo *ai, *ai_list; + int err = 0; + int port, i; + int (*net_order[FOUND_MAX + 1])(const char *, int, struct addrinfo **, + int, int); + + if (hostname == NULL && servname == NULL) { + return (EAI_NONAME); + } + + proto = NULL; + if (hints != NULL) { + if ((hints->ai_flags & ~(ISC_AI_MASK)) != 0) { + return (EAI_BADFLAGS); + } + if (hints->ai_addrlen || hints->ai_canonname || + hints->ai_addr || hints->ai_next) + { + errno = EINVAL; + return (EAI_SYSTEM); + } + family = hints->ai_family; + socktype = hints->ai_socktype; + protocol = hints->ai_protocol; + flags = hints->ai_flags; + switch (family) { + case AF_UNSPEC: + switch (hints->ai_socktype) { + case SOCK_STREAM: + proto = "tcp"; + break; + case SOCK_DGRAM: + proto = "udp"; + break; + } + break; + case AF_INET: + case AF_INET6: + switch (hints->ai_socktype) { + case 0: + break; + case SOCK_STREAM: + proto = "tcp"; + break; + case SOCK_DGRAM: + proto = "udp"; + break; + case SOCK_RAW: + break; + default: + return (EAI_SOCKTYPE); + } + break; +#ifdef AF_LOCAL + case AF_LOCAL: + switch (hints->ai_socktype) { + case 0: + break; + case SOCK_STREAM: + break; + case SOCK_DGRAM: + break; + default: + return (EAI_SOCKTYPE); + } + break; +#endif /* ifdef AF_LOCAL */ + default: + return (EAI_FAMILY); + } + } else { + protocol = 0; + family = 0; + socktype = 0; + flags = 0; + } + +#ifdef AF_LOCAL + /*! + * First, deal with AF_LOCAL. If the family was not set, + * then assume AF_LOCAL if the first character of the + * hostname/servname is '/'. + */ + + if (hostname != NULL && + (family == AF_LOCAL || (family == 0 && *hostname == '/'))) + { + return (get_local(hostname, socktype, res)); + } + + if (servname != NULL && + (family == AF_LOCAL || (family == 0 && *servname == '/'))) + { + return (get_local(servname, socktype, res)); + } +#endif /* ifdef AF_LOCAL */ + + /* + * Ok, only AF_INET and AF_INET6 left. + */ + ai_list = NULL; + + /* + * First, look up the service name (port) if it was + * requested. If the socket type wasn't specified, then + * try and figure it out. + */ + if (servname != NULL) { + char *e; + + port = strtol(servname, &e, 10); + if (*e == '\0') { + if (socktype == 0) { + return (EAI_SOCKTYPE); + } + if (port < 0 || port > 65535) { + return (EAI_SERVICE); + } + port = htons((unsigned short)port); + } else { +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + + wVersionRequested = MAKEWORD(2, 0); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + return (EAI_FAIL); + } +#endif /* ifdef _WIN32 */ + sp = getservbyname(servname, proto); + if (sp != NULL) { + port = sp->s_port; + } +#ifdef _WIN32 + WSACleanup(); +#endif /* ifdef _WIN32 */ + if (sp == NULL) { + return (EAI_SERVICE); + } + if (socktype == 0) { + if (strcmp(sp->s_proto, "tcp") == 0) { + socktype = SOCK_STREAM; + } else if (strcmp(sp->s_proto, "udp") == 0) { + socktype = SOCK_DGRAM; + } + } + } + } else { + port = 0; + } + + /* + * Next, deal with just a service name, and no hostname. + * (we verified that one of them was non-null up above). + */ + if (hostname == NULL && (flags & AI_PASSIVE) != 0) { + if (family == AF_INET || family == 0) { + ai = ai_alloc(AF_INET, sizeof(struct sockaddr_in)); + if (ai == NULL) { + return (EAI_MEMORY); + } + ai->ai_socktype = socktype; + ai->ai_protocol = protocol; + SIN(ai->ai_addr)->sin_port = port; + ai->ai_next = ai_list; + ai_list = ai; + } + + if (family == AF_INET6 || family == 0) { + ai = ai_alloc(AF_INET6, sizeof(struct sockaddr_in6)); + if (ai == NULL) { + _freeaddrinfo(ai_list); + return (EAI_MEMORY); + } + ai->ai_socktype = socktype; + ai->ai_protocol = protocol; + SIN6(ai->ai_addr)->sin6_port = port; + ai->ai_next = ai_list; + ai_list = ai; + } + + *res = ai_list; + return (0); + } + + /* + * If the family isn't specified or AI_NUMERICHOST specified, check + * first to see if it is a numeric address. + * Though the gethostbyname2() routine will recognize numeric addresses, + * it will only recognize the format that it is being called for. Thus, + * a numeric AF_INET address will be treated by the AF_INET6 call as + * a domain name, and vice versa. Checking for both numerics here + * avoids that. + */ + if (hostname != NULL && (family == 0 || (flags & AI_NUMERICHOST) != 0)) + { + char abuf[sizeof(struct in6_addr)]; + char nbuf[NI_MAXHOST]; + int addrsize, addroff; + char ntmp[NI_MAXHOST]; + uint32_t scopeid = 0; + + /* + * Scope identifier portion. + */ + ntmp[0] = '\0'; + if (strchr(hostname, '%') != NULL) { + char *p; + strlcpy(ntmp, hostname, sizeof(ntmp)); + p = strchr(ntmp, '%'); + + if (p != NULL && parse_scopeid(p + 1, &scopeid)) { + *p = '\0'; + } else { + ntmp[0] = '\0'; + } + } + + if (inet_pton(AF_INET, hostname, (struct in_addr *)abuf) == 1) { + if (family == AF_INET6) { + /* + * Convert to a V4 mapped address. + */ + struct in6_addr *a6 = (struct in6_addr *)abuf; + memmove(&a6->s6_addr[12], &a6->s6_addr[0], 4); + memset(&a6->s6_addr[10], 0xff, 2); + memset(&a6->s6_addr[0], 0, 10); + goto inet6_addr; + } + addrsize = sizeof(struct in_addr); + addroff = offsetof(struct sockaddr_in, sin_addr); + family = AF_INET; + goto common; + } else if (ntmp[0] != '\0' && + inet_pton(AF_INET6, ntmp, abuf) == 1) + { + if (family && family != AF_INET6) { + return (EAI_NONAME); + } + addrsize = sizeof(struct in6_addr); + addroff = offsetof(struct sockaddr_in6, sin6_addr); + family = AF_INET6; + goto common; + } else if (inet_pton(AF_INET6, hostname, abuf) == 1) { + if (family != 0 && family != AF_INET6) { + return (EAI_NONAME); + } + inet6_addr: + addrsize = sizeof(struct in6_addr); + addroff = offsetof(struct sockaddr_in6, sin6_addr); + family = AF_INET6; + + common: + ai = ai_alloc(family, + ((family == AF_INET6) + ? sizeof(struct sockaddr_in6) + : sizeof(struct sockaddr_in))); + if (ai == NULL) { + return (EAI_MEMORY); + } + ai_list = ai; + ai->ai_socktype = socktype; + SIN(ai->ai_addr)->sin_port = port; + memmove((char *)ai->ai_addr + addroff, abuf, addrsize); + if (ai->ai_family == AF_INET6) { + SIN6(ai->ai_addr)->sin6_scope_id = scopeid; + } + if ((flags & AI_CANONNAME) != 0) { + if (getnameinfo(ai->ai_addr, + (socklen_t)ai->ai_addrlen, nbuf, + sizeof(nbuf), NULL, 0, + NI_NUMERICHOST) == 0) + { + ai->ai_canonname = strdup(nbuf); + if (ai->ai_canonname == NULL) { + _freeaddrinfo(ai); + return (EAI_MEMORY); + } + } else { + /* XXX raise error? */ + ai->ai_canonname = NULL; + } + } + goto done; + } else if ((flags & AI_NUMERICHOST) != 0) { + return (EAI_NONAME); + } + } + + if (hostname == NULL && (flags & AI_PASSIVE) == 0) { + set_order(family, net_order); + for (i = 0; i < FOUND_MAX; i++) { + if (net_order[i] == NULL) { + break; + } + err = (net_order[i])(hostname, flags, &ai_list, + socktype, port); + if (err != 0) { + if (ai_list != NULL) { + _freeaddrinfo(ai_list); + ai_list = NULL; + } + break; + } + } + } else { + err = resolve_name(family, hostname, flags, &ai_list, socktype, + port); + } + + if (ai_list == NULL) { + if (err == 0) { + err = EAI_NONAME; + } + return (err); + } + +done: + ai_list = ai_reverse(ai_list); + + *res = ai_list; + return (0); +} + +typedef struct gai_restrans { + dns_clientrestrans_t *xid; + bool is_inprogress; + int error; + struct addrinfo ai_sentinel; + struct gai_resstate *resstate; +} gai_restrans_t; + +typedef struct gai_resstate { + isc_mem_t *mctx; + struct gai_statehead *head; + dns_fixedname_t fixedname; + dns_name_t *qname; + gai_restrans_t *trans4; + gai_restrans_t *trans6; + ISC_LINK(struct gai_resstate) link; +} gai_resstate_t; + +typedef struct gai_statehead { + int ai_family; + int ai_flags; + int ai_socktype; + int ai_port; + isc_appctx_t *actx; + dns_client_t *dnsclient; + isc_mutex_t list_lock; + ISC_LIST(struct gai_resstate) resstates; + unsigned int activestates; +} gai_statehead_t; + +static isc_result_t +make_resstate(isc_mem_t *mctx, gai_statehead_t *head, const char *hostname, + const char *domain, gai_resstate_t **statep) { + isc_result_t result; + gai_resstate_t *state; + dns_fixedname_t fixeddomain; + dns_name_t *qdomain; + unsigned int namelen; + isc_buffer_t b; + bool need_v4 = false; + bool need_v6 = false; + + state = isc_mem_get(mctx, sizeof(*state)); + + /* Construct base domain name */ + namelen = strlen(domain); + isc_buffer_constinit(&b, domain, namelen); + isc_buffer_add(&b, namelen); + qdomain = dns_fixedname_initname(&fixeddomain); + result = dns_name_fromtext(qdomain, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, state, sizeof(*state)); + return (result); + } + + /* Construct query name */ + namelen = strlen(hostname); + isc_buffer_constinit(&b, hostname, namelen); + isc_buffer_add(&b, namelen); + state->qname = dns_fixedname_initname(&state->fixedname); + result = dns_name_fromtext(state->qname, &b, qdomain, 0, NULL); + if (result != ISC_R_SUCCESS) { + isc_mem_put(mctx, state, sizeof(*state)); + return (result); + } + + if (head->ai_family == AF_UNSPEC || head->ai_family == AF_INET) { + need_v4 = true; + } + if (head->ai_family == AF_UNSPEC || head->ai_family == AF_INET6) { + need_v6 = true; + } + + state->trans6 = NULL; + state->trans4 = NULL; + if (need_v4) { + state->trans4 = isc_mem_get(mctx, sizeof(gai_restrans_t)); + state->trans4->error = 0; + state->trans4->xid = NULL; + state->trans4->resstate = state; + state->trans4->is_inprogress = true; + state->trans4->ai_sentinel.ai_next = NULL; + } + if (need_v6) { + state->trans6 = isc_mem_get(mctx, sizeof(gai_restrans_t)); + state->trans6->error = 0; + state->trans6->xid = NULL; + state->trans6->resstate = state; + state->trans6->is_inprogress = true; + state->trans6->ai_sentinel.ai_next = NULL; + } + + state->mctx = mctx; + state->head = head; + ISC_LINK_INIT(state, link); + + *statep = state; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +make_resstates(isc_mem_t *mctx, const char *hostname, gai_statehead_t *head, + irs_resconf_t *resconf) { + isc_result_t result; + irs_resconf_searchlist_t *searchlist; + irs_resconf_search_t *searchent; + gai_resstate_t *resstate, *resstate0; + + resstate0 = NULL; + result = make_resstate(mctx, head, hostname, ".", &resstate0); + if (result != ISC_R_SUCCESS) { + return (result); + } + + searchlist = irs_resconf_getsearchlist(resconf); + for (searchent = ISC_LIST_HEAD(*searchlist); searchent != NULL; + searchent = ISC_LIST_NEXT(searchent, link)) + { + resstate = NULL; + result = make_resstate(mctx, head, hostname, + (const char *)searchent->domain, + &resstate); + if (result != ISC_R_SUCCESS) { + break; + } + + ISC_LIST_APPEND(head->resstates, resstate, link); + head->activestates++; + } + + /* + * Insert the original hostname either at the head or the tail of the + * state list, depending on the number of labels contained in the + * original name and the 'ndots' configuration parameter. + */ + if (dns_name_countlabels(resstate0->qname) > + irs_resconf_getndots(resconf) + 1) + { + ISC_LIST_PREPEND(head->resstates, resstate0, link); + } else { + ISC_LIST_APPEND(head->resstates, resstate0, link); + } + head->activestates++; + + if (result != ISC_R_SUCCESS) { + while ((resstate = ISC_LIST_HEAD(head->resstates)) != NULL) { + ISC_LIST_UNLINK(head->resstates, resstate, link); + if (resstate->trans4 != NULL) { + isc_mem_put(mctx, resstate->trans4, + sizeof(*resstate->trans4)); + } + if (resstate->trans6 != NULL) { + isc_mem_put(mctx, resstate->trans6, + sizeof(*resstate->trans6)); + } + + isc_mem_put(mctx, resstate, sizeof(*resstate)); + } + } + + return (result); +} + +static void +process_answer(isc_task_t *task, isc_event_t *event) { + int error = 0, family; + gai_restrans_t *trans = event->ev_arg; + gai_resstate_t *resstate; + dns_clientresevent_t *rev = (dns_clientresevent_t *)event; + dns_rdatatype_t qtype; + dns_name_t *name; + bool wantcname; + + REQUIRE(trans != NULL); + resstate = trans->resstate; + REQUIRE(resstate != NULL); + REQUIRE(task != NULL); + + if (trans == resstate->trans4) { + family = AF_INET; + qtype = dns_rdatatype_a; + } else { + INSIST(trans == resstate->trans6); + family = AF_INET6; + qtype = dns_rdatatype_aaaa; + } + + INSIST(trans->is_inprogress); + trans->is_inprogress = false; + + switch (rev->result) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXDOMAIN: /* treat this as a fatal error? */ + case DNS_R_NCACHENXRRSET: + break; + default: + switch (rev->vresult) { + case DNS_R_SIGINVALID: + case DNS_R_SIGEXPIRED: + case DNS_R_SIGFUTURE: + case DNS_R_KEYUNAUTHORIZED: + case DNS_R_MUSTBESECURE: + case DNS_R_COVERINGNSEC: + case DNS_R_NOTAUTHORITATIVE: + case DNS_R_NOVALIDKEY: + case DNS_R_NOVALIDDS: + case DNS_R_NOVALIDSIG: + error = EAI_INSECUREDATA; + break; + default: + error = EAI_FAIL; + } + goto done; + } + + wantcname = ((resstate->head->ai_flags & AI_CANONNAME) != 0); + + /* Parse the response and construct the addrinfo chain */ + for (name = ISC_LIST_HEAD(rev->answerlist); name != NULL; + name = ISC_LIST_NEXT(name, link)) + { + isc_result_t result; + dns_rdataset_t *rdataset; + char cname[1024]; + + if (wantcname) { + isc_buffer_t b; + + isc_buffer_init(&b, cname, sizeof(cname)); + result = dns_name_totext(name, true, &b); + if (result != ISC_R_SUCCESS) { + error = EAI_FAIL; + goto done; + } + isc_buffer_putuint8(&b, '\0'); + } + + for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL; + rdataset = ISC_LIST_NEXT(rdataset, link)) + { + if (!dns_rdataset_isassociated(rdataset)) { + continue; + } + if (rdataset->type != qtype) { + continue; + } + + for (result = dns_rdataset_first(rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + struct addrinfo *ai; + dns_rdata_t rdata; + dns_rdata_in_a_t rdata_a; + dns_rdata_in_aaaa_t rdata_aaaa; + + ai = ai_alloc( + family, + ((family == AF_INET6) + ? sizeof(struct sockaddr_in6) + : sizeof(struct sockaddr_in))); + if (ai == NULL) { + error = EAI_MEMORY; + goto done; + } + ai->ai_socktype = resstate->head->ai_socktype; + ai->ai_next = trans->ai_sentinel.ai_next; + trans->ai_sentinel.ai_next = ai; + + /* + * Set AF-specific parameters + * (IPv4/v6 address/port) + */ + dns_rdata_init(&rdata); + switch (family) { + case AF_INET: + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct( + &rdata, &rdata_a, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + SIN(ai->ai_addr)->sin_port = + resstate->head->ai_port; + memmove(&SIN(ai->ai_addr)->sin_addr, + &rdata_a.in_addr, 4); + dns_rdata_freestruct(&rdata_a); + break; + case AF_INET6: + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct( + &rdata, &rdata_aaaa, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + SIN6(ai->ai_addr)->sin6_port = + resstate->head->ai_port; + memmove(&SIN6(ai->ai_addr)->sin6_addr, + &rdata_aaaa.in6_addr, 16); + dns_rdata_freestruct(&rdata_aaaa); + break; + } + + if (wantcname) { + ai->ai_canonname = strdup(cname); + if (ai->ai_canonname == NULL) { + error = EAI_MEMORY; + goto done; + } + } + } + } + } + +done: + dns_client_freeresanswer(resstate->head->dnsclient, &rev->answerlist); + dns_client_destroyrestrans(&trans->xid); + + isc_event_free(&event); + + /* Make sure that error == 0 iff we have a non-empty list */ + if (error == 0) { + if (trans->ai_sentinel.ai_next == NULL) { + error = EAI_NONAME; + } + } else { + if (trans->ai_sentinel.ai_next != NULL) { + _freeaddrinfo(trans->ai_sentinel.ai_next); + trans->ai_sentinel.ai_next = NULL; + } + } + trans->error = error; + + /* Check whether we are done */ + if ((resstate->trans4 == NULL || !resstate->trans4->is_inprogress) && + (resstate->trans6 == NULL || !resstate->trans6->is_inprogress)) + { + /* + * We're done for this state. If there is no other outstanding + * state, we can exit. + */ + resstate->head->activestates--; + if (resstate->head->activestates == 0) { + isc_app_ctxsuspend(resstate->head->actx); + return; + } + + /* + * There are outstanding states, but if we are at the head + * of the state list (i.e., at the highest search priority) + * and have any answer, we can stop now by canceling the + * others. + */ + LOCK(&resstate->head->list_lock); + if (resstate == ISC_LIST_HEAD(resstate->head->resstates)) { + if ((resstate->trans4 != NULL && + resstate->trans4->ai_sentinel.ai_next != NULL) || + (resstate->trans6 != NULL && + resstate->trans6->ai_sentinel.ai_next != NULL)) + { + gai_resstate_t *rest; + + for (rest = ISC_LIST_NEXT(resstate, link); + rest != NULL; + rest = ISC_LIST_NEXT(rest, link)) + { + if (rest->trans4 != NULL && + rest->trans4->xid != NULL) + { + dns_client_cancelresolve( + rest->trans4->xid); + } + if (rest->trans6 != NULL && + rest->trans6->xid != NULL) + { + dns_client_cancelresolve( + rest->trans6->xid); + } + } + } else { + /* + * This search fails, so we move to the tail + * of the list so that the next entry will + * have the highest priority. + */ + ISC_LIST_UNLINK(resstate->head->resstates, + resstate, link); + ISC_LIST_APPEND(resstate->head->resstates, + resstate, link); + } + } + UNLOCK(&resstate->head->list_lock); + } +} + +static int +resolve_name(int family, const char *hostname, int flags, struct addrinfo **aip, + int socktype, int port) { + isc_result_t result; + irs_context_t *irsctx; + irs_resconf_t *conf; + isc_mem_t *mctx; + isc_appctx_t *actx; + isc_task_t *task; + int terror = 0; + int error = 0; + dns_client_t *client; + gai_resstate_t *resstate; + gai_statehead_t head; + bool all_fail = true; + + /* get IRS context and the associated parameters */ + irsctx = NULL; + result = irs_context_get(&irsctx); + if (result != ISC_R_SUCCESS) { + return (EAI_FAIL); + } + actx = irs_context_getappctx(irsctx); + + mctx = irs_context_getmctx(irsctx); + task = irs_context_gettask(irsctx); + conf = irs_context_getresconf(irsctx); + client = irs_context_getdnsclient(irsctx); + + /* construct resolution states */ + head.activestates = 0; + head.ai_family = family; + head.ai_socktype = socktype; + head.ai_flags = flags; + head.ai_port = port; + head.actx = actx; + head.dnsclient = client; + isc_mutex_init(&head.list_lock); + + ISC_LIST_INIT(head.resstates); + result = make_resstates(mctx, hostname, &head, conf); + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&head.list_lock); + return (EAI_FAIL); + } + + LOCK(&head.list_lock); + for (resstate = ISC_LIST_HEAD(head.resstates); resstate != NULL; + resstate = ISC_LIST_NEXT(resstate, link)) + { + if (resstate->trans4 != NULL) { + result = dns_client_startresolve( + client, resstate->qname, dns_rdataclass_in, + dns_rdatatype_a, 0, task, process_answer, + resstate->trans4, &resstate->trans4->xid); + if (result == ISC_R_SUCCESS) { + resstate->trans4->is_inprogress = true; + all_fail = false; + } else { + resstate->trans4->is_inprogress = false; + } + } + if (resstate->trans6 != NULL) { + result = dns_client_startresolve( + client, resstate->qname, dns_rdataclass_in, + dns_rdatatype_aaaa, 0, task, process_answer, + resstate->trans6, &resstate->trans6->xid); + if (result == ISC_R_SUCCESS) { + resstate->trans6->is_inprogress = true; + all_fail = false; + } else { + resstate->trans6->is_inprogress = false; + } + } + } + UNLOCK(&head.list_lock); + + if (!all_fail) { + /* Start all the events */ + isc_app_ctxrun(actx); + } else { + error = EAI_FAIL; + } + + /* Cleanup */ + while ((resstate = ISC_LIST_HEAD(head.resstates)) != NULL) { + int terror4 = 0, terror6 = 0; + + ISC_LIST_UNLINK(head.resstates, resstate, link); + + if (*aip == NULL) { + struct addrinfo *sentinel4 = NULL; + struct addrinfo *sentinel6 = NULL; + + if (resstate->trans4 != NULL) { + sentinel4 = + resstate->trans4->ai_sentinel.ai_next; + resstate->trans4->ai_sentinel.ai_next = NULL; + } + if (resstate->trans6 != NULL) { + sentinel6 = + resstate->trans6->ai_sentinel.ai_next; + resstate->trans6->ai_sentinel.ai_next = NULL; + } + *aip = ai_concat(sentinel4, sentinel6); + } + + if (resstate->trans4 != NULL) { + INSIST(resstate->trans4->xid == NULL); + terror4 = resstate->trans4->error; + isc_mem_put(mctx, resstate->trans4, + sizeof(*resstate->trans4)); + } + if (resstate->trans6 != NULL) { + INSIST(resstate->trans6->xid == NULL); + terror6 = resstate->trans6->error; + isc_mem_put(mctx, resstate->trans6, + sizeof(*resstate->trans6)); + } + + /* + * If the entire lookup fails, we need to choose an appropriate + * error code from individual codes. We'll try to provide as + * specific a code as possible. In general, we are going to + * find an error code other than EAI_NONAME (which is too + * generic and may actually not be problematic in some cases). + * EAI_NONAME will be set below if no better code is found. + */ + if (terror == 0 || terror == EAI_NONAME) { + if (terror4 != 0 && terror4 != EAI_NONAME) { + terror = terror4; + } else if (terror6 != 0 && terror6 != EAI_NONAME) { + terror = terror6; + } + } + + isc_mem_put(mctx, resstate, sizeof(*resstate)); + } + + if (*aip == NULL) { + error = terror; + if (error == 0) { + error = EAI_NONAME; + } + } + +#if 1 /* XXX: enabled for finding leaks. should be cleaned up later. */ + isc_app_ctxfinish(actx); + irs_context_destroy(&irsctx); +#endif /* if 1 */ + + isc_mutex_destroy(&head.list_lock); + return (error); +} + +static void +set_order(int family, + int (**net_order)(const char *, int, struct addrinfo **, int, int)) { + char *order, *tok, *last; + int found; + + if (family) { + switch (family) { + case AF_INET: + *net_order++ = add_ipv4; + break; + case AF_INET6: + *net_order++ = add_ipv6; + break; + } + } else { + order = getenv("NET_ORDER"); + found = 0; + if (order != NULL) { + last = NULL; + for (tok = strtok_r(order, ":", &last); tok; + tok = strtok_r(NULL, ":", &last)) + { + if (strcasecmp(tok, "inet6") == 0) { + if ((found & FOUND_IPV6) == 0) { + *net_order++ = add_ipv6; + } + found |= FOUND_IPV6; + } else if (strcasecmp(tok, "inet") == 0 || + strcasecmp(tok, "inet4") == 0) + { + if ((found & FOUND_IPV4) == 0) { + *net_order++ = add_ipv4; + } + found |= FOUND_IPV4; + } + } + } + + /* + * Add in anything that we didn't find. + */ + if ((found & FOUND_IPV4) == 0) { + *net_order++ = add_ipv4; + } + if ((found & FOUND_IPV6) == 0) { + *net_order++ = add_ipv6; + } + } + *net_order = NULL; + return; +} + +static char v4_loop[4] = { 127, 0, 0, 1 }; + +static int +add_ipv4(const char *hostname, int flags, struct addrinfo **aip, int socktype, + int port) { + struct addrinfo *ai; + + UNUSED(hostname); + UNUSED(flags); + + ai = ai_clone(*aip, AF_INET); /* don't use ai_clone() */ + if (ai == NULL) { + return (EAI_MEMORY); + } + + *aip = ai; + ai->ai_socktype = socktype; + SIN(ai->ai_addr)->sin_port = port; + memmove(&SIN(ai->ai_addr)->sin_addr, v4_loop, 4); + + return (0); +} + +static char v6_loop[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + +static int +add_ipv6(const char *hostname, int flags, struct addrinfo **aip, int socktype, + int port) { + struct addrinfo *ai; + + UNUSED(hostname); + UNUSED(flags); + + ai = ai_clone(*aip, AF_INET6); /* don't use ai_clone() */ + if (ai == NULL) { + return (EAI_MEMORY); + } + + *aip = ai; + ai->ai_socktype = socktype; + SIN6(ai->ai_addr)->sin6_port = port; + memmove(&SIN6(ai->ai_addr)->sin6_addr, v6_loop, 16); + + return (0); +} + +/*% Free address info. */ +void +freeaddrinfo(struct addrinfo *ai) { + _freeaddrinfo(ai); +} + +static void +_freeaddrinfo(struct addrinfo *ai) { + struct addrinfo *ai_next; + + while (ai != NULL) { + ai_next = ai->ai_next; + if (ai->ai_addr != NULL) { + free(ai->ai_addr); + } + if (ai->ai_canonname) { + free(ai->ai_canonname); + } + free(ai); + ai = ai_next; + } +} + +#ifdef AF_LOCAL +static int +get_local(const char *name, int socktype, struct addrinfo **res) { + struct addrinfo *ai; + struct sockaddr_un *slocal; + + if (socktype == 0) { + return (EAI_SOCKTYPE); + } + + ai = ai_alloc(AF_LOCAL, sizeof(*slocal)); + if (ai == NULL) { + return (EAI_MEMORY); + } + + slocal = SLOCAL(ai->ai_addr); + strlcpy(slocal->sun_path, name, sizeof(slocal->sun_path)); + + ai->ai_socktype = socktype; + /* + * ai->ai_flags, ai->ai_protocol, ai->ai_canonname, + * and ai->ai_next were initialized to zero. + */ + + *res = ai; + return (0); +} +#endif /* ifdef AF_LOCAL */ + +/*! + * Allocate an addrinfo structure, and a sockaddr structure + * of the specified length. We initialize: + * ai_addrlen + * ai_family + * ai_addr + * ai_addr->sa_family + * ai_addr->sa_len (IRS_PLATFORM_HAVESALEN) + * and everything else is initialized to zero. + */ +static struct addrinfo * +ai_alloc(int family, int addrlen) { + struct addrinfo *ai; + + ai = (struct addrinfo *)calloc(1, sizeof(*ai)); + if (ai == NULL) { + return (NULL); + } + + ai->ai_addr = SA(calloc(1, addrlen)); + if (ai->ai_addr == NULL) { + free(ai); + return (NULL); + } + ai->ai_addrlen = addrlen; + ai->ai_family = family; + ai->ai_addr->sa_family = family; +#ifdef IRS_PLATFORM_HAVESALEN + ai->ai_addr->sa_len = addrlen; +#endif /* ifdef IRS_PLATFORM_HAVESALEN */ + return (ai); +} + +static struct addrinfo * +ai_clone(struct addrinfo *oai, int family) { + struct addrinfo *ai; + + ai = ai_alloc(family, + ((family == AF_INET6) ? sizeof(struct sockaddr_in6) + : sizeof(struct sockaddr_in))); + + if (ai == NULL) { + return (NULL); + } + if (oai == NULL) { + return (ai); + } + + ai->ai_flags = oai->ai_flags; + ai->ai_socktype = oai->ai_socktype; + ai->ai_protocol = oai->ai_protocol; + ai->ai_canonname = NULL; + ai->ai_next = oai; + return (ai); +} + +static struct addrinfo * +ai_reverse(struct addrinfo *oai) { + struct addrinfo *nai, *tai; + + nai = NULL; + + while (oai != NULL) { + /* + * Grab one off the old list. + */ + tai = oai; + oai = oai->ai_next; + /* + * Put it on the front of the new list. + */ + tai->ai_next = nai; + nai = tai; + } + return (nai); +} + +static struct addrinfo * +ai_concat(struct addrinfo *ai1, struct addrinfo *ai2) { + struct addrinfo *ai_tmp; + + if (ai1 == NULL) { + return (ai2); + } else if (ai2 == NULL) { + return (ai1); + } + + for (ai_tmp = ai1; ai_tmp != NULL && ai_tmp->ai_next != NULL; + ai_tmp = ai_tmp->ai_next) + { + } + + ai_tmp->ai_next = ai2; + + return (ai1); +} |