/*
 * 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.
 */

/*
 *	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(from_list, to_list, aliaspp, type, count, af_type)
	char	**from_list, **to_list,	**aliaspp;
	int	type, *count, 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(he, argp, af_type)
	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;
	}

	/* 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);
	}
out:
	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);
}