]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - sysdeps/posix/getaddrinfo.c
CVE-2016-10739: getaddrinfo: Fully parse IPv4 address strings [BZ #20018]
[thirdparty/glibc.git] / sysdeps / posix / getaddrinfo.c
index e8f4099b7f5859a4d37ed4bc4c566051f2dc4985..aa054b620f2af75efcabd14cf96e12ccb3063451 100644 (file)
@@ -1,3 +1,21 @@
+/* Host and service name lookups using Name Service Switch modules.
+   Copyright (C) 1996-2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /* The Inner Net License, Version 2.00
 
   The author(s) grant permission for redistribution and use in source and
@@ -40,12 +58,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <errno.h>
 #include <ifaddrs.h>
 #include <netdb.h>
-#include <resolv.h>
+#include <nss.h>
+#include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
+#include <resolv/res_use_inet6.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
+#include <stdint.h>
 #include <arpa/inet.h>
 #include <net/if.h>
 #include <netinet/in.h>
@@ -56,23 +78,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <sys/utsname.h>
 #include <unistd.h>
 #include <nsswitch.h>
-#include <bits/libc-lock.h>
+#include <libc-lock.h>
 #include <not-cancel.h>
 #include <nscd/nscd-client.h>
 #include <nscd/nscd_proto.h>
+#include <scratch_buffer.h>
+#include <inet/net-internal.h>
 
-#ifdef HAVE_LIBIDN
-extern int __idna_to_ascii_lz (const char *input, char **output, int flags);
-extern int __idna_to_unicode_lzlz (const char *input, char **output,
-                                  int flags);
-# include <libidn/idna.h>
-#endif
-
-#define GAIH_OKIFUNSPEC 0x0100
-#define GAIH_EAI        ~(GAIH_OKIFUNSPEC)
+/* Former AI_IDN_ALLOW_UNASSIGNED and AI_IDN_USE_STD3_ASCII_RULES
+   flags, now ignored.  */
+#define DEPRECATED_AI_IDN 0x300
 
-#ifndef UNIX_PATH_MAX
-# define UNIX_PATH_MAX  108
+#if IS_IN (libc)
+# define feof_unlocked(fp) __feof_unlocked (fp)
 #endif
 
 struct gaih_service
@@ -91,21 +109,14 @@ struct gaih_servtuple
 
 static const struct gaih_servtuple nullserv;
 
-struct gaih_addrtuple
-  {
-    struct gaih_addrtuple *next;
-    char *name;
-    int family;
-    uint32_t addr[4];
-    uint32_t scopeid;
-  };
 
 struct gaih_typeproto
   {
     int socktype;
     int protocol;
-    char name[4];
-    int protoflag;
+    uint8_t protoflag;
+    bool defaultflag;
+    char name[8];
   };
 
 /* Values for `protoflag'.  */
@@ -114,21 +125,23 @@ struct gaih_typeproto
 
 static const struct gaih_typeproto gaih_inet_typeproto[] =
 {
-  { 0, 0, "", 0 },
-  { SOCK_STREAM, IPPROTO_TCP, "tcp", 0 },
-  { SOCK_DGRAM, IPPROTO_UDP, "udp", 0 },
-  { SOCK_RAW, 0, "raw", GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE },
-  { 0, 0, "", 0 }
+  { 0, 0, 0, false, "" },
+  { SOCK_STREAM, IPPROTO_TCP, 0, true, "tcp" },
+  { SOCK_DGRAM, IPPROTO_UDP, 0, true, "udp" },
+#if defined SOCK_DCCP && defined IPPROTO_DCCP
+  { SOCK_DCCP, IPPROTO_DCCP, 0, false, "dccp" },
+#endif
+#ifdef IPPROTO_UDPLITE
+  { SOCK_DGRAM, IPPROTO_UDPLITE, 0, false, "udplite" },
+#endif
+#ifdef IPPROTO_SCTP
+  { SOCK_STREAM, IPPROTO_SCTP, 0, false, "sctp" },
+  { SOCK_SEQPACKET, IPPROTO_SCTP, 0, false, "sctp" },
+#endif
+  { SOCK_RAW, 0, GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE, true, "raw" },
+  { 0, 0, 0, false, "" }
 };
 
-struct gaih
-  {
-    int family;
-    int (*gaih)(const char *name, const struct gaih_service *service,
-               const struct addrinfo *req, struct addrinfo **pai,
-               unsigned int *naddrs);
-  };
-
 static const struct addrinfo default_hints =
   {
     .ai_flags = AI_DEFAULT,
@@ -144,26 +157,26 @@ static const struct addrinfo default_hints =
 
 static int
 gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
-               const struct addrinfo *req, struct gaih_servtuple *st)
+               const struct addrinfo *req, struct gaih_servtuple *st,
+               struct scratch_buffer *tmpbuf)
 {
   struct servent *s;
-  size_t tmpbuflen = 1024;
   struct servent ts;
-  char *tmpbuf;
   int r;
 
   do
     {
-      tmpbuf = __alloca (tmpbuflen);
-
-      r = __getservbyname_r (servicename, tp->name, &ts, tmpbuf, tmpbuflen,
-                            &s);
+      r = __getservbyname_r (servicename, tp->name, &ts,
+                            tmpbuf->data, tmpbuf->length, &s);
       if (r != 0 || s == NULL)
        {
          if (r == ERANGE)
-           tmpbuflen *= 2;
+           {
+             if (!scratch_buffer_grow (tmpbuf))
+               return -EAI_MEMORY;
+           }
          else
-           return GAIH_OKIFUNSPEC | -EAI_SERVICE;
+           return -EAI_SERVICE;
        }
     }
   while (r);
@@ -177,75 +190,123 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
   return 0;
 }
 
+/* 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.  *RESULT must be NULL or a pointer to a
+   linked-list.  The new addresses are appended at the end.  */
+static bool
+convert_hostent_to_gaih_addrtuple (const struct addrinfo *req,
+                                  int family,
+                                  struct hostent *h,
+                                  struct gaih_addrtuple **result)
+{
+  while (*result)
+    result = &(*result)->next;
+
+  /* Count the number of addresses in h->h_addr_list.  */
+  size_t count = 0;
+  for (char **p = h->h_addr_list; *p != NULL; ++p)
+    ++count;
+
+  /* Report no data if no addresses are available, or if the incoming
+     address size is larger than what we can store.  */
+  if (count == 0 || h->h_length > sizeof (((struct gaih_addrtuple) {}).addr))
+    return true;
+
+  struct gaih_addrtuple *array = calloc (count, sizeof (*array));
+  if (array == NULL)
+    return false;
+
+  for (size_t i = 0; i < count; ++i)
+    {
+      if (family == AF_INET && req->ai_family == AF_INET6)
+       {
+         /* Perform address mapping. */
+         array[i].family = AF_INET6;
+         memcpy(array[i].addr + 3, h->h_addr_list[i], sizeof (uint32_t));
+         array[i].addr[2] = htonl (0xffff);
+       }
+      else
+       {
+         array[i].family = family;
+         memcpy (array[i].addr, h->h_addr_list[i], h->h_length);
+       }
+      array[i].next = array + i + 1;
+    }
+  array[0].name = h->h_name;
+  array[count - 1].next = NULL;
+
+  *result = array;
+  return true;
+}
+
 #define gethosts(_family, _type) \
  {                                                                           \
-  int i;                                                                     \
-  int herrno;                                                                \
   struct hostent th;                                                         \
-  struct hostent *h;                                                         \
   char *localcanon = NULL;                                                   \
   no_data = 0;                                                               \
-  while (1) {                                                                \
-    rc = 0;                                                                  \
-    status = DL_CALL_FCT (fct, (name, _family, &th, tmpbuf, tmpbuflen,       \
-                               &rc, &herrno, NULL, &localcanon));            \
-    if (rc != ERANGE || herrno != NETDB_INTERNAL)                            \
-      break;                                                                 \
-    tmpbuf = extend_alloca (tmpbuf, tmpbuflen, 2 * tmpbuflen);               \
-  }                                                                          \
-  if (status == NSS_STATUS_SUCCESS && rc == 0)                               \
-    h = &th;                                                                 \
-  else                                                                       \
-    h = NULL;                                                                \
-  if (rc != 0)                                                               \
+  while (1)                                                                  \
+    {                                                                        \
+      status = DL_CALL_FCT (fct, (name, _family, &th,                        \
+                                 tmpbuf->data, tmpbuf->length,               \
+                                 &errno, &h_errno, NULL, &localcanon));      \
+      if (status != NSS_STATUS_TRYAGAIN || h_errno != NETDB_INTERNAL         \
+         || errno != ERANGE)                                                 \
+       break;                                                                \
+      if (!scratch_buffer_grow (tmpbuf))                                     \
+       {                                                                     \
+         __resolv_context_enable_inet6 (res_ctx, res_enable_inet6);          \
+         __resolv_context_put (res_ctx);                                     \
+         result = -EAI_MEMORY;                                               \
+         goto free_and_return;                                               \
+       }                                                                     \
+    }                                                                        \
+  if (status == NSS_STATUS_NOTFOUND                                          \
+      || status == NSS_STATUS_TRYAGAIN || status == NSS_STATUS_UNAVAIL)              \
     {                                                                        \
-      if (herrno == NETDB_INTERNAL)                                          \
+      if (h_errno == NETDB_INTERNAL)                                         \
        {                                                                     \
-         __set_h_errno (herrno);                                             \
-         return -EAI_SYSTEM;                                                 \
+         __resolv_context_enable_inet6 (res_ctx, res_enable_inet6);          \
+         __resolv_context_put (res_ctx);                                     \
+         result = -EAI_SYSTEM;                                               \
+         goto free_and_return;                                               \
        }                                                                     \
-      if (herrno == TRY_AGAIN)                                               \
+      if (h_errno == TRY_AGAIN)                                                      \
        no_data = EAI_AGAIN;                                                  \
       else                                                                   \
-       no_data = herrno == NO_DATA;                                          \
+       no_data = h_errno == NO_DATA;                                         \
     }                                                                        \
-  else if (h != NULL)                                                        \
+  else if (status == NSS_STATUS_SUCCESS)                                     \
     {                                                                        \
-      for (i = 0; h->h_addr_list[i]; i++)                                    \
+      if (!convert_hostent_to_gaih_addrtuple (req, _family, &th, &addrmem))   \
        {                                                                     \
-         if (*pat == NULL)                                                   \
-           {                                                                 \
-             *pat = __alloca (sizeof (struct gaih_addrtuple));               \
-             (*pat)->scopeid = 0;                                            \
-           }                                                                 \
-         uint32_t *addr = (*pat)->addr;                                      \
-         (*pat)->next = NULL;                                                \
-         (*pat)->name = i == 0 ? strdupa (h->h_name) : NULL;                 \
-         if (_family == AF_INET && req->ai_family == AF_INET6)               \
-           {                                                                 \
-             (*pat)->family = AF_INET6;                                      \
-             addr[3] = *(uint32_t *) h->h_addr_list[i];                      \
-             addr[2] = htonl (0xffff);                                       \
-             addr[1] = 0;                                                    \
-             addr[0] = 0;                                                    \
-           }                                                                 \
-         else                                                                \
+         __resolv_context_enable_inet6 (res_ctx, res_enable_inet6);          \
+         __resolv_context_put (res_ctx);                                     \
+         result = -EAI_SYSTEM;                                               \
+         goto free_and_return;                                               \
+       }                                                                     \
+      *pat = addrmem;                                                        \
+                                                                             \
+      if (localcanon != NULL && canon == NULL)                               \
+       {                                                                     \
+         canonbuf = __strdup (localcanon);                                   \
+         if (canonbuf == NULL)                                               \
            {                                                                 \
-             (*pat)->family = _family;                                       \
-             memcpy (addr, h->h_addr_list[i], sizeof(_type));                \
+             result = -EAI_SYSTEM;                                           \
+             goto free_and_return;                                           \
            }                                                                 \
-         pat = &((*pat)->next);                                              \
+         canon = canonbuf;                                                   \
        }                                                                     \
-                                                                             \
-      if (localcanon !=        NULL && canon == NULL)                                \
-       canon = strdupa (localcanon);                                         \
-                                                                             \
-      if (_family == AF_INET6 && i > 0)                                              \
+      if (_family == AF_INET6 && *pat != NULL)                               \
        got_ipv6 = true;                                                      \
     }                                                                        \
  }
 
 
+typedef enum nss_status (*nss_gethostbyname4_r)
+  (const char *name, struct gaih_addrtuple **pat,
+   char *buffer, size_t buflen, int *errnop,
+   int *h_errnop, int32_t *ttlp);
 typedef enum nss_status (*nss_gethostbyname3_r)
   (const char *name, int af, struct hostent *host,
    char *buffer, size_t buflen, int *errnop,
@@ -253,22 +314,46 @@ typedef enum nss_status (*nss_gethostbyname3_r)
 typedef enum nss_status (*nss_getcanonname_r)
   (const char *name, char *buffer, size_t buflen, char **result,
    int *errnop, int *h_errnop);
-extern service_user *__nss_hosts_database attribute_hidden;
 
+/* This function is called if a canonical name is requested, but if
+   the service function did not provide it.  It tries to obtain the
+   name using getcanonname_r from the same service NIP.  If the name
+   cannot be canonicalized, return a copy of NAME.  Return NULL on
+   memory allocation failure.  The returned string is allocated on the
+   heap; the caller has to free it.  */
+static char *
+getcanonname (service_user *nip, struct gaih_addrtuple *at, const char *name)
+{
+  nss_getcanonname_r cfct = __nss_lookup_function (nip, "getcanonname_r");
+  char *s = (char *) name;
+  if (cfct != NULL)
+    {
+      char buf[256];
+      if (DL_CALL_FCT (cfct, (at->name ?: name, buf, sizeof (buf),
+                             &s, &errno, &h_errno)) != NSS_STATUS_SUCCESS)
+       /* If the canonical name cannot be determined, use the passed
+          string.  */
+       s = (char *) name;
+    }
+  return __strdup (name);
+}
 
 static int
 gaih_inet (const char *name, const struct gaih_service *service,
           const struct addrinfo *req, struct addrinfo **pai,
-          unsigned int *naddrs)
+          unsigned int *naddrs, struct scratch_buffer *tmpbuf)
 {
   const struct gaih_typeproto *tp = gaih_inet_typeproto;
   struct gaih_servtuple *st = (struct gaih_servtuple *) &nullserv;
   struct gaih_addrtuple *at = NULL;
-  int rc;
   bool got_ipv6 = false;
   const char *canon = NULL;
   const char *orig_name = name;
 
+  /* Reserve stack memory for the scratch buffer in the getaddrinfo
+     function.  */
+  size_t alloca_used = sizeof (struct scratch_buffer);
+
   if (req->ai_protocol || req->ai_socktype)
     {
       ++tp;
@@ -283,9 +368,9 @@ gaih_inet (const char *name, const struct gaih_service *service,
       if (! tp->name[0])
        {
          if (req->ai_socktype)
-           return GAIH_OKIFUNSPEC | -EAI_SOCKTYPE;
+           return -EAI_SOCKTYPE;
          else
-           return GAIH_OKIFUNSPEC | -EAI_SERVICE;
+           return -EAI_SERVICE;
        }
     }
 
@@ -293,16 +378,17 @@ gaih_inet (const char *name, const struct gaih_service *service,
   if (service != NULL)
     {
       if ((tp->protoflag & GAI_PROTO_NOSERVICE) != 0)
-       return GAIH_OKIFUNSPEC | -EAI_SERVICE;
+       return -EAI_SERVICE;
 
       if (service->num < 0)
        {
          if (tp->name[0])
            {
              st = (struct gaih_servtuple *)
-               __alloca (sizeof (struct gaih_servtuple));
+               alloca_account (sizeof (struct gaih_servtuple), alloca_used);
 
-             if ((rc = gaih_inet_serv (service->name, tp, req, st)))
+             int rc = gaih_inet_serv (service->name, tp, req, st, tmpbuf);
+             if (__glibc_unlikely (rc != 0))
                return rc;
            }
          else
@@ -324,20 +410,18 @@ gaih_inet (const char *name, const struct gaih_service *service,
                    continue;
 
                  newp = (struct gaih_servtuple *)
-                   __alloca (sizeof (struct gaih_servtuple));
+                   alloca_account (sizeof (struct gaih_servtuple),
+                                   alloca_used);
 
-                 if ((rc = gaih_inet_serv (service->name, tp, req, newp)))
-                   {
-                     if (rc & GAIH_OKIFUNSPEC)
-                       continue;
-                     return rc;
-                   }
+                 if (gaih_inet_serv (service->name,
+                                     tp, req, newp, tmpbuf) != 0)
+                   continue;
 
                  *pst = newp;
                  pst = &(newp->next);
                }
              if (st == (struct gaih_servtuple *) &nullserv)
-               return GAIH_OKIFUNSPEC | -EAI_SERVICE;
+               return -EAI_SERVICE;
            }
        }
       else
@@ -352,7 +436,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
 
       if (req->ai_socktype || req->ai_protocol)
        {
-         st = __alloca (sizeof (struct gaih_servtuple));
+         st = alloca_account (sizeof (struct gaih_servtuple), alloca_used);
          st->next = NULL;
          st->socktype = tp->socktype;
          st->protocol = ((tp->protoflag & GAI_PROTO_PROTOANY)
@@ -365,59 +449,46 @@ gaih_inet (const char *name, const struct gaih_service *service,
             we know about.  */
          struct gaih_servtuple **lastp = &st;
          for (++tp; tp->name[0]; ++tp)
-           {
-             struct gaih_servtuple *newp;
+           if (tp->defaultflag)
+             {
+               struct gaih_servtuple *newp;
 
-             newp = __alloca (sizeof (struct gaih_servtuple));
-             newp->next = NULL;
-             newp->socktype = tp->socktype;
-             newp->protocol = tp->protocol;
-             newp->port = port;
+               newp = alloca_account (sizeof (struct gaih_servtuple),
+                                      alloca_used);
+               newp->next = NULL;
+               newp->socktype = tp->socktype;
+               newp->protocol = tp->protocol;
+               newp->port = port;
 
-             *lastp = newp;
-             lastp = &newp->next;
-           }
+               *lastp = newp;
+               lastp = &newp->next;
+             }
        }
     }
 
+  bool malloc_name = false;
+  struct gaih_addrtuple *addrmem = NULL;
+  char *canonbuf = NULL;
+  int result = 0;
+
   if (name != NULL)
     {
-      at = __alloca (sizeof (struct gaih_addrtuple));
-
+      at = alloca_account (sizeof (struct gaih_addrtuple), alloca_used);
       at->family = AF_UNSPEC;
       at->scopeid = 0;
       at->next = NULL;
 
-#ifdef HAVE_LIBIDN
       if (req->ai_flags & AI_IDN)
        {
-         int idn_flags = 0;
-         if (req->ai_flags & AI_IDN_ALLOW_UNASSIGNED)
-           idn_flags |= IDNA_ALLOW_UNASSIGNED;
-         if (req->ai_flags & AI_IDN_USE_STD3_ASCII_RULES)
-           idn_flags |= IDNA_USE_STD3_ASCII_RULES;
-
-         char *p = NULL;
-         rc = __idna_to_ascii_lz (name, &p, idn_flags);
-         if (rc != IDNA_SUCCESS)
-           {
-             if (rc == IDNA_MALLOC_ERROR)
-               return -EAI_MEMORY;
-             if (rc == IDNA_DLOPEN_ERROR)
-               return -EAI_SYSTEM;
-             return -EAI_IDN_ENCODE;
-           }
-         /* In case the output string is the same as the input string
-            no new string has been allocated.  */
-         if (p != name)
-           {
-             name = strdupa (p);
-             free (p);
-           }
+         char *out;
+         result = __idna_to_dns_encoding (name, &out);
+         if (result != 0)
+           return -result;
+         name = out;
+         malloc_name = true;
        }
-#endif
 
-      if (__inet_aton (name, (struct in_addr *) at->addr) != 0)
+      if (__inet_aton_exact (name, (struct in_addr *) at->addr) != 0)
        {
          if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
            at->family = AF_INET;
@@ -430,23 +501,24 @@ gaih_inet (const char *name, const struct gaih_service *service,
              at->family = AF_INET6;
            }
          else
-           return -EAI_ADDRFAMILY;
+           {
+             result = -EAI_ADDRFAMILY;
+             goto free_and_return;
+           }
 
          if (req->ai_flags & AI_CANONNAME)
            canon = name;
        }
       else if (at->family == AF_UNSPEC)
        {
-         char *namebuf = (char *) name;
          char *scope_delim = strchr (name, SCOPE_DELIMITER);
-
-         if (__builtin_expect (scope_delim != NULL, 0))
-           {
-             namebuf = alloca (scope_delim - name + 1);
-             *((char *) __mempcpy (namebuf, name, scope_delim - name)) = '\0';
-           }
-
-         if (inet_pton (AF_INET6, namebuf, at->addr) > 0)
+         int e;
+         if (scope_delim == NULL)
+           e = inet_pton (AF_INET6, name, at->addr);
+         else
+           e = __inet_pton_length (AF_INET6, name, scope_delim - name,
+                                   at->addr);
+         if (e > 0)
            {
              if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET6)
                at->family = AF_INET6;
@@ -457,30 +529,18 @@ gaih_inet (const char *name, const struct gaih_service *service,
                  at->family = AF_INET;
                }
              else
-               return -EAI_ADDRFAMILY;
-
-             if (scope_delim != NULL)
                {
-                 int try_numericscope = 0;
-                 if (IN6_IS_ADDR_LINKLOCAL (at->addr)
-                     || IN6_IS_ADDR_MC_LINKLOCAL (at->addr))
-                   {
-                     at->scopeid = if_nametoindex (scope_delim + 1);
-                     if (at->scopeid == 0)
-                       try_numericscope = 1;
-                   }
-                 else
-                   try_numericscope = 1;
+                 result = -EAI_ADDRFAMILY;
+                 goto free_and_return;
+               }
 
-                 if (try_numericscope != 0)
-                   {
-                     char *end;
-                     assert (sizeof (uint32_t) <= sizeof (unsigned long));
-                     at->scopeid = (uint32_t) strtoul (scope_delim + 1, &end,
-                                                       10);
-                     if (*end != '\0')
-                       return GAIH_OKIFUNSPEC | -EAI_NONAME;
-                   }
+             if (scope_delim != NULL
+                 && __inet6_scopeid_pton ((struct in6_addr *) at->addr,
+                                          scope_delim + 1,
+                                          &at->scopeid) != 0)
+               {
+                 result = -EAI_NONAME;
+                 goto free_and_return;
                }
 
              if (req->ai_flags & AI_CANONNAME)
@@ -493,92 +553,71 @@ gaih_inet (const char *name, const struct gaih_service *service,
          struct gaih_addrtuple **pat = &at;
          int no_data = 0;
          int no_inet6_data = 0;
-         service_user *nip = NULL;
+         service_user *nip;
          enum nss_status inet6_status = NSS_STATUS_UNAVAIL;
          enum nss_status status = NSS_STATUS_UNAVAIL;
          int no_more;
-         int old_res_options;
+         struct resolv_context *res_ctx = NULL;
+         bool res_enable_inet6 = false;
 
-         /* If we do not have to look for IPv4 and IPv6 together, use
-            the simple, old functions.  */
+         /* If we do not have to look for IPv6 addresses or the canonical
+            name, use the simple, old functions, which do not support
+            IPv6 scope ids, nor retrieving the canonical name.  */
          if (req->ai_family == AF_INET
-             || (req->ai_family == AF_INET6
-                 && ((req->ai_flags & AI_V4MAPPED) == 0
-                     || (req->ai_flags & AI_ALL) == 0)))
+             && (req->ai_flags & AI_CANONNAME) == 0)
            {
-             int family = req->ai_family;
-             size_t tmpbuflen = 512;
-             char *tmpbuf = alloca (tmpbuflen);
              int rc;
              struct hostent th;
              struct hostent *h;
-             int herrno;
 
-           simple_again:
              while (1)
                {
-                 rc = __gethostbyname2_r (name, family, &th, tmpbuf,
-                                          tmpbuflen, &h, &herrno);
-                 if (rc != ERANGE || herrno != NETDB_INTERNAL)
+                 rc = __gethostbyname2_r (name, AF_INET, &th,
+                                          tmpbuf->data, tmpbuf->length,
+                                          &h, &h_errno);
+                 if (rc != ERANGE || h_errno != NETDB_INTERNAL)
                    break;
-                 tmpbuf = extend_alloca (tmpbuf, tmpbuflen, 2 * tmpbuflen);
+                 if (!scratch_buffer_grow (tmpbuf))
+                   {
+                     result = -EAI_MEMORY;
+                     goto free_and_return;
+                   }
                }
 
              if (rc == 0)
                {
-                 if (h == NULL)
+                 if (h != NULL)
                    {
-                     if (req->ai_family == AF_INET6
-                         && (req->ai_flags & AI_V4MAPPED)
-                         && family == AF_INET6)
+                     /* We found data, convert it.  */
+                     if (!convert_hostent_to_gaih_addrtuple
+                         (req, AF_INET, h, &addrmem))
                        {
-                         /* Try again, this time looking for IPv4
-                            addresses.  */
-                         family = AF_INET;
-                         goto simple_again;
+                         result = -EAI_MEMORY;
+                         goto free_and_return;
                        }
+                     *pat = addrmem;
                    }
                  else
                    {
-                     /* We found data, now convert it into the list.  */
-                     for (int i = 0; h->h_addr_list[i]; ++i)
-                       {
-                         if (*pat == NULL)
-                           {
-                             *pat = __alloca (sizeof (struct gaih_addrtuple));
-                             (*pat)->scopeid = 0;
-                           }
-                         (*pat)->next = NULL;
-                         (*pat)->family = req->ai_family;
-                         if (family == req->ai_family)
-                           memcpy ((*pat)->addr, h->h_addr_list[i],
-                                   h->h_length);
-                         else
-                           {
-                             uint32_t *addr = (uint32_t *) (*pat)->addr;
-                             addr[3] = *(uint32_t *) h->h_addr_list[i];
-                             addr[2] = htonl (0xffff);
-                             addr[1] = 0;
-                             addr[0] = 0;
-                           }
-                         pat = &((*pat)->next);
-                       }
+                     if (h_errno == NO_DATA)
+                       result = -EAI_NODATA;
+                     else
+                       result = -EAI_NONAME;
+                     goto free_and_return;
                    }
                }
              else
                {
-                 if (herrno == NETDB_INTERNAL)
-                   {
-                     __set_h_errno (herrno);
-                     return -EAI_SYSTEM;
-                   }
-                 if (herrno == TRY_AGAIN)
-                   {
-                     return -EAI_AGAIN;
-                   }
-                 /* We made requests but they turned out no data.
-                    The name is known, though.  */
-                 return GAIH_OKIFUNSPEC | -EAI_NODATA;
+                 if (h_errno == NETDB_INTERNAL)
+                   result = -EAI_SYSTEM;
+                 else if (h_errno == TRY_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;
                }
 
              goto process_list;
@@ -589,33 +628,61 @@ gaih_inet (const char *name, const struct gaih_service *service,
              && ++__nss_not_use_nscd_hosts > NSS_NSCD_RETRY)
            __nss_not_use_nscd_hosts = 0;
 
-         if (!__nss_not_use_nscd_hosts)
+         if (!__nss_not_use_nscd_hosts
+             && !__nss_database_custom[NSS_DBSIDX_hosts])
            {
              /* Try to use nscd.  */
              struct nscd_ai_result *air = NULL;
-             int herrno;
-             int err = __nscd_getai (name, &air, &herrno);
+             int err = __nscd_getai (name, &air, &h_errno);
              if (air != NULL)
                {
                  /* Transform into gaih_addrtuple list.  */
                  bool added_canon = (req->ai_flags & AI_CANONNAME) == 0;
                  char *addrs = air->addrs;
 
+                 addrmem = calloc (air->naddrs, sizeof (*addrmem));
+                 if (addrmem == NULL)
+                   {
+                     result = -EAI_MEMORY;
+                     goto free_and_return;
+                   }
+
+                 struct gaih_addrtuple *addrfree = addrmem;
                  for (int i = 0; i < air->naddrs; ++i)
                    {
                      socklen_t size = (air->family[i] == AF_INET
                                        ? INADDRSZ : IN6ADDRSZ);
+
+                     if (!((air->family[i] == AF_INET
+                            && req->ai_family == AF_INET6
+                            && (req->ai_flags & AI_V4MAPPED) != 0)
+                           || req->ai_family == AF_UNSPEC
+                           || air->family[i] == req->ai_family))
+                       {
+                         /* Skip over non-matching result.  */
+                         addrs += size;
+                         continue;
+                       }
+
                      if (*pat == NULL)
                        {
-                         *pat = __alloca (sizeof (struct gaih_addrtuple));
+                         *pat = addrfree++;
                          (*pat)->scopeid = 0;
                        }
                      uint32_t *pataddr = (*pat)->addr;
                      (*pat)->next = NULL;
                      if (added_canon || air->canon == NULL)
                        (*pat)->name = NULL;
-                     else
-                       canon = (*pat)->name = strdupa (air->canon);
+                     else if (canonbuf == NULL)
+                       {
+                         canonbuf = __strdup (air->canon);
+                         if (canonbuf == NULL)
+                           {
+                             result = -EAI_MEMORY;
+                             goto free_and_return;
+                           }
+                         canon = (*pat)->name = canonbuf;
+                       }
 
                      if (air->family[i] == AF_INET
                          && req->ai_family == AF_INET6
@@ -645,127 +712,215 @@ gaih_inet (const char *name, const struct gaih_service *service,
                  free (air);
 
                  if (at->family == AF_UNSPEC)
-                   return GAIH_OKIFUNSPEC | -EAI_NONAME;
+                   {
+                     result = -EAI_NONAME;
+                     goto free_and_return;
+                   }
 
                  goto process_list;
                }
-             else if (err != 0 && __nss_not_use_nscd_hosts == 0)
+             else if (err == 0)
+               /* The database contains a negative entry.  */
+               goto free_and_return;
+             else if (__nss_not_use_nscd_hosts == 0)
                {
-                 if (herrno == NETDB_INTERNAL && errno == ENOMEM)
-                   return -EAI_MEMORY;
-                 if (herrno == TRY_AGAIN)
-                   return -EAI_AGAIN;
-                 return -EAI_SYSTEM;
+                 if (h_errno == NETDB_INTERNAL && errno == ENOMEM)
+                   result = -EAI_MEMORY;
+                 else if (h_errno == TRY_AGAIN)
+                   result = -EAI_AGAIN;
+                 else
+                   result = -EAI_SYSTEM;
+
+                 goto free_and_return;
                }
            }
 #endif
 
-         if (__nss_hosts_database != NULL)
-           {
-             no_more = 0;
-             nip = __nss_hosts_database;
-           }
-         else
+         if (__nss_hosts_database == NULL)
            no_more = __nss_database_lookup ("hosts", NULL,
                                             "dns [!UNAVAIL=return] files",
-                                            &nip);
-
-         if (__res_maybe_init (&_res, 0) == -1)
-           no_more = 1;
+                                            &__nss_hosts_database);
+         else
+           no_more = 0;
+         nip = __nss_hosts_database;
 
          /* 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.  Currently this is decided
-            by setting the RES_USE_INET6 bit in _res.options.  */
-         old_res_options = _res.options;
-         _res.options &= ~RES_USE_INET6;
-
-         size_t tmpbuflen = 512;
-         char *tmpbuf = alloca (tmpbuflen);
+            addresses to IPv6 addresses, so we use the no_inet6
+            function variant.  */
+         res_ctx = __resolv_context_get ();
+         res_enable_inet6 = __resolv_context_disable_inet6 (res_ctx);
+         if (res_ctx == NULL)
+           no_more = 1;
 
          while (!no_more)
            {
-             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)
+             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)
                {
-                 if (req->ai_family == AF_INET6
-                     || req->ai_family == AF_UNSPEC)
+                 while (1)
                    {
-                     gethosts (AF_INET6, struct in6_addr);
-                     no_inet6_data = no_data;
-                     inet6_status = status;
+                     status = DL_CALL_FCT (fct4, (name, pat,
+                                                  tmpbuf->data, tmpbuf->length,
+                                                  &errno, &h_errno,
+                                                  NULL));
+                     if (status == NSS_STATUS_SUCCESS)
+                       break;
+                     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_enable_inet6
+                           (res_ctx, res_enable_inet6);
+                         __resolv_context_put (res_ctx);
+                         result = -EAI_MEMORY;
+                         goto free_and_return;
+                       }
                    }
-                 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) || !got_ipv6)))
+
+                 if (status == NSS_STATUS_SUCCESS)
                    {
-                     gethosts (AF_INET, struct in_addr);
+                     assert (!no_data);
+                     no_data = 1;
 
-                     if (req->ai_family == AF_INET)
+                     if ((req->ai_flags & AI_CANONNAME) != 0 && canon == NULL)
+                       canon = (*pat)->name;
+
+                     while (*pat != NULL)
                        {
-                         no_inet6_data = no_data;
-                         inet6_status = status;
+                         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)
+                               got_ipv6 = true;
+                           }
+                         else
+                           *pat = ((*pat)->next);
                        }
                    }
 
-                 /* 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)
+                 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_flags & AI_CANONNAME) != 0 && canon == NULL)
+                     if (req->ai_family == AF_INET6
+                         || req->ai_family == AF_UNSPEC)
+                       {
+                         gethosts (AF_INET6, struct in6_addr);
+                         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) || !got_ipv6)))
                        {
-                         /* If we need the canonical name, get it
-                            from the same service as the result.  */
-                         nss_getcanonname_r cfct;
-                         int herrno;
+                         gethosts (AF_INET, struct in_addr);
 
-                         cfct = __nss_lookup_function (nip, "getcanonname_r");
-                         if (cfct != NULL)
+                         if (req->ai_family == AF_INET)
                            {
-                             const size_t max_fqdn_len = 256;
-                             char *buf = alloca (max_fqdn_len);
-                             char *s;
-
-                             if (DL_CALL_FCT (cfct, (at->name ?: name, buf,
-                                                     max_fqdn_len, &s, &rc,
-                                                     &herrno))
-                                 == NSS_STATUS_SUCCESS)
-                               canon = s;
-                             else
-                               /* Set to name now to avoid using
-                                  gethostbyaddr.  */
-                               canon = name;
+                             no_inet6_data = no_data;
+                             inet6_status = status;
                            }
                        }
 
-                     break;
+                     /* 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
+                             && canon == NULL)
+                           {
+                             canonbuf = getcanonname (nip, at, name);
+                             if (canonbuf == NULL)
+                               {
+                                 __resolv_context_enable_inet6
+                                   (res_ctx, res_enable_inet6);
+                                 __resolv_context_put (res_ctx);
+                                 result = -EAI_MEMORY;
+                                 goto free_and_return;
+                               }
+                             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);
                    }
-
-                 /* 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;
                }
 
              if (nss_next_action (nip, status) == NSS_ACTION_RETURN)
@@ -777,28 +932,43 @@ gaih_inet (const char *name, const struct gaih_service *service,
                nip = nip->next;
            }
 
-         _res.options = old_res_options;
+         __resolv_context_enable_inet6 (res_ctx, res_enable_inet6);
+         __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)
-               return -EAI_AGAIN;
+               result = -EAI_AGAIN;
+             else
+               /* We made requests but they turned out no data.  The name
+                  is known, though.  */
+               result = -EAI_NODATA;
 
-             /* We made requests but they turned out no data.  The name
-                is known, though.  */
-             return GAIH_OKIFUNSPEC | -EAI_NODATA;
+             goto free_and_return;
            }
        }
 
     process_list:
       if (at->family == AF_UNSPEC)
-       return GAIH_OKIFUNSPEC | -EAI_NONAME;
+       {
+         result = -EAI_NONAME;
+         goto free_and_return;
+       }
     }
   else
     {
       struct gaih_addrtuple *atr;
-      atr = at = __alloca (sizeof (struct gaih_addrtuple));
+      atr = at = alloca_account (sizeof (struct gaih_addrtuple), alloca_used);
       memset (at, '\0', sizeof (struct gaih_addrtuple));
 
       if (req->ai_family == AF_UNSPEC)
@@ -823,9 +993,6 @@ gaih_inet (const char *name, const struct gaih_service *service,
        }
     }
 
-  if (pai == NULL)
-    return 0;
-
   {
     struct gaih_servtuple *st2;
     struct gaih_addrtuple *at2 = at;
@@ -841,76 +1008,41 @@ gaih_inet (const char *name, const struct gaih_service *service,
        if (at2 == at && (req->ai_flags & AI_CANONNAME) != 0)
          {
            if (canon == NULL)
-             {
-               struct hostent *h = NULL;
-               int herrno;
-               struct hostent th;
-               size_t tmpbuflen = 512;
-               char *tmpbuf = NULL;
+             /* If the canonical name cannot be determined, use
+                the passed in string.  */
+             canon = orig_name;
 
-               do
-                 {
-                   tmpbuf = extend_alloca (tmpbuf, tmpbuflen, tmpbuflen * 2);
-                   rc = __gethostbyaddr_r (at2->addr,
-                                           ((at2->family == AF_INET6)
-                                            ? sizeof (struct in6_addr)
-                                            : sizeof (struct in_addr)),
-                                           at2->family, &th, tmpbuf,
-                                           tmpbuflen, &h, &herrno);
-                 }
-               while (rc == ERANGE && herrno == NETDB_INTERNAL);
-
-               if (rc != 0 && herrno == NETDB_INTERNAL)
-                 {
-                   __set_h_errno (herrno);
-                   return -EAI_SYSTEM;
-                 }
-
-               if (h != NULL)
-                 canon = h->h_name;
+           bool do_idn = req->ai_flags & AI_CANONIDN;
+           if (do_idn)
+             {
+               char *out;
+               int rc = __idna_from_dns_encoding (canon, &out);
+               if (rc == 0)
+                 canon = out;
+               else if (rc == EAI_IDN_ENCODE)
+                 /* Use the punycode name as a fallback.  */
+                 do_idn = false;
                else
                  {
-                   assert (orig_name != NULL);
-                   /* If the canonical name cannot be determined, use
-                      the passed in string.  */
-                   canon = orig_name;
+                   result = -rc;
+                   goto free_and_return;
                  }
              }
-
-#ifdef HAVE_LIBIDN
-           if (req->ai_flags & AI_CANONIDN)
+           if (!do_idn)
              {
-               int idn_flags = 0;
-               if (req->ai_flags & AI_IDN_ALLOW_UNASSIGNED)
-                 idn_flags |= IDNA_ALLOW_UNASSIGNED;
-               if (req->ai_flags & AI_IDN_USE_STD3_ASCII_RULES)
-                 idn_flags |= IDNA_USE_STD3_ASCII_RULES;
-
-               char *out;
-               int rc = __idna_to_unicode_lzlz (canon, &out, idn_flags);
-               if (rc != IDNA_SUCCESS)
+               if (canonbuf != NULL)
+                 /* We already allocated the string using malloc, but
+                    the buffer is now owned by canon.  */
+                 canonbuf = NULL;
+               else
                  {
-                   if (rc == IDNA_MALLOC_ERROR)
-                     return -EAI_MEMORY;
-                   if (rc == IDNA_DLOPEN_ERROR)
-                     return -EAI_SYSTEM;
-                   return -EAI_IDN_ENCODE;
+                   canon = __strdup (canon);
+                   if (canon == NULL)
+                     {
+                       result = -EAI_MEMORY;
+                       goto free_and_return;
+                     }
                  }
-               /* In case the output string is the same as the input
-                  string no new string has been allocated.  Otherwise
-                  make a copy.  */
-               if (out == canon)
-                 goto make_copy;
-             }
-           else
-#endif
-             {
-#ifdef HAVE_LIBIDN
-             make_copy:
-#endif
-               canon = strdup (canon);
-               if (canon == NULL)
-                 return -EAI_MEMORY;
              }
          }
 
@@ -937,7 +1069,8 @@ gaih_inet (const char *name, const struct gaih_service *service,
            if (ai == NULL)
              {
                free ((char *) canon);
-               return -EAI_MEMORY;
+               result = -EAI_MEMORY;
+               goto free_and_return;
              }
 
            ai->ai_flags = req->ai_flags;
@@ -990,15 +1123,24 @@ gaih_inet (const char *name, const struct gaih_service *service,
        at2 = at2->next;
       }
   }
-  return 0;
+
+ free_and_return:
+  if (malloc_name)
+    free ((char *) name);
+  free (addrmem);
+  free (canonbuf);
+
+  return result;
 }
 
 
 struct sort_result
 {
   struct addrinfo *dest_addr;
-  struct sockaddr_storage source_addr;
-  size_t service_order;
+  /* Using sockaddr_storage is for now overkill.  We only support IPv4
+     and IPv6 so far.  If this changes at some point we can adjust the
+     type here.  */
+  struct sockaddr_in6 source_addr;
   uint8_t source_addr_len;
   bool got_source_addr;
   uint8_t source_addr_flags;
@@ -1034,10 +1176,6 @@ static const struct scopeentry
     /* Link-local addresses: scope 2.  */
     { { { 169, 254, 0, 0 } }, htonl_c (0xffff0000), 2 },
     { { { 127, 0, 0, 0 } }, htonl_c (0xff000000), 2 },
-    /* Site-local addresses: scope 5.  */
-    { { { 10, 0, 0, 0 } }, htonl_c (0xff000000), 5 },
-    { { { 172, 16, 0, 0 } }, htonl_c (0xfff00000), 5 },
-    { { { 192, 168, 0, 0 } }, htonl_c (0xffff0000), 5 },
     /* Default: scope 14.  */
     { { { 0, 0, 0, 0 } }, htonl_c (0x00000000), 14 }
   };
@@ -1047,16 +1185,17 @@ static const struct scopeentry *scopes;
 
 
 static int
-get_scope (const struct sockaddr_storage *ss)
+get_scope (const struct sockaddr_in6 *in6)
 {
   int scope;
-  if (ss->ss_family == PF_INET6)
+  if (in6->sin6_family == PF_INET6)
     {
-      const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *) ss;
-
       if (! IN6_IS_ADDR_MULTICAST (&in6->sin6_addr))
        {
-         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr))
+         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr)
+             /* RFC 4291 2.5.3 says that the loopback address is to be
+                treated like a link-local address.  */
+             || IN6_IS_ADDR_LOOPBACK (&in6->sin6_addr))
            scope = 2;
          else if (IN6_IS_ADDR_SITELOCAL (&in6->sin6_addr))
            scope = 5;
@@ -1067,9 +1206,9 @@ get_scope (const struct sockaddr_storage *ss)
       else
        scope = in6->sin6_addr.s6_addr[1] & 0xf;
     }
-  else if (ss->ss_family == PF_INET)
+  else if (in6->sin6_family == PF_INET)
     {
-      const struct sockaddr_in *in = (const struct sockaddr_in *) ss;
+      const struct sockaddr_in *in = (const struct sockaddr_in *) in6;
 
       size_t cnt = 0;
       while (1)
@@ -1105,22 +1244,22 @@ static const struct prefixentry *labels;
 static const struct prefixentry default_labels[] =
   {
     /* See RFC 3484 for the details.  */
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } },
-      128, 0 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      16, 2 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      96, 3 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } } },
-      96, 4 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }
+      }, 128, 0 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 16, 2 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 3 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 4 },
     /* The next two entries differ from RFC 3484.  We need to treat
        IPv6 site-local addresses special because they are never NATed,
        unlike site-locale IPv4 addresses.  If this would not happen, on
@@ -1128,23 +1267,23 @@ static const struct prefixentry default_labels[] =
        sorting would prefer the IPv6 site-local addresses, causing
        unnecessary delays when trying to connect to a global IPv6 address
        through a site-local IPv6 address.  */
-    { { .in6_u
-       = { .u6_addr8 = { 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      10, 5 },
-    { { .in6_u
-       = { .u6_addr8 = { 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      7, 6 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 10, 5 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 7, 6 },
     /* Additional rule for Teredo tunnels.  */
-    { { .in6_u
-       = { .u6_addr8 = { 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      32, 7 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      0, 1 }
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 32, 7 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 0, 1 }
   };
 
 
@@ -1155,61 +1294,52 @@ static const struct prefixentry *precedence;
 static const struct prefixentry default_precedence[] =
   {
     /* See RFC 3484 for the details.  */
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } },
-      128, 50 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      16, 30 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      96, 20 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } } },
-      96, 10 },
-    { { .in6_u
-       = { .u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
-      0, 40 }
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }
+      }, 128, 50 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 16, 30 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 20 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 10 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 0, 40 }
   };
 
 
 static int
-match_prefix (const struct sockaddr_storage *ss,
+match_prefix (const struct sockaddr_in6 *in6,
              const struct prefixentry *list, int default_val)
 {
   int idx;
   struct sockaddr_in6 in6_mem;
-  const struct sockaddr_in6 *in6;
 
-  if (ss->ss_family == PF_INET6)
-    in6 = (const struct sockaddr_in6 *) ss;
-  else if (ss->ss_family == PF_INET)
+  if (in6->sin6_family == PF_INET)
     {
-      const struct sockaddr_in *in = (const struct sockaddr_in *) ss;
+      const struct sockaddr_in *in = (const struct sockaddr_in *) in6;
 
-      /* Convert to IPv6 address.  */
+      /* Construct a V4-to-6 mapped address.  */
       in6_mem.sin6_family = PF_INET6;
       in6_mem.sin6_port = in->sin_port;
       in6_mem.sin6_flowinfo = 0;
-      if (in->sin_addr.s_addr == htonl (0x7f000001))
-       in6_mem.sin6_addr = (struct in6_addr) IN6ADDR_LOOPBACK_INIT;
-      else
-       {
-         /* Construct a V4-to-6 mapped address.  */
-         memset (&in6_mem.sin6_addr, '\0', sizeof (in6_mem.sin6_addr));
-         in6_mem.sin6_addr.s6_addr16[5] = 0xffff;
-         in6_mem.sin6_addr.s6_addr32[3] = in->sin_addr.s_addr;
-         in6_mem.sin6_scope_id = 0;
-       }
+      memset (&in6_mem.sin6_addr, '\0', sizeof (in6_mem.sin6_addr));
+      in6_mem.sin6_addr.s6_addr16[5] = 0xffff;
+      in6_mem.sin6_addr.s6_addr32[3] = in->sin_addr.s_addr;
+      in6_mem.sin6_scope_id = 0;
 
       in6 = &in6_mem;
     }
-  else
+  else if (in6->sin6_family != PF_INET6)
     return default_val;
 
   for (idx = 0; ; ++idx)
@@ -1241,18 +1371,18 @@ match_prefix (const struct sockaddr_storage *ss,
 
 
 static int
-get_label (const struct sockaddr_storage *ss)
+get_label (const struct sockaddr_in6 *in6)
 {
   /* XXX What is a good default value?  */
-  return match_prefix (ss, labels, INT_MAX);
+  return match_prefix (in6, labels, INT_MAX);
 }
 
 
 static int
-get_precedence (const struct sockaddr_storage *ss)
+get_precedence (const struct sockaddr_in6 *in6)
 {
   /* XXX What is a good default value?  */
-  return match_prefix (ss, precedence, 0);
+  return match_prefix (in6, precedence, 0);
 }
 
 
@@ -1272,9 +1402,11 @@ fls (uint32_t a)
 static int
 rfc3484_sort (const void *p1, const void *p2, void *arg)
 {
-  const struct sort_result *a1 = (const struct sort_result *) p1;
-  const struct sort_result *a2 = (const struct sort_result *) p2;
+  const size_t idx1 = *(const size_t *) p1;
+  const size_t idx2 = *(const size_t *) p2;
   struct sort_result_combo *src = (struct sort_result_combo *) arg;
+  struct sort_result *a1 = &src->results[idx1];
+  struct sort_result *a2 = &src->results[idx2];
 
   /* Rule 1: Avoid unusable destinations.
      We have the got_source_addr flag set if the destination is reachable.  */
@@ -1287,10 +1419,10 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
   /* Rule 2: Prefer matching scope.  Only interesting if both
      destination addresses are IPv6.  */
   int a1_dst_scope
-    = get_scope ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+    = get_scope ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
 
   int a2_dst_scope
-    = get_scope ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+    = get_scope ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
 
   if (a1->got_source_addr)
     {
@@ -1330,11 +1462,11 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
   if (a1->got_source_addr)
     {
       int a1_dst_label
-       = get_label ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+       = get_label ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
       int a1_src_label = get_label (&a1->source_addr);
 
       int a2_dst_label
-       = get_label ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+       = get_label ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
       int a2_src_label = get_label (&a2->source_addr);
 
       if (a1_dst_label == a1_src_label && a2_dst_label != a2_src_label)
@@ -1346,9 +1478,9 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
 
   /* Rule 6: Prefer higher precedence.  */
   int a1_prec
-    = get_precedence ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+    = get_precedence ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
   int a2_prec
-    = get_precedence ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+    = get_precedence ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
 
   if (a1_prec > a2_prec)
     return -1;
@@ -1364,27 +1496,54 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
         (most?) cases.  */
       if (a1->index != a2->index)
        {
-         if (a1->native == -1 || a2->native == -1)
+         int a1_native = a1->native;
+         int a2_native = a2->native;
+
+         if (a1_native == -1 || a2_native == -1)
            {
-             /* If we do not have the information use 'native' as the
-                default.  */
-             int a1_native = 0;
-             int a2_native = 0;
-             __check_native (a1->index, &a1_native, a2->index, &a2_native);
+             uint32_t a1_index;
+             if (a1_native == -1)
+               {
+                 /* If we do not have the information use 'native' as
+                    the default.  */
+                 a1_native = 0;
+                 a1_index = a1->index;
+               }
+             else
+               a1_index = 0xffffffffu;
+
+             uint32_t a2_index;
+             if (a2_native == -1)
+               {
+                 /* If we do not have the information use 'native' as
+                    the default.  */
+                 a2_native = 0;
+                 a2_index = a2->index;
+               }
+             else
+               a2_index = 0xffffffffu;
+
+             __check_native (a1_index, &a1_native, a2_index, &a2_native);
 
              /* Fill in the results in all the records.  */
              for (int i = 0; i < src->nresults; ++i)
-               {
-                 if (a1->native == -1 && src->results[i].index == a1->index)
+               if (a1_index != -1 && src->results[i].index == a1_index)
+                 {
+                   assert (src->results[i].native == -1
+                           || src->results[i].native == a1_native);
                    src->results[i].native = a1_native;
-                 if (a2->native == -1 && src->results[i].index == a2->index)
+                 }
+               else if (a2_index != -1 && src->results[i].index == a2_index)
+                 {
+                   assert (src->results[i].native == -1
+                           || src->results[i].native == a2_native);
                    src->results[i].native = a2_native;
-               }
+                 }
            }
 
-         if (a1->native && !a2->native)
+         if (a1_native && !a2_native)
            return -1;
-         if (!a1->native && a2->native)
+         if (!a1_native && a2_native)
            return 1;
        }
     }
@@ -1406,8 +1565,8 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
 
       if (a1->dest_addr->ai_family == PF_INET)
        {
-         assert (a1->source_addr.ss_family == PF_INET);
-         assert (a2->source_addr.ss_family == PF_INET);
+         assert (a1->source_addr.sin6_family == PF_INET);
+         assert (a2->source_addr.sin6_family == PF_INET);
 
          /* Outside of subnets, as defined by the network masks,
             common address prefixes for IPv4 addresses make no sense.
@@ -1437,8 +1596,8 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
        }
       else if (a1->dest_addr->ai_family == PF_INET6)
        {
-         assert (a1->source_addr.ss_family == PF_INET6);
-         assert (a2->source_addr.ss_family == PF_INET6);
+         assert (a1->source_addr.sin6_family == PF_INET6);
+         assert (a2->source_addr.sin6_family == PF_INET6);
 
          struct sockaddr_in6 *in1_dst;
          struct sockaddr_in6 *in1_src;
@@ -1478,7 +1637,7 @@ rfc3484_sort (const void *p1, const void *p2, void *arg)
      compare with the value indicating the order in which the entries
      have been received from the services.  NB: no two entries can have
      the same order so the test will never return zero.  */
-  return a1->service_order < a2->service_order ? -1 : 1;
+  return idx1 < idx2 ? -1 : 1;
 }
 
 
@@ -1504,8 +1663,41 @@ static int gaiconf_reload_flag;
 static int gaiconf_reload_flag_ever_set;
 
 /* Last modification time.  */
+#ifdef _STATBUF_ST_NSEC
+
 static struct timespec gaiconf_mtime;
 
+static inline void
+save_gaiconf_mtime (const struct stat64 *st)
+{
+  gaiconf_mtime = st->st_mtim;
+}
+
+static inline bool
+check_gaiconf_mtime (const struct stat64 *st)
+{
+  return (st->st_mtim.tv_sec == gaiconf_mtime.tv_sec
+          && st->st_mtim.tv_nsec == gaiconf_mtime.tv_nsec);
+}
+
+#else
+
+static time_t gaiconf_mtime;
+
+static inline void
+save_gaiconf_mtime (const struct stat64 *st)
+{
+  gaiconf_mtime = st->st_mtime;
+}
+
+static inline bool
+check_gaiconf_mtime (const struct stat64 *st)
+{
+  return st->st_mtime == gaiconf_mtime;
+}
+
+#endif
+
 
 libc_freeres_fn(fini)
 {
@@ -1611,7 +1803,7 @@ gaiconf_init (void)
   size_t nscopelist = 0;
   bool scopelist_nullbits = false;
 
-  FILE *fp = fopen (GAICONF_FNAME, "rc");
+  FILE *fp = fopen (GAICONF_FNAME, "rce");
   if (fp != NULL)
     {
       struct stat64 st;
@@ -1749,6 +1941,7 @@ gaiconf_init (void)
                    *cp++ = '\0';
                  if (inet_pton (AF_INET6, val1, &prefix))
                    {
+                     bits = 128;
                      if (IN6_IS_ADDR_V4MAPPED (&prefix)
                          && (cp == NULL
                              || (bits = strtoul (cp, &endp, 10)) != ULONG_MAX
@@ -1946,7 +2139,7 @@ gaiconf_init (void)
       if (oldscope != default_scopes)
        free ((void *) oldscope);
 
-      gaiconf_mtime = st.st_mtim;
+      save_gaiconf_mtime (&st);
     }
   else
     {
@@ -1968,7 +2161,7 @@ gaiconf_reload (void)
 {
   struct stat64 st;
   if (__xstat64 (_STAT_VER, GAICONF_FNAME, &st) != 0
-      || memcmp (&st.st_mtim, &gaiconf_mtime, sizeof (gaiconf_mtime)) != 0)
+      || !check_gaiconf_mtime (&st))
     gaiconf_init ();
 }
 
@@ -1997,10 +2190,7 @@ getaddrinfo (const char *name, const char *service,
 
   if (hints->ai_flags
       & ~(AI_PASSIVE|AI_CANONNAME|AI_NUMERICHOST|AI_ADDRCONFIG|AI_V4MAPPED
-#ifdef HAVE_LIBIDN
-         |AI_IDN|AI_CANONIDN|AI_IDN_ALLOW_UNASSIGNED
-         |AI_IDN_USE_STD3_ASCII_RULES
-#endif
+         |AI_IDN|AI_CANONIDN|DEPRECATED_AI_IDN
          |AI_NUMERICSERV|AI_ALL))
     return EAI_BADFLAGS;
 
@@ -2011,20 +2201,23 @@ getaddrinfo (const char *name, const char *service,
   size_t in6ailen = 0;
   bool seen_ipv4 = false;
   bool seen_ipv6 = false;
-  /* We might need information about what interfaces are available.
-     Also determine whether we have IPv4 or IPv6 interfaces or both.  We
-     cannot cache the results since new interfaces could be added at
-     any time.  */
-  __check_pf (&seen_ipv4, &seen_ipv6, &in6ai, &in6ailen);
+  bool check_pf_called = false;
 
   if (hints->ai_flags & AI_ADDRCONFIG)
     {
+      /* We might need information about what interfaces are available.
+        Also determine whether we have IPv4 or IPv6 interfaces or both.  We
+        cannot cache the results since new interfaces could be added at
+        any time.  */
+      __check_pf (&seen_ipv4, &seen_ipv6, &in6ai, &in6ailen);
+      check_pf_called = true;
+
       /* Now make a decision on what we return, if anything.  */
       if (hints->ai_family == PF_UNSPEC && (seen_ipv4 || seen_ipv6))
        {
          /* If we haven't seen both IPv4 and IPv6 interfaces we can
             narrow down the search.  */
-         if (! seen_ipv4 || ! seen_ipv6)
+         if ((! seen_ipv4 || ! seen_ipv6) && (seen_ipv4 || seen_ipv6))
            {
              local_hints = *hints;
              local_hints.ai_family = seen_ipv4 ? PF_INET : PF_INET6;
@@ -2035,7 +2228,7 @@ getaddrinfo (const char *name, const char *service,
               || (hints->ai_family == PF_INET6 && ! seen_ipv6))
        {
          /* We cannot possibly return a valid answer.  */
-         free (in6ai);
+         __free_in6ai (in6ai);
          return EAI_NONAME;
        }
     }
@@ -2049,7 +2242,7 @@ getaddrinfo (const char *name, const char *service,
        {
          if (hints->ai_flags & AI_NUMERICSERV)
            {
-             free (in6ai);
+             __free_in6ai (in6ai);
              return EAI_NONAME;
            }
 
@@ -2061,34 +2254,33 @@ getaddrinfo (const char *name, const char *service,
   else
     pservice = NULL;
 
-  struct addrinfo **end;
-  if (pai)
-    end = &p;
-  else
-    end = NULL;
+  struct addrinfo **end = &p;
 
   unsigned int naddrs = 0;
   if (hints->ai_family == AF_UNSPEC || hints->ai_family == AF_INET
       || hints->ai_family == AF_INET6)
     {
-      last_i = gaih_inet (name, pservice, hints, end, &naddrs);
+      struct scratch_buffer tmpbuf;
+      scratch_buffer_init (&tmpbuf);
+      last_i = gaih_inet (name, pservice, hints, end, &naddrs, &tmpbuf);
+      scratch_buffer_free (&tmpbuf);
+
       if (last_i != 0)
        {
          freeaddrinfo (p);
-         free (in6ai);
+         __free_in6ai (in6ai);
 
-         return -(last_i & GAIH_EAI);
+         return -last_i;
+       }
+      while (*end)
+       {
+         end = &((*end)->ai_next);
+         ++nresults;
        }
-      if (end)
-       while (*end)
-         {
-           end = &((*end)->ai_next);
-           ++nresults;
-         }
     }
   else
     {
-      free (in6ai);
+      __free_in6ai (in6ai);
       return EAI_FAMILY;
     }
 
@@ -2099,10 +2291,32 @@ getaddrinfo (const char *name, const char *service,
       __typeof (once) old_once = once;
       __libc_once (once, gaiconf_init);
       /* Sort results according to RFC 3484.  */
-      struct sort_result results[nresults];
+      struct sort_result *results;
+      size_t *order;
       struct addrinfo *q;
       struct addrinfo *last = NULL;
       char *canonname = NULL;
+      bool malloc_results;
+      size_t alloc_size = nresults * (sizeof (*results) + sizeof (size_t));
+
+      malloc_results
+       = !__libc_use_alloca (alloc_size);
+      if (malloc_results)
+       {
+         results = malloc (alloc_size);
+         if (results == NULL)
+           {
+             __free_in6ai (in6ai);
+             return EAI_MEMORY;
+           }
+       }
+      else
+       results = alloca (alloc_size);
+      order = (size_t *) (results + nresults);
+
+      /* Now we definitely need the interface information.  */
+      if (! check_pf_called)
+       __check_pf (&seen_ipv4, &seen_ipv6, &in6ai, &in6ailen);
 
       /* If we have information about deprecated and temporary addresses
         sort the array now.  */
@@ -2115,8 +2329,8 @@ getaddrinfo (const char *name, const char *service,
       for (i = 0, q = p; q != NULL; ++i, last = q, q = q->ai_next)
        {
          results[i].dest_addr = q;
-         results[i].service_order = i;
          results[i].native = -1;
+         order[i] = i;
 
          /* If we just looked up the address for a different
             protocol, reuse the result.  */
@@ -2146,9 +2360,9 @@ getaddrinfo (const char *name, const char *service,
                {
                  if (fd != -1)
                  close_retry:
-                   close_not_cancel_no_status (fd);
+                   __close_nocancel_nostatus (fd);
                  af = q->ai_family;
-                 fd = __socket (af, SOCK_DGRAM, IPPROTO_IP);
+                 fd = __socket (af, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_IP);
                }
              else
                {
@@ -2180,7 +2394,14 @@ getaddrinfo (const char *name, const char *service,
                          tmp.addr[0] = 0;
                          tmp.addr[1] = 0;
                          tmp.addr[2] = htonl (0xffff);
-                         tmp.addr[3] = sinp->sin_addr.s_addr;
+                         /* Special case for lo interface, the source address
+                            being possibly different than the interface
+                            address. */
+                         if ((ntohl(sinp->sin_addr.s_addr) & 0xff000000)
+                             == 0x7f000000)
+                           tmp.addr[3] = htonl(0x7f000001);
+                         else
+                           tmp.addr[3] = sinp->sin_addr.s_addr;
                        }
                      else
                        {
@@ -2242,36 +2463,39 @@ getaddrinfo (const char *name, const char *service,
        }
 
       if (fd != -1)
-       close_not_cancel_no_status (fd);
+       __close_nocancel_nostatus (fd);
 
       /* We got all the source addresses we can get, now sort using
         the information.  */
       struct sort_result_combo src
        = { .results = results, .nresults = nresults };
-      if (__builtin_expect (gaiconf_reload_flag_ever_set, 0))
+      if (__glibc_unlikely (gaiconf_reload_flag_ever_set))
        {
          __libc_lock_define_initialized (static, lock);
 
          __libc_lock_lock (lock);
-         if (old_once && gaiconf_reload_flag)
+         if (__libc_once_get (old_once) && gaiconf_reload_flag)
            gaiconf_reload ();
-         qsort_r (results, nresults, sizeof (results[0]), rfc3484_sort, &src);
+         __qsort_r (order, nresults, sizeof (order[0]), rfc3484_sort, &src);
          __libc_lock_unlock (lock);
        }
       else
-       qsort_r (results, nresults, sizeof (results[0]), rfc3484_sort, &src);
+       __qsort_r (order, nresults, sizeof (order[0]), rfc3484_sort, &src);
 
       /* Queue the results up as they come out of sorting.  */
-      q = p = results[0].dest_addr;
+      q = p = results[order[0]].dest_addr;
       for (i = 1; i < nresults; ++i)
-       q = q->ai_next = results[i].dest_addr;
+       q = q->ai_next = results[order[i]].dest_addr;
       q->ai_next = NULL;
 
       /* Fill in the canonical name into the new first entry.  */
       p->ai_canonname = canonname;
+
+      if (malloc_results)
+       free (results);
     }
 
-  free (in6ai);
+  __free_in6ai (in6ai);
 
   if (p)
     {
@@ -2279,14 +2503,11 @@ getaddrinfo (const char *name, const char *service,
       return 0;
     }
 
-  if (pai == NULL && last_i == 0)
-    return 0;
-
-  return last_i ? -(last_i & GAIH_EAI) : EAI_NONAME;
+  return last_i ? -last_i : EAI_NONAME;
 }
 libc_hidden_def (getaddrinfo)
 
-static_link_warning (getaddrinfo)
+nss_interface_function (getaddrinfo)
 
 void
 freeaddrinfo (struct addrinfo *ai)