diff options
-rw-r--r-- | sysdeps/posix/getaddrinfo.c | 563 |
1 files changed, 286 insertions, 277 deletions
diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c index 01be932b3f..f70ce2c76b 100644 --- a/sysdeps/posix/getaddrinfo.c +++ b/sysdeps/posix/getaddrinfo.c @@ -159,6 +159,14 @@ static const struct addrinfo default_hints = .ai_next = NULL }; +static void +gaih_result_reset (struct gaih_result *res) +{ + if (res->free_at) + free (res->at); + free (res->canon); + memset (res, 0, sizeof (*res)); +} static int gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp, @@ -197,13 +205,10 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp, /* Convert struct hostent to a list of struct gaih_addrtuple objects. h_name is not copied, and the struct hostent object must not be deallocated - prematurely. The new addresses are appended to the tuple array in - RESULT. */ + prematurely. The new addresses are appended to the tuple array in RES. */ static bool -convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, - int family, - struct hostent *h, - struct gaih_addrtuple **result) +convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, int family, + struct hostent *h, struct gaih_result *res) { /* Count the number of addresses in h->h_addr_list. */ size_t count = 0; @@ -215,7 +220,7 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, if (count == 0 || h->h_length > sizeof (((struct gaih_addrtuple) {}).addr)) return true; - struct gaih_addrtuple *array = *result; + struct gaih_addrtuple *array = res->at; size_t old = 0; while (array != NULL) @@ -224,12 +229,14 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, array = array->next; } - array = realloc (*result, (old + count) * sizeof (*array)); + array = realloc (res->at, (old + count) * sizeof (*array)); if (array == NULL) return false; - *result = array; + res->got_ipv6 = family == AF_INET6; + res->at = array; + res->free_at = true; /* Update the next pointers on reallocation. */ for (size_t i = 0; i < old; i++) @@ -278,7 +285,7 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, { \ __resolv_context_put (res_ctx); \ result = -EAI_MEMORY; \ - goto free_and_return; \ + goto out; \ } \ } \ if (status == NSS_STATUS_NOTFOUND \ @@ -288,7 +295,7 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, { \ __resolv_context_put (res_ctx); \ result = -EAI_SYSTEM; \ - goto free_and_return; \ + goto out; \ } \ if (h_errno == TRY_AGAIN) \ no_data = EAI_AGAIN; \ @@ -297,27 +304,24 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, } \ else if (status == NSS_STATUS_SUCCESS) \ { \ - if (!convert_hostent_to_gaih_addrtuple (req, _family, &th, &addrmem)) \ + if (!convert_hostent_to_gaih_addrtuple (req, _family, &th, res)) \ { \ __resolv_context_put (res_ctx); \ result = -EAI_SYSTEM; \ - goto free_and_return; \ + goto out; \ } \ - *pat = addrmem; \ \ - if (localcanon != NULL && res.canon == NULL) \ + if (localcanon != NULL && res->canon == NULL) \ { \ char *canonbuf = __strdup (localcanon); \ if (canonbuf == NULL) \ { \ __resolv_context_put (res_ctx); \ result = -EAI_SYSTEM; \ - goto free_and_return; \ + goto out; \ } \ - res.canon = canonbuf; \ + res->canon = canonbuf; \ } \ - if (_family == AF_INET6 && *pat != NULL) \ - res.got_ipv6 = true; \ } \ } @@ -590,6 +594,260 @@ out: } #endif +static int +get_nss_addresses (const char *name, const struct addrinfo *req, + struct scratch_buffer *tmpbuf, struct gaih_result *res) +{ + int no_data = 0; + int no_inet6_data = 0; + nss_action_list nip; + enum nss_status inet6_status = NSS_STATUS_UNAVAIL; + enum nss_status status = NSS_STATUS_UNAVAIL; + int no_more; + struct resolv_context *res_ctx = NULL; + bool do_merge = false; + int result = 0; + + no_more = !__nss_database_get (nss_database_hosts, &nip); + + /* If we are looking for both IPv4 and IPv6 address we don't + want the lookup functions to automatically promote IPv4 + addresses to IPv6 addresses, so we use the no_inet6 + function variant. */ + res_ctx = __resolv_context_get (); + if (res_ctx == NULL) + no_more = 1; + + while (!no_more) + { + /* Always start afresh; continue should discard previous results + and the hosts database does not support merge. */ + gaih_result_reset (res); + + if (do_merge) + { + __set_h_errno (NETDB_INTERNAL); + __set_errno (EBUSY); + break; + } + + no_data = 0; + nss_gethostbyname4_r *fct4 = NULL; + + /* gethostbyname4_r sends out parallel A and AAAA queries and + is thus only suitable for PF_UNSPEC. */ + if (req->ai_family == PF_UNSPEC) + fct4 = __nss_lookup_function (nip, "gethostbyname4_r"); + + if (fct4 != NULL) + { + while (1) + { + status = DL_CALL_FCT (fct4, (name, &res->at, + tmpbuf->data, tmpbuf->length, + &errno, &h_errno, + NULL)); + if (status == NSS_STATUS_SUCCESS) + break; + /* gethostbyname4_r may write into AT, so reset it. */ + res->at = NULL; + if (status != NSS_STATUS_TRYAGAIN + || errno != ERANGE || h_errno != NETDB_INTERNAL) + { + if (h_errno == TRY_AGAIN) + no_data = EAI_AGAIN; + else + no_data = h_errno == NO_DATA; + break; + } + + if (!scratch_buffer_grow (tmpbuf)) + { + __resolv_context_put (res_ctx); + result = -EAI_MEMORY; + goto out; + } + } + + if (status == NSS_STATUS_SUCCESS) + { + assert (!no_data); + no_data = 1; + + if ((req->ai_flags & AI_CANONNAME) != 0 && res->canon == NULL) + { + char *canonbuf = __strdup (res->at->name); + if (canonbuf == NULL) + { + __resolv_context_put (res_ctx); + result = -EAI_MEMORY; + goto out; + } + res->canon = canonbuf; + } + + struct gaih_addrtuple **pat = &res->at; + + while (*pat != NULL) + { + if ((*pat)->family == AF_INET + && req->ai_family == AF_INET6 + && (req->ai_flags & AI_V4MAPPED) != 0) + { + uint32_t *pataddr = (*pat)->addr; + (*pat)->family = AF_INET6; + pataddr[3] = pataddr[0]; + pataddr[2] = htonl (0xffff); + pataddr[1] = 0; + pataddr[0] = 0; + pat = &((*pat)->next); + no_data = 0; + } + else if (req->ai_family == AF_UNSPEC + || (*pat)->family == req->ai_family) + { + pat = &((*pat)->next); + + no_data = 0; + if (req->ai_family == AF_INET6) + res->got_ipv6 = true; + } + else + *pat = ((*pat)->next); + } + } + + no_inet6_data = no_data; + } + else + { + nss_gethostbyname3_r *fct = NULL; + if (req->ai_flags & AI_CANONNAME) + /* No need to use this function if we do not look for + the canonical name. The function does not exist in + all NSS modules and therefore the lookup would + often fail. */ + fct = __nss_lookup_function (nip, "gethostbyname3_r"); + if (fct == NULL) + /* We are cheating here. The gethostbyname2_r + function does not have the same interface as + gethostbyname3_r but the extra arguments the + latter takes are added at the end. So the + gethostbyname2_r code will just ignore them. */ + fct = __nss_lookup_function (nip, "gethostbyname2_r"); + + if (fct != NULL) + { + if (req->ai_family == AF_INET6 + || req->ai_family == AF_UNSPEC) + { + gethosts (AF_INET6); + no_inet6_data = no_data; + inet6_status = status; + } + if (req->ai_family == AF_INET + || req->ai_family == AF_UNSPEC + || (req->ai_family == AF_INET6 + && (req->ai_flags & AI_V4MAPPED) + /* Avoid generating the mapped addresses if we + know we are not going to need them. */ + && ((req->ai_flags & AI_ALL) || !res->got_ipv6))) + { + gethosts (AF_INET); + + if (req->ai_family == AF_INET) + { + no_inet6_data = no_data; + inet6_status = status; + } + } + + /* If we found one address for AF_INET or AF_INET6, + don't continue the search. */ + if (inet6_status == NSS_STATUS_SUCCESS + || status == NSS_STATUS_SUCCESS) + { + if ((req->ai_flags & AI_CANONNAME) != 0 + && res->canon == NULL) + { + char *canonbuf = getcanonname (nip, res->at, name); + if (canonbuf == NULL) + { + __resolv_context_put (res_ctx); + result = -EAI_MEMORY; + goto out; + } + res->canon = canonbuf; + } + status = NSS_STATUS_SUCCESS; + } + else + { + /* We can have different states for AF_INET and + AF_INET6. Try to find a useful one for both. */ + if (inet6_status == NSS_STATUS_TRYAGAIN) + status = NSS_STATUS_TRYAGAIN; + else if (status == NSS_STATUS_UNAVAIL + && inet6_status != NSS_STATUS_UNAVAIL) + status = inet6_status; + } + } + else + { + /* Could not locate any of the lookup functions. + The NSS lookup code does not consistently set + errno, so we need to supply our own error + code here. The root cause could either be a + resource allocation failure, or a missing + service function in the DSO (so it should not + be listed in /etc/nsswitch.conf). Assume the + former, and return EBUSY. */ + status = NSS_STATUS_UNAVAIL; + __set_h_errno (NETDB_INTERNAL); + __set_errno (EBUSY); + } + } + + if (nss_next_action (nip, status) == NSS_ACTION_RETURN) + break; + + /* The hosts database does not support MERGE. */ + if (nss_next_action (nip, status) == NSS_ACTION_MERGE) + do_merge = true; + + nip++; + if (nip->module == NULL) + no_more = -1; + } + + __resolv_context_put (res_ctx); + + /* If we have a failure which sets errno, report it using + EAI_SYSTEM. */ + if ((status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL) + && h_errno == NETDB_INTERNAL) + { + result = -EAI_SYSTEM; + goto out; + } + + if (no_data != 0 && no_inet6_data != 0) + { + /* If both requests timed out report this. */ + if (no_data == EAI_AGAIN && no_inet6_data == EAI_AGAIN) + result = -EAI_AGAIN; + else + /* We made requests but they turned out no data. The name + is known, though. */ + result = -EAI_NODATA; + } + +out: + if (result != 0) + gaih_result_reset (res); + return result; +} + /* Convert numeric addresses to binary into RES. On failure, RES->AT is set to NULL and an error code is returned. If AI_NUMERIC_HOST is not requested and the function cannot determine a result, RES->AT is set to NULL and 0 @@ -723,7 +981,7 @@ try_simple_gethostbyname (const char *name, const struct addrinfo *req, /* We found data, convert it. RES->AT from the conversion will either be an allocated block or NULL, both of which are safe to pass to free (). */ - if (!convert_hostent_to_gaih_addrtuple (req, AF_INET, h, &res->at)) + if (!convert_hostent_to_gaih_addrtuple (req, AF_INET, h, res)) return -EAI_MEMORY; res->free_at = true; @@ -801,264 +1059,14 @@ gaih_inet (const char *name, const struct gaih_service *service, goto process_list; #endif - int no_data = 0; - int no_inet6_data = 0; - nss_action_list nip; - enum nss_status inet6_status = NSS_STATUS_UNAVAIL; - enum nss_status status = NSS_STATUS_UNAVAIL; - int no_more; - struct resolv_context *res_ctx = NULL; - bool do_merge = false; - - no_more = !__nss_database_get (nss_database_hosts, &nip); - - /* If we are looking for both IPv4 and IPv6 address we don't - want the lookup functions to automatically promote IPv4 - addresses to IPv6 addresses, so we use the no_inet6 - function variant. */ - res_ctx = __resolv_context_get (); - if (res_ctx == NULL) - no_more = 1; - - while (!no_more) - { - /* Always start afresh; continue should discard previous results - and the hosts database does not support merge. */ - res.at = NULL; - free (res.canon); - free (addrmem); - res.canon = NULL; - addrmem = NULL; - got_ipv6 = false; - - if (do_merge) - { - __set_h_errno (NETDB_INTERNAL); - __set_errno (EBUSY); - break; - } - - no_data = 0; - nss_gethostbyname4_r *fct4 = NULL; - - /* gethostbyname4_r sends out parallel A and AAAA queries and - is thus only suitable for PF_UNSPEC. */ - if (req->ai_family == PF_UNSPEC) - fct4 = __nss_lookup_function (nip, "gethostbyname4_r"); - - if (fct4 != NULL) - { - while (1) - { - status = DL_CALL_FCT (fct4, (name, &res.at, - tmpbuf->data, tmpbuf->length, - &errno, &h_errno, - NULL)); - if (status == NSS_STATUS_SUCCESS) - break; - /* gethostbyname4_r may write into AT, so reset it. */ - res.at = NULL; - if (status != NSS_STATUS_TRYAGAIN - || errno != ERANGE || h_errno != NETDB_INTERNAL) - { - if (h_errno == TRY_AGAIN) - no_data = EAI_AGAIN; - else - no_data = h_errno == NO_DATA; - break; - } - - if (!scratch_buffer_grow (tmpbuf)) - { - __resolv_context_put (res_ctx); - result = -EAI_MEMORY; - goto free_and_return; - } - } - - if (status == NSS_STATUS_SUCCESS) - { - assert (!no_data); - no_data = 1; - - if ((req->ai_flags & AI_CANONNAME) != 0 && res.canon == NULL) - { - char *canonbuf = __strdup (res.at->name); - if (canonbuf == NULL) - { - __resolv_context_put (res_ctx); - result = -EAI_MEMORY; - goto free_and_return; - } - res.canon = canonbuf; - } - - struct gaih_addrtuple **pat = &res.at; - - while (*pat != NULL) - { - if ((*pat)->family == AF_INET - && req->ai_family == AF_INET6 - && (req->ai_flags & AI_V4MAPPED) != 0) - { - uint32_t *pataddr = (*pat)->addr; - (*pat)->family = AF_INET6; - pataddr[3] = pataddr[0]; - pataddr[2] = htonl (0xffff); - pataddr[1] = 0; - pataddr[0] = 0; - pat = &((*pat)->next); - no_data = 0; - } - else if (req->ai_family == AF_UNSPEC - || (*pat)->family == req->ai_family) - { - pat = &((*pat)->next); - - no_data = 0; - if (req->ai_family == AF_INET6) - res.got_ipv6 = true; - } - else - *pat = ((*pat)->next); - } - } - - no_inet6_data = no_data; - } - else - { - nss_gethostbyname3_r *fct = NULL; - if (req->ai_flags & AI_CANONNAME) - /* No need to use this function if we do not look for - the canonical name. The function does not exist in - all NSS modules and therefore the lookup would - often fail. */ - fct = __nss_lookup_function (nip, "gethostbyname3_r"); - if (fct == NULL) - /* We are cheating here. The gethostbyname2_r - function does not have the same interface as - gethostbyname3_r but the extra arguments the - latter takes are added at the end. So the - gethostbyname2_r code will just ignore them. */ - fct = __nss_lookup_function (nip, "gethostbyname2_r"); - - if (fct != NULL) - { - struct gaih_addrtuple **pat = &res.at; - - if (req->ai_family == AF_INET6 - || req->ai_family == AF_UNSPEC) - { - gethosts (AF_INET6); - no_inet6_data = no_data; - inet6_status = status; - } - if (req->ai_family == AF_INET - || req->ai_family == AF_UNSPEC - || (req->ai_family == AF_INET6 - && (req->ai_flags & AI_V4MAPPED) - /* Avoid generating the mapped addresses if we - know we are not going to need them. */ - && ((req->ai_flags & AI_ALL) || !res.got_ipv6))) - { - gethosts (AF_INET); - - if (req->ai_family == AF_INET) - { - no_inet6_data = no_data; - inet6_status = status; - } - } - - /* If we found one address for AF_INET or AF_INET6, - don't continue the search. */ - if (inet6_status == NSS_STATUS_SUCCESS - || status == NSS_STATUS_SUCCESS) - { - if ((req->ai_flags & AI_CANONNAME) != 0 - && res.canon == NULL) - { - char *canonbuf = getcanonname (nip, res.at, name); - if (canonbuf == NULL) - { - __resolv_context_put (res_ctx); - result = -EAI_MEMORY; - goto free_and_return; - } - res.canon = canonbuf; - } - status = NSS_STATUS_SUCCESS; - } - else - { - /* We can have different states for AF_INET and - AF_INET6. Try to find a useful one for both. */ - if (inet6_status == NSS_STATUS_TRYAGAIN) - status = NSS_STATUS_TRYAGAIN; - else if (status == NSS_STATUS_UNAVAIL - && inet6_status != NSS_STATUS_UNAVAIL) - status = inet6_status; - } - } - else - { - /* Could not locate any of the lookup functions. - The NSS lookup code does not consistently set - errno, so we need to supply our own error - code here. The root cause could either be a - resource allocation failure, or a missing - service function in the DSO (so it should not - be listed in /etc/nsswitch.conf). Assume the - former, and return EBUSY. */ - status = NSS_STATUS_UNAVAIL; - __set_h_errno (NETDB_INTERNAL); - __set_errno (EBUSY); - } - } - - if (nss_next_action (nip, status) == NSS_ACTION_RETURN) - break; - - /* The hosts database does not support MERGE. */ - if (nss_next_action (nip, status) == NSS_ACTION_MERGE) - do_merge = true; - - nip++; - if (nip->module == NULL) - no_more = -1; - } - - __resolv_context_put (res_ctx); - - /* If we have a failure which sets errno, report it using - EAI_SYSTEM. */ - if ((status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL) - && h_errno == NETDB_INTERNAL) - { - result = -EAI_SYSTEM; - goto free_and_return; - } - - if (no_data != 0 && no_inet6_data != 0) - { - /* If both requests timed out report this. */ - if (no_data == EAI_AGAIN && no_inet6_data == EAI_AGAIN) - result = -EAI_AGAIN; - else - /* We made requests but they turned out no data. The name - is known, though. */ - result = -EAI_NODATA; - - goto free_and_return; - } + if ((result = get_nss_addresses (name, req, tmpbuf, &res)) != 0) + goto free_and_return; + else if (res.at != NULL) + goto process_list; - process_list: - if (res.at == NULL) - { - result = -EAI_NONAME; - goto free_and_return; - } + /* None of the lookups worked, so name not found. */ + result = -EAI_NONAME; + goto free_and_return; } else { @@ -1089,6 +1097,7 @@ gaih_inet (const char *name, const struct gaih_service *service, } } +process_list: { /* Set up the canonical name if we need it. */ if ((result = process_canonname (req, orig_name, &res)) != 0) |