Subject: Re: Extending ipv4/6 support for nsswitch?
To: Bernd Ernesti <netbsd@arresum.inka.de>
From: None <itojun@iijlab.net>
List: tech-net
Date: 02/15/2000 22:36:26
>>the current search path for ipv4/6 is fixed and has some problems
>>when you only want a ipv4 search for dns entries.
>>What about extending the syntax so you can specify that you want
>>a filesIPv4, then a dnsIPv4 and if that all fails a filesIPv6 and
>>dnsIPv6 lookups?
>>And keep the old behaviour when you use files and dns.
>>hosts:	files4 dns4 files6 dns4
>>Hmmm, is it possible to get ipv6 entries fron nis?
>	The above does not really help.  nsdispatch() will be given specific
>	address family from gethostbyname2() and obeys that.
>	I'm trying to address the issue, by having _dns_gethaddr() variant
>	for use from getaddrinfo().  I think we don't need to impose
>	nsdispatch() and nsswitch.conf, thanks to very generic code in
>	nsdispatch().  I'll try to commit it sooner.

	The patch should fix the following problems:
	- getaddrinfo(3) now behaves nsswitch.conf search order.
	  if ai_family = PF_UNSPEC, and order is "files dns", it would do
	  the following:
		lookup /etc/hosts for any of matching entry.
		return all entries that match hostname.

		if nothing is in /etc/hosts, query DNS with T_ANY
		(like "dig hostname any").  return all T_A/T_AAAA entries.
	  So, if we have the following entries in /etc/hosts, they will all
	  be returned against "localhost" query.
		127.0.0.1	localhost
		::1		localhost
	- allow extended scoped address syntax (like lo0%fe80::1) in /etc/hosts.

	I admit there are too many similar code with gethnamaddr.c.
	I hope to clean them up a bit but I don't think I can fully merge them.
	(I fear to break gethostby*)

	After I fix the following, I think of committing it.
	- add NIS case
	- sync with extended scoped address syntax change
	  (really sorry about the mess, this is based on ongoing draft.
	  now fe80::1%lo0)

itojun


---
Index: getaddrinfo.c
===================================================================
RCS file: /cvsroot/basesrc/lib/libc/net/getaddrinfo.c,v
retrieving revision 1.28
diff -u -r1.28 getaddrinfo.c
--- getaddrinfo.c	2000/02/10 03:06:53	1.28
+++ getaddrinfo.c	2000/02/15 13:30:43
@@ -69,6 +69,16 @@
 #include <stdio.h>
 #include <errno.h>
 
+#include <syslog.h>
+#include <stdarg.h>
+#include <nsswitch.h>
+
+#ifdef YP
+#include <rpc/rpc.h>
+#include <rpcsvc/yp_prot.h>
+#include <rpcsvc/ypclnt.h>
+#endif
+
 #define SUCCESS 0
 #define ANY 0
 #define YES 1
@@ -135,6 +145,9 @@
 	{ PF_INET, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
 	{ PF_INET, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
 	{ PF_INET, SOCK_RAW, ANY, NULL, 0x05 },
+	{ PF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, "udp", 0x07 },
+	{ PF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, "tcp", 0x07 },
+	{ PF_UNSPEC, SOCK_RAW, ANY, NULL, 0x05 },
 	{ -1, 0, 0, NULL, 0 },
 };
 
@@ -144,6 +157,22 @@
 #define PTON_MAX	4
 #endif
 
+static const ns_src default_dns_files[] = {
+	{ NSSRC_FILES, 	NS_SUCCESS },
+	{ NSSRC_DNS, 	NS_SUCCESS },
+	{ 0 }
+};
+
+#if PACKETSZ > 1024
+#define MAXPACKET	PACKETSZ
+#else
+#define MAXPACKET	1024
+#endif
+
+typedef union {
+	HEADER hdr;
+	u_char buf[MAXPACKET];
+} querybuf;
 
 static int str_isnumber __P((const char *));
 static int explore_fqdn __P((const struct addrinfo *, const char *,
@@ -166,6 +195,14 @@
 static int ip6_str2scopeid __P((char *, struct sockaddr_in6 *));
 #endif 
 
+static struct addrinfo *getanswer __P((const querybuf *, int, const char *, int,
+	const struct addrinfo *, int *));
+static int _dns_getaddrinfo __P((void *, void *, va_list));
+static void _sethtent __P((void));
+static void _endhtent __P((void));
+static struct addrinfo *_gethtent __P((const char *, const struct addrinfo *));
+static int _files_getaddrinfo __P((void *, void *, va_list));
+
 static char *ai_errlist[] = {
 	"Success",
 	"Address family for hostname not supported",	/* EAI_ADDRFAMILY */
@@ -274,10 +311,9 @@
 	struct addrinfo ai;
 	struct addrinfo ai0;
 	struct addrinfo *pai;
-	const struct afd *afd;
 	const struct explore *ex;
 
-	sentinel.ai_next = NULL;
+	memset(&sentinel, 0, sizeof(sentinel));
 	cur = &sentinel;
 	pai = &ai;
 	pai->ai_flags = 0;
@@ -362,6 +398,10 @@
 	for (ex = explore; ex->e_af >= 0; ex++) {
 		*pai = ai0;
 
+		/* PF_UNSPEC entries are prepared for DNS queries only */
+		if (ex->e_af == PF_UNSPEC)
+			continue;
+
 		if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
 			continue;
 		if (!MATCH(pai->ai_socktype, ex->e_socktype, WILD_SOCKTYPE(ex)))
@@ -406,42 +446,32 @@
 	 * we would like to prefer AF_INET6 than AF_INET, so we'll make a
 	 * outer loop by AFs.
 	 */
-	for (afd = afdl; afd->a_af; afd++) {
+	for (ex = explore; ex->e_af >= 0; ex++) {
 		*pai = ai0;
 
-		if (!MATCH_FAMILY(pai->ai_family, afd->a_af, 1))
+		/* require exact match for family field */
+		if (pai->ai_family != ex->e_af)
 			continue;
-
-		for (ex = explore; ex->e_af >= 0; ex++) {
-			*pai = ai0;
-
-			if (pai->ai_family == PF_UNSPEC)
-				pai->ai_family = afd->a_af;
 
-			if (!MATCH_FAMILY(pai->ai_family, ex->e_af, WILD_AF(ex)))
-				continue;
-			if (!MATCH(pai->ai_socktype, ex->e_socktype,
-					WILD_SOCKTYPE(ex))) {
-				continue;
-			}
-			if (!MATCH(pai->ai_protocol, ex->e_protocol,
-					WILD_PROTOCOL(ex))) {
-				continue;
-			}
+		if (!MATCH(pai->ai_socktype, ex->e_socktype,
+				WILD_SOCKTYPE(ex))) {
+			continue;
+		}
+		if (!MATCH(pai->ai_protocol, ex->e_protocol,
+				WILD_PROTOCOL(ex))) {
+			continue;
+		}
 
-			if (pai->ai_family == PF_UNSPEC)
-				pai->ai_family = ex->e_af;
-			if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
-				pai->ai_socktype = ex->e_socktype;
-			if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
-				pai->ai_protocol = ex->e_protocol;
+		if (pai->ai_socktype == ANY && ex->e_socktype != ANY)
+			pai->ai_socktype = ex->e_socktype;
+		if (pai->ai_protocol == ANY && ex->e_protocol != ANY)
+			pai->ai_protocol = ex->e_protocol;
 
-			error = explore_fqdn(pai, hostname, servname,
-				&cur->ai_next);
+		error = explore_fqdn(pai, hostname, servname,
+			&cur->ai_next);
 
-			while (cur && cur->ai_next)
-				cur = cur->ai_next;
-		}
+		while (cur && cur->ai_next)
+			cur = cur->ai_next;
 	}
 
 	/* XXX */
@@ -476,20 +506,19 @@
 	const char *servname;
 	struct addrinfo **res;
 {
-	struct hostent *hp;
-	int h_error;
-	int af;
-	char **aplist = NULL, *apbuf = NULL;
-	char *ap;
-	struct addrinfo sentinel, *cur;
-	int i;
-	int naddrs;
-	const struct afd *afd;
+	struct addrinfo *result;
+	struct addrinfo *cur;
 	int error = 0;
+	static const ns_dtab dtab[] = {
+		NS_FILES_CB(_files_getaddrinfo, NULL)
+		{ NSSRC_DNS, _dns_getaddrinfo, NULL },	/* force -DHESIOD */
+#if 0
+		NS_NIS_CB(_yp_gethtbyname, NULL)
+#endif
+		{ 0 }
+	};
 
-	*res = NULL;
-	sentinel.ai_next = NULL;
-	cur = &sentinel;
+	result = NULL;
 
 	/*
 	 * If AI_ADDRCONFIG is specified, check if we are expected to
@@ -504,99 +533,22 @@
 	if (get_portmatch(pai, servname) != 0)
 		return 0;
 
-	afd = find_afd(pai->ai_family);
-
-	hp = gethostbyname2(hostname, pai->ai_family);
-	h_error = h_errno;
-
-	if (hp == NULL) {
-		switch (h_error) {
-		case HOST_NOT_FOUND:
-		case NO_DATA:
-			error = EAI_NODATA;
-			break;
-		case TRY_AGAIN:
-			error = EAI_AGAIN;
-			break;
-		case NO_RECOVERY:
-		case NETDB_INTERNAL:
-		default:
-			error = EAI_FAIL;
-			break;
-		}
-	} else if ((hp->h_name == NULL) || (hp->h_name[0] == 0)
-			|| (hp->h_addr_list[0] == NULL)) {
-		hp = NULL;
+	if (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
+	    default_dns_files, hostname, pai) != NS_SUCCESS)
 		error = EAI_FAIL;
-	}
-
-	if (hp == NULL)
-		goto free;
-
-	/*
-	 * hp will be overwritten if we use gethostbyname2().
-	 * always deep copy for simplification.
-	 */
-	for (naddrs = 0; hp->h_addr_list[naddrs] != NULL; naddrs++)
-		;
-	naddrs++;
-	aplist = (char **)malloc(sizeof(aplist[0]) * naddrs);
-	apbuf = (char *)malloc((size_t)hp->h_length * naddrs);
-	if (aplist == NULL || apbuf == NULL) {
-		error = EAI_MEMORY;
-		goto free;
-	}
-	memset(aplist, 0, sizeof(aplist[0]) * naddrs);
-	for (i = 0; i < naddrs; i++) {
-		if (hp->h_addr_list[i] == NULL) {
-			aplist[i] = NULL;
-			continue;
+	else {
+		error = 0;
+		for (cur = result; cur; cur = cur->ai_next) {
+			GET_PORT(cur, servname);
+			/* canonname should be filled already */
 		}
-		memcpy(&apbuf[i * hp->h_length], hp->h_addr_list[i],
-			(size_t)hp->h_length);
-		aplist[i] = &apbuf[i * hp->h_length];
 	}
-
-	for (i = 0; aplist[i] != NULL; i++) {
-		af = hp->h_addrtype;
-		ap = aplist[i];
-#ifdef AF_INET6
-		if (af == AF_INET6
-		 && IN6_IS_ADDR_V4MAPPED((struct in6_addr *)ap)) {
-			af = AF_INET;
-			ap = ap + sizeof(struct in6_addr)
-				- sizeof(struct in_addr);
-		}
-#endif
-
-		if (af != pai->ai_family)
-			continue;
-
-		GET_AI(cur->ai_next, afd, ap);
-		GET_PORT(cur->ai_next, servname);
-		if ((pai->ai_flags & AI_CANONNAME) != 0) {
-			/*
-			 * RFC2553 says that ai_canonname will be set only for
-			 * the first element.  we do it for all the elements,
-			 * just for convenience.
-			 */
-			GET_CANONNAME(cur->ai_next, hp->h_name);
-		}
 
-		while (cur && cur->ai_next)
-			cur = cur->ai_next;
-	}
+	*res = result;
 
-	*res = sentinel.ai_next;
 	return 0;
 
 free:
-	if (aplist)
-		free(aplist);
-	if (apbuf)
-		free(apbuf);
-	if (sentinel.ai_next)
-		freeaddrinfo(sentinel.ai_next);
 	return error;
 }
 
@@ -1001,3 +953,353 @@
 		return -1;
 }
 #endif 
+
+/* XXX code duplicate with gethnamaddr.c */
+
+static const char AskedForGot[] =
+	"gethostby*.getanswer: asked for \"%s\", got \"%s\"";
+static FILE *hostf = NULL;
+
+static struct addrinfo *
+getanswer(answer, anslen, qname, qtype, pai, errp)
+	const querybuf *answer;
+	int anslen;
+	const char *qname;
+	int qtype;
+	const struct addrinfo *pai;
+	int *errp;
+{
+	struct addrinfo sentinel, *cur;
+	struct addrinfo ai;
+	const struct afd *afd;
+	char *canonname;
+	const HEADER *hp;
+	const u_char *cp;
+	int n;
+	const u_char *eom;
+	char *bp;
+	int type, class, buflen, ancount, qdcount;
+	int haveanswer, had_error;
+	char tbuf[MAXDNAME];
+	const char *tname;
+	int (*name_ok) __P((const char *));
+	char hostbuf[8*1024];
+
+	memset(&sentinel, 0, sizeof(sentinel));
+	cur = &sentinel;
+
+	tname = qname;
+	canonname = NULL;
+	eom = answer->buf + anslen;
+	switch (qtype) {
+	case T_A:
+	case T_AAAA:
+	case T_ANY:	/*use T_ANY only for T_A/T_AAAA lookup*/
+		name_ok = res_hnok;
+		break;
+	default:
+		return (NULL);	/* XXX should be abort(); */
+	}
+	/*
+	 * find first satisfactory answer
+	 */
+	hp = &answer->hdr;
+	ancount = ntohs(hp->ancount);
+	qdcount = ntohs(hp->qdcount);
+	bp = hostbuf;
+	buflen = sizeof hostbuf;
+	cp = answer->buf + HFIXEDSZ;
+	if (qdcount != 1) {
+		*errp = NO_RECOVERY;
+		return (NULL);
+	}
+	n = dn_expand(answer->buf, eom, cp, bp, buflen);
+	if ((n < 0) || !(*name_ok)(bp)) {
+		*errp = NO_RECOVERY;
+		return (NULL);
+	}
+	cp += n + QFIXEDSZ;
+	if (qtype == T_A || qtype == T_AAAA || qtype == T_ANY) {
+		/* res_send() has already verified that the query name is the
+		 * same as the one we sent; this just gets the expanded name
+		 * (i.e., with the succeeding search-domain tacked on).
+		 */
+		n = strlen(bp) + 1;		/* for the \0 */
+		if (n >= MAXHOSTNAMELEN) {
+			*errp = NO_RECOVERY;
+			return (NULL);
+		}
+		canonname = bp;
+		bp += n;
+		buflen -= n;
+		/* The qname can be abbreviated, but h_name is now absolute. */
+		qname = canonname;
+	}
+	haveanswer = 0;
+	had_error = 0;
+	while (ancount-- > 0 && cp < eom && !had_error) {
+		n = dn_expand(answer->buf, eom, cp, bp, buflen);
+		if ((n < 0) || !(*name_ok)(bp)) {
+			had_error++;
+			continue;
+		}
+		cp += n;			/* name */
+		type = _getshort(cp);
+ 		cp += INT16SZ;			/* type */
+		class = _getshort(cp);
+ 		cp += INT16SZ + INT32SZ;	/* class, TTL */
+		n = _getshort(cp);
+		cp += INT16SZ;			/* len */
+		if (class != C_IN) {
+			/* XXX - debug? syslog? */
+			cp += n;
+			continue;		/* XXX - had_error++ ? */
+		}
+		if ((qtype == T_A || qtype == T_AAAA || qtype == T_ANY) &&
+		    type == T_CNAME) {
+			n = dn_expand(answer->buf, eom, cp, tbuf, sizeof tbuf);
+			if ((n < 0) || !(*name_ok)(tbuf)) {
+				had_error++;
+				continue;
+			}
+			cp += n;
+			/* Get canonical name. */
+			n = strlen(tbuf) + 1;	/* for the \0 */
+			if (n > buflen || n >= MAXHOSTNAMELEN) {
+				had_error++;
+				continue;
+			}
+			strcpy(bp, tbuf);
+			canonname = bp;
+			bp += n;
+			buflen -= n;
+			continue;
+		}
+		if (qtype == T_ANY) {
+			if (!(type == T_A || type == T_AAAA)) {
+				cp += n;
+				continue;
+			}
+		} else if (type != qtype) {
+			if (type != T_KEY && type != T_SIG)
+				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++ ? */
+		}
+		switch (type) {
+		case T_A:
+		case T_AAAA:
+			if (strcasecmp(canonname, bp) != 0) {
+				syslog(LOG_NOTICE|LOG_AUTH,
+				       AskedForGot, canonname, bp);
+				cp += n;
+				continue;	/* XXX - had_error++ ? */
+			}
+			if (type == T_A && n != INADDRSZ) {
+				cp += n;
+				continue;
+			}
+			if (type == T_AAAA && n != IN6ADDRSZ) {
+				cp += n;
+				continue;
+			}
+			if (!haveanswer) {
+				int nn;
+
+				canonname = bp;
+				nn = strlen(bp) + 1;	/* for the \0 */
+				bp += nn;
+				buflen -= nn;
+			}
+
+			/* don't overwrite pai */
+			ai = *pai;
+			ai.ai_family = (type == T_A) ? AF_INET : AF_INET6;
+			afd = find_afd(ai.ai_family);
+			if (afd == NULL) {
+				cp += n;
+				continue;
+			}
+			cur->ai_next = get_ai(&ai, afd, cp);
+			if (cur->ai_next == NULL)
+				had_error++;
+			while (cur && cur->ai_next)
+				cur = cur->ai_next;
+			cp += n;
+			break;
+		default:
+			abort();
+		}
+		if (!had_error)
+			haveanswer++;
+	}
+	if (haveanswer) {
+		if (!canonname)
+			(void)get_canonname(pai, sentinel.ai_next, qname);
+		else
+			(void)get_canonname(pai, sentinel.ai_next, canonname);
+		return sentinel.ai_next;
+	}
+
+	*errp = NO_RECOVERY;
+	return NULL;
+}
+
+/*ARGSUSED*/
+static int
+_dns_getaddrinfo(rv, cb_data, ap)
+	void	*rv;
+	void	*cb_data;
+	va_list	 ap;
+{
+	struct addrinfo *ai;
+	querybuf buf;
+	int n, type;
+	const char *name;
+	const struct addrinfo *pai;
+	int error;
+
+	name = va_arg(ap, char *);
+	pai = va_arg(ap, const struct addrinfo *);
+
+	switch (pai->ai_family) {
+	case AF_INET:
+		type = T_A;
+		break;
+	case AF_INET6:
+		type = T_AAAA;
+		break;
+	case AF_UNSPEC:
+		type = T_ANY;
+		break;
+	default:
+		return NS_UNAVAIL;
+	}
+	if ((n = res_search(name, C_IN, type, buf.buf, sizeof(buf))) < 0)
+		return NS_NOTFOUND;
+	ai = getanswer(&buf, n, name, type, pai, &error);
+	if (ai == NULL)
+		switch (error) {
+		case HOST_NOT_FOUND:
+			return NS_NOTFOUND;
+		case TRY_AGAIN:
+			return NS_TRYAGAIN;
+		default:
+			return NS_UNAVAIL;
+		}
+	*((struct addrinfo **)rv) = ai;
+	return NS_SUCCESS;
+}
+
+static void
+_sethtent()
+{
+	if (!hostf)
+		hostf = fopen(_PATH_HOSTS, "r" );
+	else
+		rewind(hostf);
+}
+
+static void
+_endhtent()
+{
+	if (hostf) {
+		(void) fclose(hostf);
+		hostf = NULL;
+	}
+}
+
+static struct addrinfo *
+_gethtent(name, pai)
+	const char *name;
+	const struct addrinfo *pai;
+{
+	char *p;
+	char *cp, *tname;
+	struct addrinfo hints, *res0, *res;
+	int error;
+	const char *addr;
+	char hostbuf[8*1024];
+
+	if (!hostf && !(hostf = fopen(_PATH_HOSTS, "r" )))
+		return (NULL);
+ again:
+	if (!(p = fgets(hostbuf, sizeof hostbuf, hostf)))
+		return (NULL);
+	if (*p == '#')
+		goto again;
+	if (!(cp = strpbrk(p, "#\n")))
+		goto again;
+	*cp = '\0';
+	if (!(cp = strpbrk(p, " \t")))
+		goto again;
+	*cp++ = '\0';
+	addr = p;
+	/* if this is not something we're looking for, skip it. */
+	while (cp && *cp) {
+		if (*cp == ' ' || *cp == '\t') {
+			cp++;
+			continue;
+		}
+		tname = cp;
+		if ((cp = strpbrk(cp, " \t")) != NULL)
+			*cp++ = '\0';
+		if (strcasecmp(name, tname) == 0)
+			goto found;
+	}
+	goto again;
+
+found:
+	hints = *pai;
+	hints.ai_flags = AI_NUMERICHOST;
+	error = getaddrinfo(addr, NULL, &hints, &res0);
+	if (error)
+		goto again;
+	for (res = res0; res; res = res->ai_next) {
+		/* cover it up */
+		res->ai_flags = pai->ai_flags;
+
+		if (pai->ai_flags & AI_CANONNAME) {
+			if (get_canonname(pai, res, name) != 0) {
+				freeaddrinfo(res0);
+				goto again;
+			}
+		}
+	}
+	return res0;
+}
+
+/*ARGSUSED*/
+static int
+_files_getaddrinfo(rv, cb_data, ap)
+	void	*rv;
+	void	*cb_data;
+	va_list	 ap;
+{
+	const char *name;
+	const struct addrinfo *pai;
+	struct addrinfo sentinel, *cur;
+	struct addrinfo *p;
+
+	name = va_arg(ap, char *);
+	pai = va_arg(ap, struct addrinfo *);
+
+	memset(&sentinel, 0, sizeof(sentinel));
+	cur = &sentinel;
+
+	_sethtent();
+	while ((p = _gethtent(name, pai)) != NULL) {
+		cur->ai_next = p;
+		while (cur && cur->ai_next)
+			cur = cur->ai_next;
+	}
+	_endhtent();
+
+	*((struct addrinfo **)rv) = sentinel.ai_next;
+	if (sentinel.ai_next == NULL)
+		return NS_NOTFOUND;
+	return NS_SUCCESS;
+}