From 9098deb96a49fdb88ec833b7fa2087f6bd4c6aca Mon Sep 17 00:00:00 2001
From: Siddhesh Poyarekar <siddhesh@sourceware.org>
Date: Mon, 7 Mar 2022 15:56:22 +0530
Subject: [PATCH] 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>
(cherry picked from commit 906cecbe0889e601c91d9aba738049c73ebe4dd2)
---
 sysdeps/posix/getaddrinfo.c | 563 ++++++++++++++++++------------------
 1 file 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)
-- 
2.39.3
