gaih_inet: separate nss lookup loop into its own function
authorSiddhesh Poyarekar <siddhesh@sourceware.org>
Mon, 7 Mar 2022 10:26:22 +0000 (15:56 +0530)
committerSiddhesh Poyarekar <siddhesh@sourceware.org>
Tue, 22 Mar 2022 14:09:33 +0000 (19:39 +0530)
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
Reviewed-by: DJ Delorie <dj@redhat.com>
sysdeps/posix/getaddrinfo.c

index 01be932..f70ce2c 100644 (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;
-
-      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)