root/usr/src/lib/nsswitch/dns/common/dns_common.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright (c) 2013, Joyent, Inc.  All rights reserved.
 * Copyright 2023 Oxide Computer Company
 */

/*
 *      dns_common.c
 */

#include "dns_common.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <net/if.h>

#pragma weak    dn_expand
#pragma weak    res_ninit
#pragma weak    res_ndestroy
#pragma weak    res_nsearch
#pragma weak    res_nclose
#pragma weak    ns_get16
#pragma weak    ns_get32
#pragma weak    __ns_get16
#pragma weak    __ns_get32

#define DNS_ALIASES     0
#define DNS_ADDRLIST    1
#define DNS_MAPDLIST    2

#ifndef tolower
#define tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) | 0x20 : (c))
#endif

static int
dns_netdb_aliases(char **from_list, char **to_list, char **aliaspp, int type,
    int *count, int af_type)
{
        char            *fstr;
        int             cnt = 0;
        size_t          len;

        *count = 0;
        if ((char *)to_list >= *aliaspp)
                return (NSS_STR_PARSE_ERANGE);

        for (fstr = from_list[cnt]; fstr != NULL; fstr = from_list[cnt]) {
                if (type == DNS_ALIASES) {
                        len = strlen(fstr) + 1;
                } else {
                        len = (af_type == AF_INET) ? sizeof (struct in_addr) :
                            sizeof (struct in6_addr);
                }
                *aliaspp -= len;
                to_list[cnt] = *aliaspp;
                if (*aliaspp <= (char *)&to_list[cnt+1])
                        return (NSS_STR_PARSE_ERANGE);
                if (type == DNS_MAPDLIST) {
                        /* LINTED: E_BAD_PTR_CAST_ALIGN */
                        struct in6_addr *addr6p = (struct in6_addr *)*aliaspp;

                        (void) memset(addr6p, '\0', sizeof (struct in6_addr));
                        (void) memcpy(&addr6p->s6_addr[12], fstr,
                            sizeof (struct in_addr));
                        addr6p->s6_addr[10] = 0xffU;
                        addr6p->s6_addr[11] = 0xffU;
                        ++cnt;
                } else {
                        (void) memcpy (*aliaspp, fstr, len);
                        ++cnt;
                }
        }
        to_list[cnt] = NULL;

        *count = cnt;
        if (cnt == 0)
                return (NSS_STR_PARSE_PARSE);

        return (NSS_STR_PARSE_SUCCESS);
}


int
ent2result(struct hostent *he, nss_XbyY_args_t *argp, int af_type)
{
        char            *buffer, *limit;
        int             buflen = argp->buf.buflen;
        int             ret, count;
        size_t len;
        struct hostent  *host;
        struct in_addr  *addrp;
        struct in6_addr *addrp6;

        limit = argp->buf.buffer + buflen;
        host = (struct hostent *)argp->buf.result;
        buffer = argp->buf.buffer;

        /* h_addrtype and h_length */
        host->h_addrtype = af_type;
        host->h_length = (af_type == AF_INET) ? sizeof (struct in_addr) :
            sizeof (struct in6_addr);

        /* h_name */
        len = strlen(he->h_name) + 1;
        host->h_name = buffer;
        if (host->h_name + len >= limit)
                return (NSS_STR_PARSE_ERANGE);
        (void) memcpy(host->h_name, he->h_name, len);
        buffer += len;

        /* h_addr_list */
        if (af_type == AF_INET) {
                addrp = (struct in_addr *)ROUND_DOWN(limit, sizeof (*addrp));
                host->h_addr_list = (char **)ROUND_UP(buffer, sizeof (char **));
                ret = dns_netdb_aliases(he->h_addr_list, host->h_addr_list,
                    (char **)&addrp, DNS_ADDRLIST, &count, af_type);
                if (ret != NSS_STR_PARSE_SUCCESS)
                        return (ret);
                /* h_aliases */
                host->h_aliases = host->h_addr_list + count + 1;
                ret = dns_netdb_aliases(he->h_aliases, host->h_aliases,
                    (char **)&addrp, DNS_ALIASES, &count, af_type);
        } else {
                addrp6 = (struct in6_addr *)ROUND_DOWN(limit, sizeof (*addrp6));
                host->h_addr_list = (char **)ROUND_UP(buffer, sizeof (char **));
                if (he->h_addrtype == AF_INET && af_type == AF_INET6) {
                        ret = dns_netdb_aliases(he->h_addr_list,
                            host->h_addr_list, (char **)&addrp6,
                            DNS_MAPDLIST, &count, af_type);
                } else {
                        ret = dns_netdb_aliases(he->h_addr_list,
                            host->h_addr_list, (char **)&addrp6,
                            DNS_ADDRLIST, &count, af_type);
                }
                if (ret != NSS_STR_PARSE_SUCCESS)
                        return (ret);
                /* h_aliases */
                host->h_aliases = host->h_addr_list + count + 1;
                ret = dns_netdb_aliases(he->h_aliases, host->h_aliases,
                    (char **)&addrp6, DNS_ALIASES, &count, af_type);
        }
        if (ret == NSS_STR_PARSE_PARSE)
                ret = NSS_STR_PARSE_SUCCESS;

        return (ret);
}

/*
 * Convert the hostent structure into string in the following
 * format:
 *
 * IP-address official-host-name nicknames ...
 *
 * If more than one IP-addresses matches the official-host-name,
 * the above line will be followed by:
 * IP-address-1 official-host-name
 * IP-address-2 official-host-name
 * ...
 *
 * This is so that the str2hostent function in libnsl
 * can convert the string back to the original hostent
 * data.
 */
int
ent2str(struct hostent  *hp, nss_XbyY_args_t *ap, int af_type)
{
        char            **p;
        char            obuf[INET6_ADDRSTRLEN];
        void            *addr;
        struct in_addr  in4;
        int             af;
        int             n;
        const char      *res;
        char            **q;
        int             l = ap->buf.buflen;
        char            *s = ap->buf.buffer;

        /*
         * for "hosts" lookup, we only want address type of
         * AF_INET. For "ipnodes", we can have both AF_INET
         * and AF_INET6.
         */
        if (af_type == AF_INET && hp->h_addrtype != AF_INET)
                return (NSS_STR_PARSE_PARSE);

        for (p = hp->h_addr_list; *p != 0; p++) {

                if (p != hp->h_addr_list) {
                        *s = '\n';
                        s++;
                        l--;
                }

                if (hp->h_addrtype == AF_INET6) {
                        /* LINTED: E_BAD_PTR_CAST_ALIGN */
                        if (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)*p)) {
                                /* LINTED: E_BAD_PTR_CAST_ALIGN */
                                IN6_V4MAPPED_TO_INADDR((struct in6_addr *)*p,
                                    &in4);
                                af = AF_INET;
                                addr = &in4;
                        } else {
                                af = AF_INET6;
                                addr = *p;
                        }
                } else {
                        af = AF_INET;
                        addr = *p;
                }
                res = inet_ntop(af, addr, obuf, sizeof (obuf));
                if (res == NULL)
                        return (NSS_STR_PARSE_PARSE);

                if ((n = snprintf(s, l, "%s", res)) >= l)
                        return (NSS_STR_PARSE_ERANGE);
                l -= n;
                s += n;
                if (hp->h_name != NULL && *hp->h_name != '\0') {
                        if ((n = snprintf(s, l, " %s", hp->h_name)) >= l)
                                return (NSS_STR_PARSE_ERANGE);
                        l -= n;
                        s += n;
                }
                if (p == hp->h_addr_list) {
                        for (q = hp->h_aliases; q && *q; q++) {
                                if ((n = snprintf(s, l, " %s", *q)) >= l)
                                        return (NSS_STR_PARSE_ERANGE);
                                l -= n;
                                s += n;
                        }
                }
        }

        ap->returnlen = s - ap->buf.buffer;
        return (NSS_STR_PARSE_SUCCESS);
}

nss_backend_t *
_nss_dns_constr(dns_backend_op_t ops[], int n_ops)
{
        dns_backend_ptr_t       be;

        if ((be = (dns_backend_ptr_t)malloc(sizeof (*be))) == 0)
                return (0);

        be->ops = ops;
        be->n_ops = n_ops;
        return ((nss_backend_t *)be);
}

/*
 * name_is_alias(aliases_ptr, name_ptr)
 * Verify name matches an alias in the provided aliases list.
 *
 * Within DNS there should be only one canonical name, aliases should
 * all refer to the one canonical.  However alias chains do occur and
 * pre BIND 9 servers may also respond with multiple CNAMEs.  This
 * routine checks if a given name has been provided as a CNAME in the
 * response.  This assumes that the chains have been sent in-order.
 *
 * INPUT:
 *  aliases_ptr: space separated list of alias names.
 *  name_ptr: name to look for in aliases_ptr list.
 * RETURNS: NSS_SUCCESS or NSS_NOTFOUND
 *  NSS_SUCCESS indicates that the name is listed in the collected aliases.
 */
static nss_status_t
name_is_alias(char *aliases_ptr, char *name_ptr)
{
        char *host_ptr;
        /* Loop through alias string and compare it against host string. */
        while (*aliases_ptr != '\0') {
                host_ptr = name_ptr;

                /* Compare name with alias. */
                while (tolower(*host_ptr) == tolower(*aliases_ptr) &&
                    *host_ptr != '\0') {
                        host_ptr++;
                        aliases_ptr++;
                }

                /*
                 * If name was exhausted and the next character in the
                 * alias is either the end-of-string or space
                 * character then we have a match.
                 */
                if (*host_ptr == '\0' &&
                    (*aliases_ptr == '\0' || *aliases_ptr == ' ')) {
                        return (NSS_SUCCESS);
                }

                /* Alias did not match, step over remainder of alias. */
                while (*aliases_ptr != ' ' && *aliases_ptr != '\0')
                        aliases_ptr++;
                /* Step over separator character. */
                while (*aliases_ptr == ' ') aliases_ptr++;
        }
        return (NSS_NOTFOUND);
}

static int
_nss_has_interfaces(boolean_t *v4, boolean_t *v6)
{
        struct ifaddrs *ifp, *i;
        struct in_addr in4;
        struct in6_addr in6;
        const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;

        *v4 = *v6 = B_FALSE;

        if (getifaddrs(&ifp) != 0)
                return (-1);

        for (i = ifp; i != NULL; i = i->ifa_next) {
                if (i->ifa_flags & IFF_LOOPBACK)
                        continue;
                if ((i->ifa_flags & IFF_UP) == 0)
                        continue;

                if (i->ifa_addr->sa_family == AF_INET) {
                        if (*v4 != B_FALSE)
                                continue;

                        if (((struct sockaddr_in *)i->ifa_addr)->
                            sin_addr.s_addr == INADDR_ANY)
                                continue;
                        *v4 = B_TRUE;
                }

                if (i->ifa_addr->sa_family == AF_INET6) {
                        if (*v6 != B_FALSE)
                                continue;

                        if (memcmp(&in6addr_any,
                            &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr,
                            sizeof (struct in6_addr)) == 0)
                                continue;
                        *v6 = B_TRUE;
                }
        }

        freeifaddrs(ifp);
        return (0);
}

/*
 * nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode)
 *      nss2 get hosts/ipnodes with ttl backend DNS search engine.
 *
 * This API is given a pointer to a packed buffer, and the buffer size
 * It's job is to perform the appropriate res_nsearch, extract the
 * results and build a unmarshalled hosts/ipnodes result buffer.
 * Additionally in the extended results a nssuint_t ttl is placed.
 * This ttl is the lessor of the ttl's extracted from the result.
 *
 * RETURNS:  NSS_SUCCESS or NSS_ERROR
 *      If an NSS_ERROR result is returned, nscd is expected
 *      to resubmit the gethosts request using the old style
 *      nsswitch lookup format.
 */

nss_status_t
_nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode)
{
        /* nss buffer variables */
        nss_pheader_t   *pbuf = (nss_pheader_t *)buffer;
        nss_XbyY_args_t arg;
        char            *dbname;
        int             dbop;
        nss_status_t    sret;
        size_t          bsize, blen;
        char            *bptr;
        /* resolver query variables */
        struct __res_state stat, *statp;        /* dns state block */
        union msg {
                uchar_t buf[NS_MAXMSG];         /* max legal DNS answer size */
                HEADER  h;
        } resbuf;
        char aliases[NS_MAXMSG];                /* set of aliases */
        const char      *name;
        int             qtype;
        /* answer parsing variables */
        HEADER          *hp;
        uchar_t         *cp;    /* current location in message */
        uchar_t         *bom;   /* start of message */
        uchar_t         *eom;   /* end of message */
        uchar_t         *eor;   /* end of record */
        int             ancount, qdcount;
        int             type, class;
        nssuint_t       nttl, ttl, *pttl;       /* The purpose of this API */
        int             n, ret;
        const char      *np;
        /* temporary buffers */
        char            nbuf[INET6_ADDRSTRLEN]; /* address parser */
        char            host[MAXHOSTNAMELEN];   /* result host name */
        char            ans[MAXHOSTNAMELEN];    /* record name */
        char            aname[MAXHOSTNAMELEN];  /* alias result (C_NAME) */
        /* misc variables */
        int             af;
        char            *ap, *apc;
        int             hlen = 0, alen, iplen, len, isans;
        boolean_t       has_v4 = B_FALSE, has_v6 = B_FALSE;
        int             flags, family, pass2 = 0;

        statp = &stat;
        (void) memset(statp, '\0', sizeof (struct __res_state));
        if (res_ninit(statp) == -1) {
                return (NSS_ERROR);
        }

        ap = apc = (char *)aliases;
        alen = 0;
        ttl = (nssuint_t)0xFFFFFFF;             /* start w/max, find smaller */

        /* save space for ttl otherwise, why bother... */
        bsize = pbuf->data_len - sizeof (nssuint_t);
        bptr = (char *)buffer + pbuf->data_off;
        blen = 0;
        sret = nss_packed_getkey(buffer, bufsize, &dbname, &dbop, &arg);
        if (sret != NSS_SUCCESS) {
                res_ndestroy(statp);
                return (NSS_ERROR);
        }

        /*
         * There may be flags set when we are handling ipnode. There are three
         * different values for flags:
         *
         *  o AI_V4MAPPED
         *  o AI_ALL
         *  o AI_ADDRCONFIG
         *
         * The first two only have a meaning when af_family is ipv6. The latter
         * means something in both cases. These flags are documented in
         * getipnodebyname(3SOCKET), though the combinations leave a little
         * something to be desired. It would be great if we could actually use
         * getipnodebyname directly here since it already knows how to handle
         * this kind of logic; however, we're not quite so lucky. Ideally we
         * would add such an interface to libresolv.so.2 to handle this kind of
         * thing, but that's rather painful as well. We'll summarize what has to
         * happen below:
         *
         * AI_ALL is only meaningful when AI_V4MAPPED is also specified. Both
         * are ignored if the family is not AF_INET6
         *
         * family == AF_INET, flags | AI_ADDRCONFIG
         *  - lookup A records iff we have v4 plumbed
         * family == AF_INET, !(flags | AI_ADDRCONFIG)
         *  - lookup A records
         * family == AF_INET6, flags == 0 || flags == AI_ALL
         *  - lookup AAAA records
         * family == AF_INET6, flags | AI_V4MAPPED
         *  - lookup AAAA, if none, lookup A
         * family == AF_INET6, flags | AI_ADDRCONFIG
         *  - lookup AAAA records if ipv6
         * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ALL
         *  - lookup AAAA records, lookup A records
         * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ADDRCONFIG
         *  - lookup AAAA records if ipv6
         *  - If no AAAA && ipv4 exists, lookup A
         * family == AF_INET6, flags | AI_V4MAPPED && flags | AI_ADDRCONFIG &&
         * flags | AI_ALL
         *  - lookup AAAA records if ipv6
         *  - loookup A records if ipv4
         */
        if (ipnode) {
                /* initially only handle the simple cases */
                name = arg.key.ipnode.name;
                flags = arg.key.ipnode.flags;
                family = arg.key.ipnode.af_family;
                if (flags != 0) {
                        /*
                         * Figure out our first pass. We'll determine if we need
                         * to do a second pass afterwards once we successfully
                         * finish our first pass.
                         */
                        if ((flags & AI_ADDRCONFIG) != 0) {
                                if (_nss_has_interfaces(&has_v4, &has_v6) !=
                                    0) {
                                        res_ndestroy(statp);
                                        return (NSS_ERROR);
                                }
                                /* Impossible situations... */
                                if (family == AF_INET && has_v4 == B_FALSE) {
                                        res_ndestroy(statp);
                                        return (NSS_NOTFOUND);
                                }
                                if (family == AF_INET6 && has_v6 == B_FALSE &&
                                    !(flags & AI_V4MAPPED)) {
                                        res_ndestroy(statp);
                                        return (NSS_NOTFOUND);
                                }
                                if (family == AF_INET6 && has_v6)
                                        qtype = T_AAAA;
                                if (family == AF_INET || (family == AF_INET6 &&
                                    has_v6 == B_FALSE && flags & AI_V4MAPPED))
                                        qtype = T_A;
                        } else {
                                has_v4 = has_v6 = B_TRUE;
                                if (family == AF_INET6)
                                        qtype = T_AAAA;
                                else
                                        qtype = T_A;
                        }
                } else {
                        if (family == AF_INET6)
                                qtype = T_AAAA;
                        else
                                qtype = T_A;
                }
        } else {
                name = arg.key.name;
                qtype = T_A;
        }

searchagain:
        ret = res_nsearch(statp, name, C_IN, qtype, resbuf.buf, NS_MAXMSG);
        if (ret == -1) {
                /*
                 * We want to continue on unless we got NO_RECOVERY. Otherwise,
                 * HOST_NOT_FOUND, TRY_AGAIN, and NO_DATA all suggest to me that
                 * we should keep going.
                 */
                if (statp->res_h_errno == NO_RECOVERY) {
                        /* else lookup error - handle in general code */
                        res_ndestroy(statp);
                        return (NSS_ERROR);
                }

                /*
                 * We found something on our first pass. Make sure that we do
                 * not clobber this information. This ultimately means that we
                 * were successful.
                 */
                if (pass2 == 2)
                        goto out;

                /*
                 * If we're on the second pass (eg. we need to check both for A
                 * and AAAA records), or we were only ever doing a search for
                 * one type of record and are not supposed to do a second pass,
                 * then we need to return that we couldn't find anything to the
                 * user.
                 */
                if (pass2 == 1 || flags == 0 || family == AF_INET ||
                    (family == AF_INET6 && !(flags & AI_V4MAPPED))) {
                        pbuf->p_herrno = HOST_NOT_FOUND;
                        pbuf->p_status = NSS_NOTFOUND;
                        pbuf->data_len = 0;
                        res_ndestroy(statp);
                        return (NSS_NOTFOUND);
                }

                /*
                 * If we were only requested to search for flags on an IPv6
                 * interface or we have no IPv4 interface, we stick to only
                 * doing a single pass and bail now.
                 */
                if ((flags & AI_ADDRCONFIG) && !(flags & AI_ALL) &&
                    has_v4 == B_FALSE) {
                        pbuf->p_herrno = HOST_NOT_FOUND;
                        pbuf->p_status = NSS_NOTFOUND;
                        pbuf->data_len = 0;
                        res_ndestroy(statp);
                        return (NSS_NOTFOUND);
                }
                qtype = T_A;
                flags = 0;
                pass2 = 1;
                goto searchagain;
        }

        cp = resbuf.buf;
        hp = (HEADER *)&resbuf.h;
        bom = cp;
        eom = cp + ret;

        ancount = ntohs(hp->ancount);
        qdcount = ntohs(hp->qdcount);
        cp += HFIXEDSZ;
        if (qdcount != 1) {
                res_ndestroy(statp);
                return (NSS_ERROR);
        }
        n = dn_expand(bom, eom, cp, host, MAXHOSTNAMELEN);
        if (n < 0) {
                res_ndestroy(statp);
                return (NSS_ERROR);
        } else
                hlen = strlen(host);
        /* no host name is an error, return */
        if (hlen <= 0) {
                res_ndestroy(statp);
                return (NSS_ERROR);
        }
        cp += n + QFIXEDSZ;
        if (cp > eom) {
                res_ndestroy(statp);
                return (NSS_ERROR);
        }
        while (ancount-- > 0 && cp < eom && blen < bsize) {
                n = dn_expand(bom, eom, cp, ans, MAXHOSTNAMELEN);
                if (n > 0) {
                        /*
                         * Check that the expanded name is either the
                         * name we asked for or a learned alias.
                         */
                        if ((isans = strncasecmp(host, ans, hlen)) != 0 &&
                            (alen == 0 || name_is_alias(aliases, ans)
                            == NSS_NOTFOUND)) {
                                res_ndestroy(statp);
                                return (NSS_ERROR);     /* spoof? */
                        }
                }
                cp += n;
                /* bounds check */
                type = ns_get16(cp);                    /* type */
                cp += INT16SZ;
                class = ns_get16(cp);                   /* class */
                cp += INT16SZ;
                nttl = (nssuint_t)ns_get32(cp);         /* ttl in sec */
                if (nttl < ttl)
                        ttl = nttl;
                cp += INT32SZ;
                n = ns_get16(cp);                       /* len */
                cp += INT16SZ;
                if (class != C_IN) {
                        cp += n;
                        continue;
                }
                eor = cp + n;
                if (type == T_CNAME) {
                        /*
                         * The name looked up is really an alias and the
                         * canonical name should be in the RDATA.
                         * A canonical name may have several aliases but an
                         * alias should only have one canonical name.
                         * However multiple CNAMEs and CNAME chains do exist!
                         *
                         * Just error out on attempted buffer overflow exploit,
                         * generic code will syslog.
                         *
                         */
                        n = dn_expand(bom, eor, cp, aname, MAXHOSTNAMELEN);
                        if (n > 0 && (len = strlen(aname)) > 0) {
                                if (isans == 0) { /* host matched ans. */
                                        /*
                                         * Append host to alias list.
                                         */
                                        if (alen + hlen + 2 > NS_MAXMSG) {
                                                res_ndestroy(statp);
                                                return (NSS_ERROR);
                                        }
                                        *apc++ = ' ';
                                        alen++;
                                        (void) strlcpy(apc, host,
                                            NS_MAXMSG - alen);
                                        alen += hlen;
                                        apc += hlen;
                                }
                                /*
                                 * Overwrite host with canonical name.
                                 */
                                if (strlcpy(host, aname, MAXHOSTNAMELEN) >=
                                    MAXHOSTNAMELEN) {
                                        res_ndestroy(statp);
                                        return (NSS_ERROR);
                                }
                                hlen = len;
                        }
                        cp += n;
                        continue;
                }
                if (type != qtype) {
                        cp += n;
                        continue;
                }
                /* check data size */
                if ((type == T_A && n != INADDRSZ) ||
                    (type == T_AAAA && n != IN6ADDRSZ)) {
                        cp += n;
                        continue;
                }
                af = (type == T_A ? AF_INET : AF_INET6);
                np = inet_ntop(af, (void *)cp, nbuf, INET6_ADDRSTRLEN);
                if (np == NULL) {
                        res_ndestroy(statp);
                        return (NSS_ERROR);
                }
                cp += n;
                /* append IP host aliases to results */
                iplen = strlen(np);
                /* ip <SP> hostname [<SP>][aliases] */
                len = iplen + 2 + hlen + alen;
                if (alen > 0)
                        len++;
                if (blen + len > bsize) {
                        res_ndestroy(statp);
                        return (NSS_ERROR);
                }
                (void) strlcpy(bptr, np, bsize - blen);
                blen += iplen;
                bptr += iplen;
                *bptr++ = ' ';
                blen++;
                (void) strlcpy(bptr, host, bsize - blen);
                blen += hlen;
                bptr += hlen;
                if (alen > 0) {
                        *bptr++ = ' ';
                        blen++;
                        (void) strlcpy(bptr, ap, bsize - blen);
                        blen += alen;
                        bptr += alen;
                }
                *bptr++ = '\n';
                blen++;
        }

        /* Depending on our flags we may need to go back another time. */
        if (qtype == T_AAAA && family == AF_INET6 &&
            ((flags & AI_V4MAPPED) != 0) && ((flags & AI_ALL) != 0) &&
            has_v4 == B_TRUE) {
                qtype = T_A;
                pass2 = 2; /* Indicate that we found data this pass */
                goto searchagain;
        }

out:
        /* Presumably the buffer is now filled. */
        len = ROUND_UP(blen, sizeof (nssuint_t));
        /* still room? */
        if (len + sizeof (nssuint_t) > pbuf->data_len) {
                /* sigh, no, what happened? */
                res_ndestroy(statp);
                return (NSS_ERROR);
        }

        pbuf->ext_off = pbuf->data_off + len;
        pbuf->ext_len = sizeof (nssuint_t);
        pbuf->data_len = blen;
        pttl = (nssuint_t *)((void *)((char *)pbuf + pbuf->ext_off));
        *pttl = ttl;
        res_ndestroy(statp);
        return (NSS_SUCCESS);
}