]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - resolv/nss_dns/dns-host.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / resolv / nss_dns / dns-host.c
index f8345bb5912176dbb0636472ccd7ea1236f5d52e..d5210c14799369894747d1fd3dd9d1619627cc50 100644 (file)
@@ -1,21 +1,20 @@
-/* Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.
+/* Copyright (C) 1996-2019 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Extended from original form by Ulrich Drepper <drepper@cygnus.com>, 1996.
 
    The GNU C Library is free software; you can redistribute it and/or
-   modify it under the terms of the GNU Library General Public License as
-   published by the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
+   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
-   Library General Public License for more details.
+   Lesser General Public License for more details.
 
-   You should have received a copy of the GNU Library General Public
-   License along with the GNU C Library; see the file COPYING.LIB.  If not,
-   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-   Boston, MA 02111-1307, USA.  */
+   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/>.  */
 
 /* Parts of this file are plain copies of the file `gethtnamadr.c' from
    the bind package and it has the following copyright.  */
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
  * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
@@ -75,6 +70,7 @@
  * --Copyright--
  */
 
+#include <assert.h>
 #include <ctype.h>
 #include <errno.h>
 #include <netdb.h>
 #include <stdlib.h>
 #include <stddef.h>
 #include <string.h>
-#include <sys/syslog.h>
 
 #include "nsswitch.h"
+#include <arpa/nameser.h>
 
-/* Get implementation for some internal functions.  */
+#include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
+
+/* Get implementations of some internal functions.  */
 #include <resolv/mapv4v6addr.h>
 #include <resolv/mapv4v6hostent.h>
 
-/* Maximum number of aliases we allow.  */
-#define MAX_NR_ALIASES 48
-#define MAX_NR_ADDRS   48
+#define RESOLVSORT
 
-#if PACKETSZ > 1024
+#if PACKETSZ > 65536
 # define MAXPACKET     PACKETSZ
 #else
-# define MAXPACKET     1024
+# define MAXPACKET     65536
 #endif
 /* As per RFC 1034 and 1035 a host name cannot exceed 255 octets in length.  */
 #ifdef MAXHOSTNAMELEN
 #endif
 #define MAXHOSTNAMELEN 256
 
-static const char AskedForGot[] = "\
-gethostby*.getanswer: asked for \"%s\", got \"%s\"";
-
-
 /* We need this time later.  */
 typedef union querybuf
 {
@@ -116,19 +109,82 @@ typedef union querybuf
   u_char buf[MAXPACKET];
 } querybuf;
 
-
-static enum nss_status getanswer_r (const querybuf *answer, int anslen,
+static enum nss_status getanswer_r (struct resolv_context *ctx,
+                                   const querybuf *answer, int anslen,
                                    const char *qname, int qtype,
                                    struct hostent *result, char *buffer,
-                                   size_t buflen, int *h_errnop);
+                                   size_t buflen, int *errnop, int *h_errnop,
+                                   int map, int32_t *ttlp, char **canonp);
+
+static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
+                                      const querybuf *answer2, int anslen2,
+                                      const char *qname,
+                                      struct gaih_addrtuple **pat,
+                                      char *buffer, size_t buflen,
+                                      int *errnop, int *h_errnop,
+                                      int32_t *ttlp);
+
+static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
+                                              const char *name, int af,
+                                              struct hostent *result,
+                                              char *buffer, size_t buflen,
+                                              int *errnop, int *h_errnop,
+                                              int32_t *ttlp,
+                                              char **canonp);
+
+/* Return the expected RDATA length for an address record type (A or
+   AAAA).  */
+static int
+rrtype_to_rdata_length (int type)
+{
+  switch (type)
+    {
+    case T_A:
+      return INADDRSZ;
+    case T_AAAA:
+      return IN6ADDRSZ;
+    default:
+      return -1;
+    }
+}
+
 
 enum nss_status
-_nss_dns_gethostbyname2_r (const char *name, int af, struct hostent *result,
-                          char *buffer, size_t buflen, int *h_errnop)
+_nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
+                          char *buffer, size_t buflen, int *errnop,
+                          int *h_errnop, int32_t *ttlp, char **canonp)
 {
-  querybuf host_buffer;
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
+  enum nss_status status = gethostbyname3_context
+    (ctx, name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp);
+  __resolv_context_put (ctx);
+  return status;
+}
+
+static enum nss_status
+gethostbyname3_context (struct resolv_context *ctx,
+                       const char *name, int af, struct hostent *result,
+                       char *buffer, size_t buflen, int *errnop,
+                       int *h_errnop, int32_t *ttlp, char **canonp)
+{
+  union
+  {
+    querybuf *buf;
+    u_char *ptr;
+  } host_buffer;
+  querybuf *orig_host_buffer;
+  char tmp[NS_MAXDNAME];
   int size, type, n;
   const char *cp;
+  int map = 0;
+  int olderr = errno;
+  enum nss_status status;
 
   switch (af) {
   case AF_INET:
@@ -140,8 +196,8 @@ _nss_dns_gethostbyname2_r (const char *name, int af, struct hostent *result,
     type = T_AAAA;
     break;
   default:
-    *h_errnop = NETDB_INTERNAL;
-    __set_errno (EAFNOSUPPORT);
+    *h_errnop = NO_DATA;
+    *errnop = EAFNOSUPPORT;
     return NSS_STATUS_UNAVAIL;
   }
 
@@ -153,45 +209,211 @@ _nss_dns_gethostbyname2_r (const char *name, int af, struct hostent *result,
    * this is also done in res_query() since we are not the only
    * function that looks up host names.
    */
-  if (strchr (name, '.') == NULL && (cp = __hostalias (name)) != NULL)
+  if (strchr (name, '.') == NULL
+      && (cp = __res_context_hostalias (ctx, name, tmp, sizeof (tmp))) != NULL)
     name = cp;
 
-  n = res_search (name, C_IN, type, host_buffer.buf, sizeof (host_buffer.buf));
+  host_buffer.buf = orig_host_buffer = (querybuf *) alloca (1024);
+
+  n = __res_context_search (ctx, name, C_IN, type, host_buffer.buf->buf,
+                           1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0)
     {
+      switch (errno)
+       {
+       case ESRCH:
+         status = NSS_STATUS_TRYAGAIN;
+         h_errno = TRY_AGAIN;
+         break;
+       /* System has run out of file descriptors.  */
+       case EMFILE:
+       case ENFILE:
+         h_errno = NETDB_INTERNAL;
+         /* Fall through.  */
+       case ECONNREFUSED:
+       case ETIMEDOUT:
+         status = NSS_STATUS_UNAVAIL;
+         break;
+       default:
+         status = NSS_STATUS_NOTFOUND;
+         break;
+       }
       *h_errnop = h_errno;
-      return errno == ECONNREFUSED ? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
+      if (h_errno == TRY_AGAIN)
+       *errnop = EAGAIN;
+      else
+       __set_errno (olderr);
+
+      /* If we are looking for an IPv6 address and mapping is enabled
+        by having the RES_USE_INET6 bit in _res.options set, we try
+        another lookup.  */
+      if (af == AF_INET6 && res_use_inet6 ())
+       n = __res_context_search (ctx, name, C_IN, T_A, host_buffer.buf->buf,
+                                 host_buffer.buf != orig_host_buffer
+                                 ? MAXPACKET : 1024, &host_buffer.ptr,
+                                 NULL, NULL, NULL, NULL);
+
+      if (n < 0)
+       {
+         if (host_buffer.buf != orig_host_buffer)
+           free (host_buffer.buf);
+         return status;
+       }
+
+      map = 1;
+
+      result->h_addrtype = AF_INET;
+      result->h_length = INADDRSZ;
     }
 
-  return getanswer_r (&host_buffer, n, name, type, result, buffer, buflen,
-                     h_errnop);
+  status = getanswer_r
+    (ctx, host_buffer.buf, n, name, type, result, buffer, buflen,
+     errnop, h_errnop, map, ttlp, canonp);
+  if (host_buffer.buf != orig_host_buffer)
+    free (host_buffer.buf);
+  return status;
+}
+
+enum nss_status
+_nss_dns_gethostbyname2_r (const char *name, int af, struct hostent *result,
+                          char *buffer, size_t buflen, int *errnop,
+                          int *h_errnop)
+{
+  return _nss_dns_gethostbyname3_r (name, af, result, buffer, buflen, errnop,
+                                   h_errnop, NULL, NULL);
 }
 
 
 enum nss_status
 _nss_dns_gethostbyname_r (const char *name, struct hostent *result,
-                         char *buffer, size_t buflen, int *h_errnop)
+                         char *buffer, size_t buflen, int *errnop,
+                         int *h_errnop)
 {
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
   enum nss_status status = NSS_STATUS_NOTFOUND;
-
-  if (_res.options & RES_USE_INET6)
-    status = _nss_dns_gethostbyname2_r (name, AF_INET6, result, buffer,
-                                       buflen, h_errnop);
+  if (res_use_inet6 ())
+    status = gethostbyname3_context (ctx, name, AF_INET6, result, buffer,
+                                    buflen, errnop, h_errnop, NULL, NULL);
   if (status == NSS_STATUS_NOTFOUND)
-    status = _nss_dns_gethostbyname2_r (name, AF_INET, result, buffer,
-                                       buflen, h_errnop);
+    status = gethostbyname3_context (ctx, name, AF_INET, result, buffer,
+                                    buflen, errnop, h_errnop, NULL, NULL);
+  __resolv_context_put (ctx);
+  return status;
+}
 
+
+enum nss_status
+_nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
+                          char *buffer, size_t buflen, int *errnop,
+                          int *herrnop, int32_t *ttlp)
+{
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *herrnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
+
+  /*
+   * if there aren't any dots, it could be a user-level alias.
+   * this is also done in res_query() since we are not the only
+   * function that looks up host names.
+   */
+  if (strchr (name, '.') == NULL)
+    {
+      char *tmp = alloca (NS_MAXDNAME);
+      const char *cp = __res_context_hostalias (ctx, name, tmp, NS_MAXDNAME);
+      if (cp != NULL)
+       name = cp;
+    }
+
+  union
+  {
+    querybuf *buf;
+    u_char *ptr;
+  } host_buffer;
+  querybuf *orig_host_buffer;
+  host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);
+  u_char *ans2p = NULL;
+  int nans2p = 0;
+  int resplen2 = 0;
+  int ans2p_malloced = 0;
+
+  int olderr = errno;
+  enum nss_status status;
+  int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
+                               host_buffer.buf->buf, 2048, &host_buffer.ptr,
+                               &ans2p, &nans2p, &resplen2, &ans2p_malloced);
+  if (n >= 0)
+    {
+      status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
+                              resplen2, name, pat, buffer, buflen,
+                              errnop, herrnop, ttlp);
+    }
+  else
+    {
+      switch (errno)
+       {
+       case ESRCH:
+         status = NSS_STATUS_TRYAGAIN;
+         h_errno = TRY_AGAIN;
+         break;
+       /* System has run out of file descriptors.  */
+       case EMFILE:
+       case ENFILE:
+         h_errno = NETDB_INTERNAL;
+         /* Fall through.  */
+       case ECONNREFUSED:
+       case ETIMEDOUT:
+         status = NSS_STATUS_UNAVAIL;
+         break;
+       default:
+         status = NSS_STATUS_NOTFOUND;
+         break;
+       }
+
+      *herrnop = h_errno;
+      if (h_errno == TRY_AGAIN)
+       *errnop = EAGAIN;
+      else
+       __set_errno (olderr);
+    }
+
+  /* Check whether ans2p was separately allocated.  */
+  if (ans2p_malloced)
+    free (ans2p);
+
+  if (host_buffer.buf != orig_host_buffer)
+    free (host_buffer.buf);
+
+  __resolv_context_put (ctx);
   return status;
 }
 
 
+extern enum nss_status _nss_dns_gethostbyaddr2_r (const void *addr,
+                                                 socklen_t len, int af,
+                                                 struct hostent *result,
+                                                 char *buffer, size_t buflen,
+                                                 int *errnop, int *h_errnop,
+                                                 int32_t *ttlp);
+hidden_proto (_nss_dns_gethostbyaddr2_r)
+
 enum nss_status
-_nss_dns_gethostbyaddr_r (const char *addr, int len, int af,
-                         struct hostent *result, char *buffer, size_t buflen,
-                         int *h_errnop)
+_nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
+                          struct hostent *result, char *buffer, size_t buflen,
+                          int *errnop, int *h_errnop, int32_t *ttlp)
 {
   static const u_char mapped[] = { 0,0, 0,0, 0,0, 0,0, 0,0, 0xff,0xff };
   static const u_char tunnelled[] = { 0,0, 0,0, 0,0, 0,0, 0,0, 0,0 };
+  static const u_char v6local[] = { 0,0, 0,1 };
   const u_char *uaddr = (const u_char *)addr;
   struct host_data
   {
@@ -200,13 +422,42 @@ _nss_dns_gethostbyaddr_r (const char *addr, int len, int af,
     char *h_addr_ptrs[MAX_NR_ADDRS + 1];
     char linebuffer[0];
   } *host_data = (struct host_data *) buffer;
-  querybuf host_buffer;
-  char qbuf[MAXDNAME+1], *qp;
-  int size, n, status;
+  union
+  {
+    querybuf *buf;
+    u_char *ptr;
+  } host_buffer;
+  querybuf *orig_host_buffer;
+  char qbuf[MAXDNAME+1], *qp = NULL;
+  size_t size;
+  int n, status;
+  int olderr = errno;
+
+ uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct host_data);
+ buffer += pad;
+ buflen = buflen > pad ? buflen - pad : 0;
+
+ if (__glibc_unlikely (buflen < sizeof (struct host_data)))
+   {
+     *errnop = ERANGE;
+     *h_errnop = NETDB_INTERNAL;
+     return NSS_STATUS_TRYAGAIN;
+   }
+
+ host_data = (struct host_data *) buffer;
+
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
 
-  if (af == AF_INET6 && len == IN6ADDRSZ &&
-      (memcmp (uaddr, mapped, sizeof mapped) == 0
-       || memcmp (uaddr, tunnelled, sizeof tunnelled) == 0))
+  if (af == AF_INET6 && len == IN6ADDRSZ
+      && (memcmp (uaddr, mapped, sizeof mapped) == 0
+         || (memcmp (uaddr, tunnelled, sizeof tunnelled) == 0
+             && memcmp (&uaddr[sizeof tunnelled], v6local, sizeof v6local))))
     {
       /* Unmap. */
       addr += sizeof mapped;
@@ -224,17 +475,21 @@ _nss_dns_gethostbyaddr_r (const char *addr, int len, int af,
       size = IN6ADDRSZ;
       break;
     default:
-      __set_errno (EAFNOSUPPORT);
+      *errnop = EAFNOSUPPORT;
       *h_errnop = NETDB_INTERNAL;
+      __resolv_context_put (ctx);
       return NSS_STATUS_UNAVAIL;
     }
-  if (size != len)
+  if (size > len)
     {
-      __set_errno (EAFNOSUPPORT);
+      *errnop = EAFNOSUPPORT;
       *h_errnop = NETDB_INTERNAL;
+      __resolv_context_put (ctx);
       return NSS_STATUS_UNAVAIL;
     }
 
+  host_buffer.buf = orig_host_buffer = (querybuf *) alloca (1024);
+
   switch (af)
     {
     case AF_INET:
@@ -244,72 +499,148 @@ _nss_dns_gethostbyaddr_r (const char *addr, int len, int af,
     case AF_INET6:
       qp = qbuf;
       for (n = IN6ADDRSZ - 1; n >= 0; n--)
-       qp += sprintf (qp, "%x.%x.", uaddr[n] & 0xf, (uaddr[n] >> 4) & 0xf);
-      strcpy (qp, "ip6.int");
+       {
+         static const char nibblechar[16] = "0123456789abcdef";
+         *qp++ = nibblechar[uaddr[n] & 0xf];
+         *qp++ = '.';
+         *qp++ = nibblechar[(uaddr[n] >> 4) & 0xf];
+         *qp++ = '.';
+       }
+      strcpy(qp, "ip6.arpa");
       break;
     default:
       /* Cannot happen.  */
+      break;
     }
 
-  n = res_query (qbuf, C_IN, T_PTR, (u_char *)host_buffer.buf,
-                sizeof host_buffer);
+  n = __res_context_query (ctx, qbuf, C_IN, T_PTR, host_buffer.buf->buf,
+                          1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0)
     {
       *h_errnop = h_errno;
+      __set_errno (olderr);
+      if (host_buffer.buf != orig_host_buffer)
+       free (host_buffer.buf);
+      __resolv_context_put (ctx);
       return errno == ECONNREFUSED ? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
     }
 
-  status = getanswer_r (&host_buffer, n, qbuf, T_PTR, result, buffer, buflen,
-                       h_errnop);
+  status = getanswer_r
+    (ctx, host_buffer.buf, n, qbuf, T_PTR, result, buffer, buflen,
+     errnop, h_errnop, 0 /* XXX */, ttlp, NULL);
+  if (host_buffer.buf != orig_host_buffer)
+    free (host_buffer.buf);
   if (status != NSS_STATUS_SUCCESS)
     {
-      *h_errnop = h_errno;
+      __resolv_context_put (ctx);
       return status;
     }
 
-#ifdef SUNSECURITY
-  This is not implemented because it is not possible to use the current
-  source from bind in a multi-threaded program.
-#endif
-
   result->h_addrtype = af;
   result->h_length = len;
   memcpy (host_data->host_addr, addr, len);
   host_data->h_addr_ptrs[0] = (char *) host_data->host_addr;
   host_data->h_addr_ptrs[1] = NULL;
-  if (af == AF_INET && (_res.options & RES_USE_INET6))
-    {
-      map_v4v6_address ((char *) host_data->host_addr,
-                       (char *) host_data->host_addr);
-      result->h_addrtype = AF_INET6;
-      result->h_length = IN6ADDRSZ;
-    }
   *h_errnop = NETDB_SUCCESS;
+  __resolv_context_put (ctx);
   return NSS_STATUS_SUCCESS;
 }
+hidden_def (_nss_dns_gethostbyaddr2_r)
 
 
+enum nss_status
+_nss_dns_gethostbyaddr_r (const void *addr, socklen_t len, int af,
+                         struct hostent *result, char *buffer, size_t buflen,
+                         int *errnop, int *h_errnop)
+{
+  return _nss_dns_gethostbyaddr2_r (addr, len, af, result, buffer, buflen,
+                                   errnop, h_errnop, NULL);
+}
+
+static void
+addrsort (struct resolv_context *ctx, char **ap, int num)
+{
+  int i, j;
+  char **p;
+  short aval[MAX_NR_ADDRS];
+  int needsort = 0;
+  size_t nsort = __resolv_context_sort_count (ctx);
+
+  p = ap;
+  if (num > MAX_NR_ADDRS)
+    num = MAX_NR_ADDRS;
+  for (i = 0; i < num; i++, p++)
+    {
+      for (j = 0 ; (unsigned)j < nsort; j++)
+       {
+         struct resolv_sortlist_entry e
+           = __resolv_context_sort_entry (ctx, j);
+         if (e.addr.s_addr == (((struct in_addr *)(*p))->s_addr & e.mask))
+           break;
+       }
+      aval[i] = j;
+      if (needsort == 0 && i > 0 && j < aval[i-1])
+       needsort = i;
+    }
+  if (!needsort)
+    return;
+
+  while (needsort++ < num)
+    for (j = needsort - 2; j >= 0; j--)
+      if (aval[j] > aval[j+1])
+       {
+         char *hp;
+
+         i = aval[j];
+         aval[j] = aval[j+1];
+         aval[j+1] = i;
+
+         hp = ap[j];
+         ap[j] = ap[j+1];
+         ap[j+1] = hp;
+       }
+      else
+       break;
+}
+
 static enum nss_status
-getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
+getanswer_r (struct resolv_context *ctx,
+            const querybuf *answer, int anslen, const char *qname, int qtype,
             struct hostent *result, char *buffer, size_t buflen,
-            int *h_errnop)
+            int *errnop, int *h_errnop, int map, int32_t *ttlp, char **canonp)
 {
   struct host_data
   {
     char *aliases[MAX_NR_ALIASES];
     unsigned char host_addr[16];       /* IPv4 or IPv6 */
-    char *h_addr_ptrs[MAX_NR_ADDRS + 1];
-    char linebuffer[0];
-  } *host_data = (struct host_data *) buffer;
-  int linebuflen = buflen - offsetof (struct host_data, linebuffer);
-  register const HEADER *hp;
+    char *h_addr_ptrs[0];
+  } *host_data;
+  int linebuflen;
+  const HEADER *hp;
   const u_char *end_of_message, *cp;
   int n, ancount, qdcount;
   int haveanswer, had_error;
   char *bp, **ap, **hap;
   char tbuf[MAXDNAME];
   const char *tname;
-  int (*name_ok) __P ((const char *));
+  int (*name_ok) (const char *);
+  u_char packtmp[NS_MAXCDNAME];
+  int have_to_map = 0;
+  uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct host_data);
+  buffer += pad;
+  buflen = buflen > pad ? buflen - pad : 0;
+  if (__glibc_unlikely (buflen < sizeof (struct host_data)))
+    {
+      /* The buffer is too small.  */
+    too_small:
+      *errnop = ERANGE;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_TRYAGAIN;
+    }
+  host_data = (struct host_data *) buffer;
+  linebuflen = buflen - sizeof (struct host_data);
+  if (buflen - sizeof (struct host_data) != linebuflen)
+    linebuflen = INT_MAX;
 
   tname = qname;
   result->h_name = NULL;
@@ -324,6 +655,7 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
       name_ok = res_dnok;
       break;
     default:
+      *errnop = ENOENT;
       return NSS_STATUS_UNAVAIL;  /* XXX should be abort(); */
     }
 
@@ -331,19 +663,42 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
    * find first satisfactory answer
    */
   hp = &answer->hdr;
-  bp = host_data->linebuffer;
   ancount = ntohs (hp->ancount);
   qdcount = ntohs (hp->qdcount);
   cp = answer->buf + HFIXEDSZ;
-  if (qdcount != 1)
+  if (__glibc_unlikely (qdcount != 1))
     {
       *h_errnop = NO_RECOVERY;
       return NSS_STATUS_UNAVAIL;
     }
+  if (sizeof (struct host_data) + (ancount + 1) * sizeof (char *) >= buflen)
+    goto too_small;
+  bp = (char *) &host_data->h_addr_ptrs[ancount + 1];
+  linebuflen -= (ancount + 1) * sizeof (char *);
+
+  n = __ns_name_unpack (answer->buf, end_of_message, cp,
+                       packtmp, sizeof packtmp);
+  if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
+    {
+      if (__glibc_unlikely (errno == EMSGSIZE))
+       goto too_small;
+
+      n = -1;
+    }
+
+  if (n > 0 && bp[0] == '.')
+    bp[0] = '\0';
 
-  n = dn_expand (answer->buf, end_of_message, cp, bp, linebuflen);
-  if (n < 0 || (*name_ok) (bp) == 0)
+  if (__glibc_unlikely (n < 0))
     {
+      *errnop = errno;
+      *h_errnop = NO_RECOVERY;
+      return NSS_STATUS_UNAVAIL;
+    }
+  if (__glibc_unlikely (name_ok (bp) == 0))
+    {
+      errno = EBADMSG;
+      *errnop = EBADMSG;
       *h_errnop = NO_RECOVERY;
       return NSS_STATUS_UNAVAIL;
     }
@@ -358,12 +713,15 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
       n = strlen (bp) + 1;             /* for the \0 */
       if (n >= MAXHOSTNAMELEN)
        {
-         __set_h_errno (NO_RECOVERY);
+         *h_errnop = NO_RECOVERY;
+         *errnop = ENOENT;
          return NSS_STATUS_TRYAGAIN;
        }
       result->h_name = bp;
       bp += n;
       linebuflen -= n;
+      if (linebuflen < 0)
+       goto too_small;
       /* The qname can be abbreviated, but h_name is now absolute. */
       qname = result->h_name;
     }
@@ -381,32 +739,62 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
     {
       int type, class;
 
-      n = dn_expand (answer->buf, end_of_message, cp, bp, linebuflen);
-      if (n < 0 || (*name_ok) (bp) == 0)
+      n = __ns_name_unpack (answer->buf, end_of_message, cp,
+                           packtmp, sizeof packtmp);
+      if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
+       {
+         if (__glibc_unlikely (errno == EMSGSIZE))
+           goto too_small;
+
+         n = -1;
+       }
+
+      if (__glibc_unlikely (n < 0 || (*name_ok) (bp) == 0))
        {
          ++had_error;
          continue;
        }
       cp += n;                         /* name */
-      type = _getshort (cp);
+
+      if (__glibc_unlikely (cp + 10 > end_of_message))
+       {
+         ++had_error;
+         continue;
+       }
+
+      type = __ns_get16 (cp);
       cp += INT16SZ;                   /* type */
-      class = _getshort(cp);
-      cp += INT16SZ + INT32SZ;         /* class, TTL */
-      n = _getshort(cp);
+      class = __ns_get16 (cp);
+      cp += INT16SZ;                   /* class */
+      int32_t ttl = __ns_get32 (cp);
+      cp += INT32SZ;                   /* TTL */
+      n = __ns_get16 (cp);
       cp += INT16SZ;                   /* len */
-      if (class != C_IN)
+
+      if (end_of_message - cp < n)
+       {
+         /* RDATA extends beyond the end of the packet.  */
+         ++had_error;
+         continue;
+       }
+
+      if (__glibc_unlikely (class != C_IN))
        {
          /* XXX - debug? syslog? */
          cp += n;
          continue;                     /* XXX - had_error++ ? */
        }
 
-      if ((qtype ==T_A || qtype == T_AAAA) && type == T_CNAME)
+      if ((qtype == T_A || qtype == T_AAAA) && type == T_CNAME)
        {
+         /* A CNAME could also have a TTL entry.  */
+         if (ttlp != NULL && ttl < *ttlp)
+             *ttlp = ttl;
+
          if (ap >= &host_data->aliases[MAX_NR_ALIASES - 1])
            continue;
          n = dn_expand (answer->buf, end_of_message, cp, tbuf, sizeof tbuf);
-         if (n < 0 || (*name_ok) (tbuf) == 0)
+         if (__glibc_unlikely (n < 0 || (*name_ok) (tbuf) == 0))
            {
              ++had_error;
              continue;
@@ -415,7 +803,7 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
          /* Store alias.  */
          *ap++ = bp;
          n = strlen (bp) + 1;          /* For the \0.  */
-         if (n >= MAXHOSTNAMELEN)
+         if (__glibc_unlikely (n >= MAXHOSTNAMELEN))
            {
              ++had_error;
              continue;
@@ -424,21 +812,27 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
          linebuflen -= n;
          /* Get canonical name.  */
          n = strlen (tbuf) + 1;        /* For the \0.  */
-         if ((size_t) n > buflen || n >= MAXHOSTNAMELEN)
+         if (__glibc_unlikely (n > linebuflen))
+           goto too_small;
+         if (__glibc_unlikely (n >= MAXHOSTNAMELEN))
            {
              ++had_error;
              continue;
            }
-         result->h_name = strcpy (bp, tbuf);   /* Cannot overflow.  */
-         bp += n;
+         result->h_name = bp;
+         bp = __mempcpy (bp, tbuf, n); /* Cannot overflow.  */
          linebuflen -= n;
          continue;
        }
 
       if (qtype == T_PTR && type == T_CNAME)
        {
+         /* A CNAME could also have a TTL entry.  */
+         if (ttlp != NULL && ttl < *ttlp)
+             *ttlp = ttl;
+
          n = dn_expand (answer->buf, end_of_message, cp, tbuf, sizeof tbuf);
-         if (n < 0 || res_dnok (tbuf) == 0)
+         if (__glibc_unlikely (n < 0 || res_dnok (tbuf) == 0))
            {
              ++had_error;
              continue;
@@ -446,21 +840,23 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
          cp += n;
          /* Get canonical name.  */
          n = strlen (tbuf) + 1;   /* For the \0.  */
-         if ((size_t) n > buflen || n >= MAXHOSTNAMELEN)
+         if (__glibc_unlikely (n > linebuflen))
+           goto too_small;
+         if (__glibc_unlikely (n >= MAXHOSTNAMELEN))
            {
              ++had_error;
              continue;
            }
-         tname = strcpy (bp, tbuf);    /* Cannot overflow.  */
-         bp += n;
+         tname = bp;
+         bp = __mempcpy (bp, tbuf, n); /* Cannot overflow.  */
          linebuflen -= n;
          continue;
        }
-      if (type != qtype)
+
+      if (type == T_A && qtype == T_AAAA && map)
+       have_to_map = 1;
+      else if (__glibc_unlikely (type != qtype))
        {
-         syslog (LOG_NOTICE | LOG_AUTH,
-              "gethostby*.getanswer: asked for \"%s %s %s\", got type \"%s\"",
-                 qname, p_class (C_IN), p_type (qtype), p_type (type));
          cp += n;
          continue;                     /* XXX - had_error++ ? */
        }
@@ -468,63 +864,50 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
       switch (type)
        {
        case T_PTR:
-         if (strcasecmp (tname, bp) != 0)
+         if (__glibc_unlikely (strcasecmp (tname, bp) != 0))
            {
-             syslog (LOG_NOTICE | LOG_AUTH, AskedForGot, qname, bp);
              cp += n;
              continue;                 /* XXX - had_error++ ? */
            }
-         n = dn_expand (answer->buf, end_of_message, cp, bp, linebuflen);
-         if (n < 0 || res_hnok (bp) == 0)
+
+         n = __ns_name_unpack (answer->buf, end_of_message, cp,
+                               packtmp, sizeof packtmp);
+         if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
            {
-             ++had_error;
-             break;
+             if (__glibc_unlikely (errno == EMSGSIZE))
+               goto too_small;
+
+             n = -1;
            }
-#if MULTI_PTRS_ARE_ALIASES
-         cp += n;
-         if (haveanswer == 0)
-           result->h_name = bp;
-         else if (ap < &host_data->aliases[MAXALIASES-1])
-           *ap++ = bp;
-         else
-           n = -1;
-         if (n != -1)
+
+         if (__glibc_unlikely (n < 0 || res_hnok (bp) == 0))
            {
-             n = strlen (bp) + 1;      /* for the \0 */
-             if (n >= MAXHOSTNAMELEN)
-               {
-                 ++had_error;
-                 break;
-               }
-             bp += n;
-             linebuflen -= n;
+             ++had_error;
+             break;
            }
-         break;
-#else
+         if (ttlp != NULL && ttl < *ttlp)
+             *ttlp = ttl;
+         /* bind would put multiple PTR records as aliases, but we don't do
+            that.  */
          result->h_name = bp;
-         if (_res.options & RES_USE_INET6)
-           {
-             n = strlen (bp) + 1;      /* for the \0 */
-             if (n >= MAXHOSTNAMELEN)
-               {
-                 ++had_error;
-                 break;
-               }
-             bp += n;
-             linebuflen -= n;
-             map_v4v6_hostent (result, &bp, &linebuflen);
-           }
          *h_errnop = NETDB_SUCCESS;
          return NSS_STATUS_SUCCESS;
-#endif
        case T_A:
        case T_AAAA:
-         if (strcasecmp (result->h_name, bp) != 0)
+         if (__glibc_unlikely (strcasecmp (result->h_name, bp) != 0))
            {
-             syslog (LOG_NOTICE | LOG_AUTH, AskedForGot, result->h_name, bp);
              cp += n;
              continue;                 /* XXX - had_error++ ? */
            }
+
+         /* Stop parsing at a record whose length is incorrect.  */
+         if (n != rrtype_to_rdata_length (type))
+           {
+             ++had_error;
+             break;
+           }
+
+         /* Skip records of the wrong type.  */
          if (n != result->h_length)
            {
              cp += n;
@@ -532,28 +915,27 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
            }
          if (!haveanswer)
            {
-             register int nn;
+             int nn;
 
+             /* We compose a single hostent out of the entire chain of
+                entries, so the TTL of the hostent is essentially the lowest
+                TTL in the chain.  */
+             if (ttlp != NULL && ttl < *ttlp)
+               *ttlp = ttl;
+             if (canonp != NULL)
+               *canonp = bp;
              result->h_name = bp;
              nn = strlen (bp) + 1;     /* for the \0 */
              bp += nn;
              linebuflen -= nn;
            }
 
+         linebuflen -= sizeof (align) - ((u_long) bp % sizeof (align));
          bp += sizeof (align) - ((u_long) bp % sizeof (align));
 
-         if (n >= linebuflen)
-           {
-             ++had_error;
-             continue;
-           }
-         if (hap >= &host_data->h_addr_ptrs[MAX_NR_ADDRS-1])
-           {
-             cp += n;
-             continue;
-           }
-         memcpy (*hap++ = bp, cp, n);
-         bp += n;
+         if (__glibc_unlikely (n > linebuflen))
+           goto too_small;
+         bp = __mempcpy (*hap++ = bp, cp, n);
          cp += n;
          linebuflen -= n;
          break;
@@ -568,32 +950,419 @@ getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
     {
       *ap = NULL;
       *hap = NULL;
-#if defined RESOLVSORT
       /*
        * Note: we sort even if host can take only one address
        * in its return structures - should give it the "best"
        * address in that case, not some random one
        */
-      if (_res.nsort && haveanswer > 1 && qtype == T_A)
-       addrsort (host_data->h_addr_ptrs, haveanswer);
-#endif /*RESOLVSORT*/
+      if (haveanswer > 1 && qtype == T_A
+         && __resolv_context_sort_count (ctx) > 0)
+       addrsort (ctx, host_data->h_addr_ptrs, haveanswer);
 
       if (result->h_name == NULL)
        {
          n = strlen (qname) + 1;       /* For the \0.  */
-         if (n > linebuflen || n >= MAXHOSTNAMELEN)
+         if (n > linebuflen)
+           goto too_small;
+         if (n >= MAXHOSTNAMELEN)
            goto no_recovery;
-         result->h_name = strcpy (bp, qname);  /* Cannot overflow.  */
-         bp += n;
+         result->h_name = bp;
+         bp = __mempcpy (bp, qname, n);        /* Cannot overflow.  */
          linebuflen -= n;
        }
 
-      if (_res.options & RES_USE_INET6)
-       map_v4v6_hostent (result, &bp, &linebuflen);
+      if (have_to_map)
+       if (map_v4v6_hostent (result, &bp, &linebuflen))
+         goto too_small;
       *h_errnop = NETDB_SUCCESS;
       return NSS_STATUS_SUCCESS;
     }
  no_recovery:
   *h_errnop = NO_RECOVERY;
+  *errnop = ENOENT;
+  /* Special case here: if the resolver sent a result but it only
+     contains a CNAME while we are looking for a T_A or T_AAAA record,
+     we fail with NOTFOUND instead of TRYAGAIN.  */
+  return ((qtype == T_A || qtype == T_AAAA) && ap != host_data->aliases
+          ? NSS_STATUS_NOTFOUND : NSS_STATUS_TRYAGAIN);
+}
+
+
+static enum nss_status
+gaih_getanswer_slice (const querybuf *answer, int anslen, const char *qname,
+                     struct gaih_addrtuple ***patp,
+                     char **bufferp, size_t *buflenp,
+                     int *errnop, int *h_errnop, int32_t *ttlp, int *firstp)
+{
+  char *buffer = *bufferp;
+  size_t buflen = *buflenp;
+
+  struct gaih_addrtuple **pat = *patp;
+  const HEADER *hp = &answer->hdr;
+  int ancount = ntohs (hp->ancount);
+  int qdcount = ntohs (hp->qdcount);
+  const u_char *cp = answer->buf + HFIXEDSZ;
+  const u_char *end_of_message = answer->buf + anslen;
+  if (__glibc_unlikely (qdcount != 1))
+    {
+      *h_errnop = NO_RECOVERY;
+      return NSS_STATUS_UNAVAIL;
+    }
+
+  u_char packtmp[NS_MAXCDNAME];
+  int n = __ns_name_unpack (answer->buf, end_of_message, cp,
+                           packtmp, sizeof packtmp);
+  /* We unpack the name to check it for validity.  But we do not need
+     it later.  */
+  if (n != -1 && __ns_name_ntop (packtmp, buffer, buflen) == -1)
+    {
+      if (__glibc_unlikely (errno == EMSGSIZE))
+       {
+       too_small:
+         *errnop = ERANGE;
+         *h_errnop = NETDB_INTERNAL;
+         return NSS_STATUS_TRYAGAIN;
+       }
+
+      n = -1;
+    }
+
+  if (__glibc_unlikely (n < 0))
+    {
+      *errnop = errno;
+      *h_errnop = NO_RECOVERY;
+      return NSS_STATUS_UNAVAIL;
+    }
+  if (__glibc_unlikely (res_hnok (buffer) == 0))
+    {
+      errno = EBADMSG;
+      *errnop = EBADMSG;
+      *h_errnop = NO_RECOVERY;
+      return NSS_STATUS_UNAVAIL;
+    }
+  cp += n + QFIXEDSZ;
+
+  int haveanswer = 0;
+  int had_error = 0;
+  char *canon = NULL;
+  char *h_name = NULL;
+  int h_namelen = 0;
+
+  if (ancount == 0)
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
+
+  while (ancount-- > 0 && cp < end_of_message && had_error == 0)
+    {
+      n = __ns_name_unpack (answer->buf, end_of_message, cp,
+                           packtmp, sizeof packtmp);
+      if (n != -1 &&
+         (h_namelen = __ns_name_ntop (packtmp, buffer, buflen)) == -1)
+       {
+         if (__glibc_unlikely (errno == EMSGSIZE))
+           goto too_small;
+
+         n = -1;
+       }
+      if (__glibc_unlikely (n < 0 || res_hnok (buffer) == 0))
+       {
+         ++had_error;
+         continue;
+       }
+      if (*firstp && canon == NULL)
+       {
+         h_name = buffer;
+         buffer += h_namelen;
+         buflen -= h_namelen;
+       }
+
+      cp += n;                         /* name */
+
+      if (__glibc_unlikely (cp + 10 > end_of_message))
+       {
+         ++had_error;
+         continue;
+       }
+
+      int type = __ns_get16 (cp);
+      cp += INT16SZ;                   /* type */
+      int class = __ns_get16 (cp);
+      cp += INT16SZ;                   /* class */
+      int32_t ttl = __ns_get32 (cp);
+      cp += INT32SZ;                   /* TTL */
+      n = __ns_get16 (cp);
+      cp += INT16SZ;                   /* len */
+
+      if (end_of_message - cp < n)
+       {
+         /* RDATA extends beyond the end of the packet.  */
+         ++had_error;
+         continue;
+       }
+
+      if (class != C_IN)
+       {
+         cp += n;
+         continue;
+       }
+
+      if (type == T_CNAME)
+       {
+         char tbuf[MAXDNAME];
+
+         /* A CNAME could also have a TTL entry.  */
+         if (ttlp != NULL && ttl < *ttlp)
+             *ttlp = ttl;
+
+         n = dn_expand (answer->buf, end_of_message, cp, tbuf, sizeof tbuf);
+         if (__glibc_unlikely (n < 0 || res_hnok (tbuf) == 0))
+           {
+             ++had_error;
+             continue;
+           }
+         cp += n;
+
+         if (*firstp)
+           {
+             /* Reclaim buffer space.  */
+             if (h_name + h_namelen == buffer)
+               {
+                 buffer = h_name;
+                 buflen += h_namelen;
+               }
+
+             n = strlen (tbuf) + 1;
+             if (__glibc_unlikely (n > buflen))
+               goto too_small;
+             if (__glibc_unlikely (n >= MAXHOSTNAMELEN))
+               {
+                 ++had_error;
+                 continue;
+               }
+
+             canon = buffer;
+             buffer = __mempcpy (buffer, tbuf, n);
+             buflen -= n;
+             h_namelen = 0;
+           }
+         continue;
+       }
+
+      /* Stop parsing if we encounter a record with incorrect RDATA
+        length.  */
+      if (type == T_A || type == T_AAAA)
+       {
+         if (n != rrtype_to_rdata_length (type))
+           {
+             ++had_error;
+             continue;
+           }
+       }
+      else
+       {
+         /* Skip unknown records.  */
+         cp += n;
+         continue;
+       }
+
+      assert (type == T_A || type == T_AAAA);
+      if (*pat == NULL)
+       {
+         uintptr_t pad = (-(uintptr_t) buffer
+                          % __alignof__ (struct gaih_addrtuple));
+         buffer += pad;
+         buflen = buflen > pad ? buflen - pad : 0;
+
+         if (__glibc_unlikely (buflen < sizeof (struct gaih_addrtuple)))
+           goto too_small;
+
+         *pat = (struct gaih_addrtuple *) buffer;
+         buffer += sizeof (struct gaih_addrtuple);
+         buflen -= sizeof (struct gaih_addrtuple);
+       }
+
+      (*pat)->name = NULL;
+      (*pat)->next = NULL;
+
+      if (*firstp)
+       {
+         /* We compose a single hostent out of the entire chain of
+            entries, so the TTL of the hostent is essentially the lowest
+            TTL in the chain.  */
+         if (ttlp != NULL && ttl < *ttlp)
+           *ttlp = ttl;
+
+         (*pat)->name = canon ?: h_name;
+
+         *firstp = 0;
+       }
+
+      (*pat)->family = type == T_A ? AF_INET : AF_INET6;
+      memcpy ((*pat)->addr, cp, n);
+      cp += n;
+      (*pat)->scopeid = 0;
+
+      pat = &((*pat)->next);
+
+      haveanswer = 1;
+    }
+
+  if (haveanswer)
+    {
+      *patp = pat;
+      *bufferp = buffer;
+      *buflenp = buflen;
+
+      *h_errnop = NETDB_SUCCESS;
+      return NSS_STATUS_SUCCESS;
+    }
+
+  /* Special case here: if the resolver sent a result but it only
+     contains a CNAME while we are looking for a T_A or T_AAAA record,
+     we fail with NOTFOUND instead of TRYAGAIN.  */
+  if (canon != NULL)
+    {
+      *h_errnop = HOST_NOT_FOUND;
+      return NSS_STATUS_NOTFOUND;
+    }
+
+  *h_errnop = NETDB_INTERNAL;
   return NSS_STATUS_TRYAGAIN;
 }
+
+
+static enum nss_status
+gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
+               int anslen2, const char *qname,
+               struct gaih_addrtuple **pat, char *buffer, size_t buflen,
+               int *errnop, int *h_errnop, int32_t *ttlp)
+{
+  int first = 1;
+
+  enum nss_status status = NSS_STATUS_NOTFOUND;
+
+  /* Combining the NSS status of two distinct queries requires some
+     compromise and attention to symmetry (A or AAAA queries can be
+     returned in any order).  What follows is a breakdown of how this
+     code is expected to work and why. We discuss only SUCCESS,
+     TRYAGAIN, NOTFOUND and UNAVAIL, since they are the only returns
+     that apply (though RETURN and MERGE exist).  We make a distinction
+     between TRYAGAIN (recoverable) and TRYAGAIN' (not-recoverable).
+     A recoverable TRYAGAIN is almost always due to buffer size issues
+     and returns ERANGE in errno and the caller is expected to retry
+     with a larger buffer.
+
+     Lastly, you may be tempted to make significant changes to the
+     conditions in this code to bring about symmetry between responses.
+     Please don't change anything without due consideration for
+     expected application behaviour.  Some of the synthesized responses
+     aren't very well thought out and sometimes appear to imply that
+     IPv4 responses are always answer 1, and IPv6 responses are always
+     answer 2, but that's not true (see the implementation of send_dg
+     and send_vc to see response can arrive in any order, particularly
+     for UDP). However, we expect it holds roughly enough of the time
+     that this code works, but certainly needs to be fixed to make this
+     a more robust implementation.
+
+     ----------------------------------------------
+     | Answer 1 Status /   | Synthesized | Reason |
+     | Answer 2 Status     | Status      |        |
+     |--------------------------------------------|
+     | SUCCESS/SUCCESS     | SUCCESS     | [1]    |
+     | SUCCESS/TRYAGAIN    | TRYAGAIN    | [5]    |
+     | SUCCESS/TRYAGAIN'   | SUCCESS     | [1]    |
+     | SUCCESS/NOTFOUND    | SUCCESS     | [1]    |
+     | SUCCESS/UNAVAIL     | SUCCESS     | [1]    |
+     | TRYAGAIN/SUCCESS    | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/TRYAGAIN'  | TRYAGAIN    | [2]    |
+     | TRYAGAIN/NOTFOUND   | TRYAGAIN    | [2]    |
+     | TRYAGAIN/UNAVAIL    | TRYAGAIN    | [2]    |
+     | TRYAGAIN'/SUCCESS   | SUCCESS     | [3]    |
+     | TRYAGAIN'/TRYAGAIN  | TRYAGAIN    | [3]    |
+     | TRYAGAIN'/TRYAGAIN' | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/NOTFOUND  | TRYAGAIN'   | [3]    |
+     | TRYAGAIN'/UNAVAIL   | UNAVAIL     | [3]    |
+     | NOTFOUND/SUCCESS    | SUCCESS     | [3]    |
+     | NOTFOUND/TRYAGAIN   | TRYAGAIN    | [3]    |
+     | NOTFOUND/TRYAGAIN'  | TRYAGAIN'   | [3]    |
+     | NOTFOUND/NOTFOUND   | NOTFOUND    | [3]    |
+     | NOTFOUND/UNAVAIL    | UNAVAIL     | [3]    |
+     | UNAVAIL/SUCCESS     | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN    | UNAVAIL     | [4]    |
+     | UNAVAIL/TRYAGAIN'   | UNAVAIL     | [4]    |
+     | UNAVAIL/NOTFOUND    | UNAVAIL     | [4]    |
+     | UNAVAIL/UNAVAIL     | UNAVAIL     | [4]    |
+     ----------------------------------------------
+
+     [1] If the first response is a success we return success.
+        This ignores the state of the second answer and in fact
+        incorrectly sets errno and h_errno to that of the second
+        answer.  However because the response is a success we ignore
+        *errnop and *h_errnop (though that means you touched errno on
+        success).  We are being conservative here and returning the
+        likely IPv4 response in the first answer as a success.
+
+     [2] If the first response is a recoverable TRYAGAIN we return
+        that instead of looking at the second response.  The
+        expectation here is that we have failed to get an IPv4 response
+        and should retry both queries.
+
+     [3] If the first response was not a SUCCESS and the second
+        response is not NOTFOUND (had a SUCCESS, need to TRYAGAIN,
+        or failed entirely e.g. TRYAGAIN' and UNAVAIL) then use the
+        result from the second response, otherwise the first responses
+        status is used.  Again we have some odd side-effects when the
+        second response is NOTFOUND because we overwrite *errnop and
+        *h_errnop that means that a first answer of NOTFOUND might see
+        its *errnop and *h_errnop values altered.  Whether it matters
+        in practice that a first response NOTFOUND has the wrong
+        *errnop and *h_errnop is undecided.
+
+     [4] If the first response is UNAVAIL we return that instead of
+        looking at the second response.  The expectation here is that
+        it will have failed similarly e.g. configuration failure.
+
+     [5] Testing this code is complicated by the fact that truncated
+        second response buffers might be returned as SUCCESS if the
+        first answer is a SUCCESS.  To fix this we add symmetry to
+        TRYAGAIN with the second response.  If the second response
+        is a recoverable error we now return TRYAGIN even if the first
+        response was SUCCESS.  */
+
+  if (anslen1 > 0)
+    status = gaih_getanswer_slice(answer1, anslen1, qname,
+                                 &pat, &buffer, &buflen,
+                                 errnop, h_errnop, ttlp,
+                                 &first);
+
+  if ((status == NSS_STATUS_SUCCESS || status == NSS_STATUS_NOTFOUND
+       || (status == NSS_STATUS_TRYAGAIN
+          /* We want to look at the second answer in case of an
+             NSS_STATUS_TRYAGAIN only if the error is non-recoverable, i.e.
+             *h_errnop is NO_RECOVERY. If not, and if the failure was due to
+             an insufficient buffer (ERANGE), then we need to drop the results
+             and pass on the NSS_STATUS_TRYAGAIN to the caller so that it can
+             repeat the query with a larger buffer.  */
+          && (*errnop != ERANGE || *h_errnop == NO_RECOVERY)))
+      && answer2 != NULL && anslen2 > 0)
+    {
+      enum nss_status status2 = gaih_getanswer_slice(answer2, anslen2, qname,
+                                                    &pat, &buffer, &buflen,
+                                                    errnop, h_errnop, ttlp,
+                                                    &first);
+      /* Use the second response status in some cases.  */
+      if (status != NSS_STATUS_SUCCESS && status2 != NSS_STATUS_NOTFOUND)
+       status = status2;
+      /* Do not return a truncated second response (unless it was
+        unavoidable e.g. unrecoverable TRYAGAIN).  */
+      if (status == NSS_STATUS_SUCCESS
+         && (status2 == NSS_STATUS_TRYAGAIN
+             && *errnop == ERANGE && *h_errnop != NO_RECOVERY))
+       status = NSS_STATUS_TRYAGAIN;
+    }
+
+  return status;
+}