gaih_inet: separate nss lookup loop into its own function

Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Reviewed-by: DJ Delorie <dj@redhat.com>
This commit is contained in:
Siddhesh Poyarekar 2022-03-07 15:56:22 +05:30
parent e7e5315b7f
commit 906cecbe08

View File

@ -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;
if ((result = get_nss_addresses (name, req, tmpbuf, &res)) != 0)
goto free_and_return;
else if (res.at != NULL)
goto process_list;
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;
}
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)