%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/deps/cares/src/lib/ares_getaddrinfo.c |
/* MIT License * * Copyright (c) 1998, 2011, 2013 Massachusetts Institute of Technology * Copyright (c) 2017 Christian Ammer * Copyright (c) 2019 Andrew Selivanov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MIT */ #include "ares_setup.h" #ifdef HAVE_GETSERVBYNAME_R # if !defined(GETSERVBYNAME_R_ARGS) || (GETSERVBYNAME_R_ARGS < 4) || \ (GETSERVBYNAME_R_ARGS > 6) # error "you MUST specify a valid number of arguments for getservbyname_r" # endif #endif #ifdef HAVE_NETINET_IN_H # include <netinet/in.h> #endif #ifdef HAVE_NETDB_H # include <netdb.h> #endif #ifdef HAVE_ARPA_INET_H # include <arpa/inet.h> #endif #include "ares_nameser.h" #ifdef HAVE_STRINGS_H # include <strings.h> #endif #include <assert.h> #ifdef HAVE_LIMITS_H # include <limits.h> #endif #include "ares.h" #include "ares_private.h" #include "ares_dns.h" #ifdef WATT32 # undef WIN32 #endif #ifdef WIN32 # include "ares_platform.h" #endif struct host_query { ares_channel_t *channel; char *name; unsigned short port; /* in host order */ ares_addrinfo_callback callback; void *arg; struct ares_addrinfo_hints hints; int sent_family; /* this family is what was is being used */ size_t timeouts; /* number of timeouts we saw for this request */ char *lookups; /* Duplicate memory from channel because of ares_reinit() */ const char *remaining_lookups; /* types of lookup we need to perform ("fb" by default, file and dns respectively) */ char **domains; /* duplicate from channel for ares_reinit() safety */ size_t ndomains; struct ares_addrinfo *ai; /* store results between lookups */ unsigned short qid_a; /* qid for A request */ unsigned short qid_aaaa; /* qid for AAAA request */ size_t remaining; /* number of DNS answers waiting for */ ares_ssize_t next_domain; /* next search domain to try */ size_t nodata_cnt; /* Track nodata responses to possibly override final result */ }; static const struct ares_addrinfo_hints default_hints = { 0, /* ai_flags */ AF_UNSPEC, /* ai_family */ 0, /* ai_socktype */ 0, /* ai_protocol */ }; /* forward declarations */ static void host_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen); static ares_bool_t as_is_first(const struct host_query *hquery); static ares_bool_t as_is_only(const struct host_query *hquery); static ares_bool_t next_dns_lookup(struct host_query *hquery); struct ares_addrinfo_cname * ares__append_addrinfo_cname(struct ares_addrinfo_cname **head) { struct ares_addrinfo_cname *tail = ares_malloc_zero(sizeof(*tail)); struct ares_addrinfo_cname *last = *head; if (tail == NULL) { return NULL; } if (!last) { *head = tail; return tail; } while (last->next) { last = last->next; } last->next = tail; return tail; } void ares__addrinfo_cat_cnames(struct ares_addrinfo_cname **head, struct ares_addrinfo_cname *tail) { struct ares_addrinfo_cname *last = *head; if (!last) { *head = tail; return; } while (last->next) { last = last->next; } last->next = tail; } /* Allocate new addrinfo and append to the tail. */ struct ares_addrinfo_node * ares__append_addrinfo_node(struct ares_addrinfo_node **head) { struct ares_addrinfo_node *tail = ares_malloc_zero(sizeof(*tail)); struct ares_addrinfo_node *last = *head; if (tail == NULL) { return NULL; } if (!last) { *head = tail; return tail; } while (last->ai_next) { last = last->ai_next; } last->ai_next = tail; return tail; } void ares__addrinfo_cat_nodes(struct ares_addrinfo_node **head, struct ares_addrinfo_node *tail) { struct ares_addrinfo_node *last = *head; if (!last) { *head = tail; return; } while (last->ai_next) { last = last->ai_next; } last->ai_next = tail; } /* Resolve service name into port number given in host byte order. * If not resolved, return 0. */ static unsigned short lookup_service(const char *service, int flags) { const char *proto; struct servent *sep; #ifdef HAVE_GETSERVBYNAME_R struct servent se; char tmpbuf[4096]; #endif if (service) { if (flags & ARES_NI_UDP) { proto = "udp"; } else if (flags & ARES_NI_SCTP) { proto = "sctp"; } else if (flags & ARES_NI_DCCP) { proto = "dccp"; } else { proto = "tcp"; } #ifdef HAVE_GETSERVBYNAME_R memset(&se, 0, sizeof(se)); sep = &se; memset(tmpbuf, 0, sizeof(tmpbuf)); # if GETSERVBYNAME_R_ARGS == 6 if (getservbyname_r(service, proto, &se, (void *)tmpbuf, sizeof(tmpbuf), &sep) != 0) { sep = NULL; /* LCOV_EXCL_LINE: buffer large so this never fails */ } # elif GETSERVBYNAME_R_ARGS == 5 sep = getservbyname_r(service, proto, &se, (void *)tmpbuf, sizeof(tmpbuf)); # elif GETSERVBYNAME_R_ARGS == 4 if (getservbyname_r(service, proto, &se, (void *)tmpbuf) != 0) { sep = NULL; } # else /* Lets just hope the OS uses TLS! */ sep = getservbyname(service, proto); # endif #else /* Lets just hope the OS uses TLS! */ # if (defined(NETWARE) && !defined(__NOVELL_LIBC__)) sep = getservbyname(service, (char *)proto); # else sep = getservbyname(service, proto); # endif #endif return (sep ? ntohs((unsigned short)sep->s_port) : 0); } return 0; } /* If the name looks like an IP address or an error occurred, * fake up a host entry, end the query immediately, and return true. * Otherwise return false. */ static ares_bool_t fake_addrinfo(const char *name, unsigned short port, const struct ares_addrinfo_hints *hints, struct ares_addrinfo *ai, ares_addrinfo_callback callback, void *arg) { struct ares_addrinfo_cname *cname; ares_status_t status = ARES_SUCCESS; ares_bool_t result = ARES_FALSE; int family = hints->ai_family; if (family == AF_INET || family == AF_INET6 || family == AF_UNSPEC) { /* It only looks like an IP address if it's all numbers and dots. */ size_t numdots = 0; ares_bool_t valid = ARES_TRUE; const char *p; for (p = name; *p; p++) { if (!ISDIGIT(*p) && *p != '.') { valid = ARES_FALSE; break; } else if (*p == '.') { numdots++; } } /* if we don't have 3 dots, it is illegal * (although inet_pton doesn't think so). */ if (numdots != 3 || !valid) { result = ARES_FALSE; } else { struct in_addr addr4; result = ares_inet_pton(AF_INET, name, &addr4) < 1 ? ARES_FALSE : ARES_TRUE; if (result) { status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes); if (status != ARES_SUCCESS) { callback(arg, (int)status, 0, NULL); return ARES_TRUE; } } } } if (!result && (family == AF_INET6 || family == AF_UNSPEC)) { struct ares_in6_addr addr6; result = ares_inet_pton(AF_INET6, name, &addr6) < 1 ? ARES_FALSE : ARES_TRUE; if (result) { status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes); if (status != ARES_SUCCESS) { callback(arg, (int)status, 0, NULL); return ARES_TRUE; } } } if (!result) { return ARES_FALSE; } if (hints->ai_flags & ARES_AI_CANONNAME) { cname = ares__append_addrinfo_cname(&ai->cnames); if (!cname) { ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return ARES_TRUE; } /* Duplicate the name, to avoid a constness violation. */ cname->name = ares_strdup(name); if (!cname->name) { ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return ARES_TRUE; } } ai->nodes->ai_socktype = hints->ai_socktype; ai->nodes->ai_protocol = hints->ai_protocol; callback(arg, ARES_SUCCESS, 0, ai); return ARES_TRUE; } static void end_hquery(struct host_query *hquery, ares_status_t status) { struct ares_addrinfo_node sentinel; struct ares_addrinfo_node *next; if (status == ARES_SUCCESS) { if (!(hquery->hints.ai_flags & ARES_AI_NOSORT) && hquery->ai->nodes) { sentinel.ai_next = hquery->ai->nodes; ares__sortaddrinfo(hquery->channel, &sentinel); hquery->ai->nodes = sentinel.ai_next; } next = hquery->ai->nodes; while (next) { next->ai_socktype = hquery->hints.ai_socktype; next->ai_protocol = hquery->hints.ai_protocol; next = next->ai_next; } } else { /* Clean up what we have collected by so far. */ ares_freeaddrinfo(hquery->ai); hquery->ai = NULL; } hquery->callback(hquery->arg, (int)status, (int)hquery->timeouts, hquery->ai); ares__strsplit_free(hquery->domains, hquery->ndomains); ares_free(hquery->lookups); ares_free(hquery->name); ares_free(hquery); } ares_bool_t ares__is_localhost(const char *name) { /* RFC6761 6.3 says : The domain "localhost." and any names falling within * ".localhost." */ size_t len; if (name == NULL) { return ARES_FALSE; } if (strcmp(name, "localhost") == 0) { return ARES_TRUE; } len = ares_strlen(name); if (len < 10 /* strlen(".localhost") */) { return ARES_FALSE; } if (strcmp(name + (len - 10 /* strlen(".localhost") */), ".localhost") == 0) { return ARES_TRUE; } return ARES_FALSE; } static ares_status_t file_lookup(struct host_query *hquery) { const ares_hosts_entry_t *entry; ares_status_t status; /* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */ if (ares__is_onion_domain(hquery->name)) { return ARES_ENOTFOUND; } status = ares__hosts_search_host( hquery->channel, (hquery->hints.ai_flags & ARES_AI_ENVHOSTS) ? ARES_TRUE : ARES_FALSE, hquery->name, &entry); if (status != ARES_SUCCESS) { goto done; } status = ares__hosts_entry_to_addrinfo( entry, hquery->name, hquery->hints.ai_family, hquery->port, (hquery->hints.ai_flags & ARES_AI_CANONNAME) ? ARES_TRUE : ARES_FALSE, hquery->ai); if (status != ARES_SUCCESS) { goto done; } done: /* RFC6761 section 6.3 #3 states that "Name resolution APIs and libraries * SHOULD recognize localhost names as special and SHOULD always return the * IP loopback address for address queries". * We will also ignore ALL errors when trying to resolve localhost, such * as permissions errors reading /etc/hosts or a malformed /etc/hosts */ if (status != ARES_SUCCESS && status != ARES_ENOMEM && ares__is_localhost(hquery->name)) { return ares__addrinfo_localhost(hquery->name, hquery->port, &hquery->hints, hquery->ai); } return status; } static void next_lookup(struct host_query *hquery, ares_status_t status) { switch (*hquery->remaining_lookups) { case 'b': /* RFC6761 section 6.3 #3 says "Name resolution APIs SHOULD NOT send * queries for localhost names to their configured caching DNS * server(s)." * Otherwise, DNS lookup. */ if (!ares__is_localhost(hquery->name) && next_dns_lookup(hquery)) { break; } hquery->remaining_lookups++; next_lookup(hquery, status); break; case 'f': /* Host file lookup */ if (file_lookup(hquery) == ARES_SUCCESS) { end_hquery(hquery, ARES_SUCCESS); break; } hquery->remaining_lookups++; next_lookup(hquery, status); break; default: /* No lookup left */ end_hquery(hquery, status); break; } } static void terminate_retries(const struct host_query *hquery, unsigned short qid) { unsigned short term_qid = (qid == hquery->qid_a) ? hquery->qid_aaaa : hquery->qid_a; const ares_channel_t *channel = hquery->channel; struct query *query = NULL; /* No other outstanding queries, nothing to do */ if (!hquery->remaining) { return; } query = ares__htable_szvp_get_direct(channel->queries_by_qid, term_qid); if (query == NULL) { return; } query->no_retries = ARES_TRUE; } static void host_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) { struct host_query *hquery = (struct host_query *)arg; ares_status_t addinfostatus = ARES_SUCCESS; unsigned short qid = 0; hquery->timeouts += (size_t)timeouts; hquery->remaining--; if (status == ARES_SUCCESS) { if (alen < 0) { addinfostatus = ARES_EBADRESP; } else { addinfostatus = ares__parse_into_addrinfo(abuf, (size_t)alen, ARES_TRUE, hquery->port, hquery->ai); } if (addinfostatus == ARES_SUCCESS && alen >= HFIXEDSZ) { qid = DNS_HEADER_QID(abuf); /* Converts to host byte order */ terminate_retries(hquery, qid); } } if (!hquery->remaining) { if (status == ARES_EDESTRUCTION || status == ARES_ECANCELLED) { /* must make sure we don't do next_lookup() on destroy or cancel, * and return the appropriate status. We won't return a partial * result in this case. */ end_hquery(hquery, (ares_status_t)status); } else if (addinfostatus != ARES_SUCCESS && addinfostatus != ARES_ENODATA) { /* error in parsing result e.g. no memory */ if (addinfostatus == ARES_EBADRESP && hquery->ai->nodes) { /* We got a bad response from server, but at least one query * ended with ARES_SUCCESS */ end_hquery(hquery, ARES_SUCCESS); } else { end_hquery(hquery, addinfostatus); } } else if (hquery->ai->nodes) { /* at least one query ended with ARES_SUCCESS */ end_hquery(hquery, ARES_SUCCESS); } else if (status == ARES_ENOTFOUND || status == ARES_ENODATA || addinfostatus == ARES_ENODATA) { if (status == ARES_ENODATA || addinfostatus == ARES_ENODATA) { hquery->nodata_cnt++; } next_lookup(hquery, hquery->nodata_cnt ? ARES_ENODATA : (ares_status_t)status); } else { end_hquery(hquery, (ares_status_t)status); } } /* at this point we keep on waiting for the next query to finish */ } static void ares_getaddrinfo_int(ares_channel_t *channel, const char *name, const char *service, const struct ares_addrinfo_hints *hints, ares_addrinfo_callback callback, void *arg) { struct host_query *hquery; unsigned short port = 0; int family; struct ares_addrinfo *ai; char *alias_name = NULL; ares_status_t status; if (!hints) { hints = &default_hints; } family = hints->ai_family; /* Right now we only know how to look up Internet addresses and unspec means try both basically. */ if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) { callback(arg, ARES_ENOTIMP, 0, NULL); return; } if (ares__is_onion_domain(name)) { callback(arg, ARES_ENOTFOUND, 0, NULL); return; } /* perform HOSTALIAS resolution (technically this function does some other * things we are going to ignore) */ status = ares__single_domain(channel, name, &alias_name); if (status != ARES_SUCCESS) { callback(arg, (int)status, 0, NULL); return; } if (alias_name) { name = alias_name; } if (service) { if (hints->ai_flags & ARES_AI_NUMERICSERV) { unsigned long val; errno = 0; val = strtoul(service, NULL, 0); if ((val == 0 && errno != 0) || val > 65535) { ares_free(alias_name); callback(arg, ARES_ESERVICE, 0, NULL); return; } port = (unsigned short)val; } else { port = lookup_service(service, 0); if (!port) { unsigned long val; errno = 0; val = strtoul(service, NULL, 0); if ((val == 0 && errno != 0) || val > 65535) { ares_free(alias_name); callback(arg, ARES_ESERVICE, 0, NULL); return; } port = (unsigned short)val; } } } ai = ares_malloc_zero(sizeof(*ai)); if (!ai) { ares_free(alias_name); callback(arg, ARES_ENOMEM, 0, NULL); return; } if (fake_addrinfo(name, port, hints, ai, callback, arg)) { ares_free(alias_name); return; } /* Allocate and fill in the host query structure. */ hquery = ares_malloc_zero(sizeof(*hquery)); if (!hquery) { ares_free(alias_name); ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return; } memset(hquery, 0, sizeof(*hquery)); hquery->name = ares_strdup(name); ares_free(alias_name); if (!hquery->name) { ares_free(hquery); ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return; } hquery->lookups = ares_strdup(channel->lookups); if (!hquery->lookups) { ares_free(hquery->name); ares_free(hquery); ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return; } if (channel->ndomains) { /* Duplicate for ares_reinit() safety */ hquery->domains = ares__strsplit_duplicate(channel->domains, channel->ndomains); if (hquery->domains == NULL) { ares_free(hquery->lookups); ares_free(hquery->name); ares_free(hquery); ares_freeaddrinfo(ai); callback(arg, ARES_ENOMEM, 0, NULL); return; } hquery->ndomains = channel->ndomains; } hquery->port = port; hquery->channel = channel; hquery->hints = *hints; hquery->sent_family = -1; /* nothing is sent yet */ hquery->callback = callback; hquery->arg = arg; hquery->remaining_lookups = hquery->lookups; hquery->ai = ai; hquery->next_domain = -1; /* Start performing lookups according to channel->lookups. */ next_lookup(hquery, ARES_ECONNREFUSED /* initial error code */); } void ares_getaddrinfo(ares_channel_t *channel, const char *name, const char *service, const struct ares_addrinfo_hints *hints, ares_addrinfo_callback callback, void *arg) { if (channel == NULL) { return; } ares__channel_lock(channel); ares_getaddrinfo_int(channel, name, service, hints, callback, arg); ares__channel_unlock(channel); } static ares_bool_t next_dns_lookup(struct host_query *hquery) { char *s = NULL; ares_bool_t is_s_allocated = ARES_FALSE; ares_status_t status; /* if next_domain == -1 and as_is_first is true, try hquery->name */ if (hquery->next_domain == -1) { if (as_is_first(hquery)) { s = hquery->name; } hquery->next_domain = 0; } /* if as_is_first is false, try hquery->name at last */ if (!s && (size_t)hquery->next_domain == hquery->ndomains) { if (!as_is_first(hquery)) { s = hquery->name; } hquery->next_domain++; } if (!s && (size_t)hquery->next_domain < hquery->ndomains && !as_is_only(hquery)) { status = ares__cat_domain(hquery->name, hquery->domains[hquery->next_domain++], &s); if (status == ARES_SUCCESS) { is_s_allocated = ARES_TRUE; } } if (s) { /* NOTE: hquery may be invalidated during the call to ares_query_qid(), * so should not be referenced after this point */ switch (hquery->hints.ai_family) { case AF_INET: hquery->remaining += 1; ares_query_qid(hquery->channel, s, C_IN, T_A, host_callback, hquery, &hquery->qid_a); break; case AF_INET6: hquery->remaining += 1; ares_query_qid(hquery->channel, s, C_IN, T_AAAA, host_callback, hquery, &hquery->qid_aaaa); break; case AF_UNSPEC: hquery->remaining += 2; ares_query_qid(hquery->channel, s, C_IN, T_A, host_callback, hquery, &hquery->qid_a); ares_query_qid(hquery->channel, s, C_IN, T_AAAA, host_callback, hquery, &hquery->qid_aaaa); break; default: break; } if (is_s_allocated) { ares_free(s); } return ARES_TRUE; } else { assert(!hquery->ai->nodes); return ARES_FALSE; } } static ares_bool_t as_is_first(const struct host_query *hquery) { const char *p; size_t ndots = 0; for (p = hquery->name; p && *p; p++) { if (*p == '.') { ndots++; } } if (as_is_only(hquery)) { /* prevent ARES_EBADNAME for valid FQDN, where ndots < channel->ndots */ return ARES_TRUE; } return ndots >= hquery->channel->ndots ? ARES_TRUE : ARES_FALSE; } static ares_bool_t as_is_only(const struct host_query *hquery) { size_t nname = ares_strlen(hquery->name); if (hquery->channel->flags & ARES_FLAG_NOSEARCH) { return ARES_TRUE; } if (hquery->name != NULL && nname && hquery->name[nname - 1] == '.') { return ARES_TRUE; } return ARES_FALSE; }