1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  *	dns_common.c
28  */
29 
30 #include "dns_common.h"
31 
32 #pragma weak	dn_expand
33 #pragma weak	res_ninit
34 #pragma weak	res_nsearch
35 #pragma weak	res_nclose
36 #pragma weak	ns_get16
37 #pragma weak	ns_get32
38 #pragma weak	__ns_get16
39 #pragma weak	__ns_get32
40 
41 #define	DNS_ALIASES	0
42 #define	DNS_ADDRLIST	1
43 #define	DNS_MAPDLIST	2
44 
45 #ifndef	tolower
46 #define	tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) | 0x20 : (c))
47 #endif
48 
49 static int
50 dns_netdb_aliases(from_list, to_list, aliaspp, type, count, af_type)
51 	char	**from_list, **to_list,	**aliaspp;
52 	int	type, *count, af_type;
53 {
54 	char		*fstr;
55 	int		cnt = 0;
56 	size_t		len;
57 
58 	*count = 0;
59 	if ((char *)to_list >= *aliaspp)
60 		return (NSS_STR_PARSE_ERANGE);
61 
62 	for (fstr = from_list[cnt]; fstr != NULL; fstr = from_list[cnt]) {
63 		if (type == DNS_ALIASES)
64 			len = strlen(fstr) + 1;
65 		else
66 			len = (af_type == AF_INET) ? sizeof (struct in_addr)
67 						: sizeof (struct in6_addr);
68 		*aliaspp -= len;
69 		to_list[cnt] = *aliaspp;
70 		if (*aliaspp <= (char *)&to_list[cnt+1])
71 			return (NSS_STR_PARSE_ERANGE);
72 		if (type == DNS_MAPDLIST) {
73 			/* LINTED: E_BAD_PTR_CAST_ALIGN */
74 			struct in6_addr *addr6p = (struct in6_addr *)*aliaspp;
75 
76 			(void) memset(addr6p, '\0', sizeof (struct in6_addr));
77 			(void) memcpy(&addr6p->s6_addr[12], fstr,
78 					sizeof (struct in_addr));
79 			addr6p->s6_addr[10] = 0xffU;
80 			addr6p->s6_addr[11] = 0xffU;
81 			++cnt;
82 		} else {
83 			(void) memcpy (*aliaspp, fstr, len);
84 			++cnt;
85 		}
86 	}
87 	to_list[cnt] = NULL;
88 
89 	*count = cnt;
90 	if (cnt == 0)
91 		return (NSS_STR_PARSE_PARSE);
92 
93 	return (NSS_STR_PARSE_SUCCESS);
94 }
95 
96 
97 int
98 ent2result(he, argp, af_type)
99 	struct hostent		*he;
100 	nss_XbyY_args_t		*argp;
101 	int			af_type;
102 {
103 	char		*buffer, *limit;
104 	int		buflen = argp->buf.buflen;
105 	int		ret, count;
106 	size_t len;
107 	struct hostent 	*host;
108 	struct in_addr	*addrp;
109 	struct in6_addr	*addrp6;
110 
111 	limit = argp->buf.buffer + buflen;
112 	host = (struct hostent *)argp->buf.result;
113 	buffer = argp->buf.buffer;
114 
115 	/* h_addrtype and h_length */
116 	host->h_addrtype = af_type;
117 	host->h_length = (af_type == AF_INET) ? sizeof (struct in_addr)
118 					: sizeof (struct in6_addr);
119 
120 	/* h_name */
121 	len = strlen(he->h_name) + 1;
122 	host->h_name = buffer;
123 	if (host->h_name + len >= limit)
124 		return (NSS_STR_PARSE_ERANGE);
125 	(void) memcpy(host->h_name, he->h_name, len);
126 	buffer += len;
127 
128 	/* h_addr_list */
129 	if (af_type == AF_INET) {
130 		addrp = (struct in_addr *)ROUND_DOWN(limit, sizeof (*addrp));
131 		host->h_addr_list = (char **)
132 				ROUND_UP(buffer, sizeof (char **));
133 		ret = dns_netdb_aliases(he->h_addr_list, host->h_addr_list,
134 			(char **)&addrp, DNS_ADDRLIST, &count, af_type);
135 		if (ret != NSS_STR_PARSE_SUCCESS)
136 			return (ret);
137 		/* h_aliases */
138 		host->h_aliases = host->h_addr_list + count + 1;
139 		ret = dns_netdb_aliases(he->h_aliases, host->h_aliases,
140 			(char **)&addrp, DNS_ALIASES, &count, af_type);
141 	} else {
142 		addrp6 = (struct in6_addr *)
143 			ROUND_DOWN(limit, sizeof (*addrp6));
144 		host->h_addr_list = (char **)
145 			ROUND_UP(buffer, sizeof (char **));
146 		if (he->h_addrtype == AF_INET && af_type == AF_INET6) {
147 			ret = dns_netdb_aliases(he->h_addr_list,
148 				host->h_addr_list, (char **)&addrp6,
149 				DNS_MAPDLIST, &count, af_type);
150 		} else {
151 			ret = dns_netdb_aliases(he->h_addr_list,
152 				host->h_addr_list, (char **)&addrp6,
153 				DNS_ADDRLIST, &count, af_type);
154 		}
155 		if (ret != NSS_STR_PARSE_SUCCESS)
156 			return (ret);
157 		/* h_aliases */
158 		host->h_aliases = host->h_addr_list + count + 1;
159 		ret = dns_netdb_aliases(he->h_aliases, host->h_aliases,
160 			(char **)&addrp6, DNS_ALIASES, &count, af_type);
161 	}
162 	if (ret == NSS_STR_PARSE_PARSE)
163 		ret = NSS_STR_PARSE_SUCCESS;
164 
165 	return (ret);
166 }
167 
168 /*
169  * Convert the hostent structure into string in the following
170  * format:
171  *
172  * IP-address official-host-name nicknames ...
173  *
174  * If more than one IP-addresses matches the official-host-name,
175  * the above line will be followed by:
176  * IP-address-1 official-host-name
177  * IP-address-2 official-host-name
178  * ...
179  *
180  * This is so that the str2hostent function in libnsl
181  * can convert the string back to the original hostent
182  * data.
183  */
184 int
185 ent2str(
186 	struct hostent	*hp,
187 	nss_XbyY_args_t *ap,
188 	int		af_type)
189 {
190 	char		**p;
191 	char		obuf[INET6_ADDRSTRLEN];
192 	void		*addr;
193 	struct in_addr	in4;
194 	int		af;
195 	int		n;
196 	const char	*res;
197 	char		**q;
198 	int		l = ap->buf.buflen;
199 	char		*s = ap->buf.buffer;
200 
201 	/*
202 	 * for "hosts" lookup, we only want address type of
203 	 * AF_INET. For "ipnodes", we can have both AF_INET
204 	 * and AF_INET6.
205 	 */
206 	if (af_type == AF_INET && hp->h_addrtype != AF_INET)
207 		return (NSS_STR_PARSE_PARSE);
208 
209 	for (p = hp->h_addr_list; *p != 0; p++) {
210 
211 		if (p != hp->h_addr_list) {
212 			*s = '\n';
213 			s++;
214 			l--;
215 		}
216 
217 		if (hp->h_addrtype == AF_INET6) {
218 			/* LINTED: E_BAD_PTR_CAST_ALIGN */
219 			if (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)*p)) {
220 				/* LINTED: E_BAD_PTR_CAST_ALIGN */
221 				IN6_V4MAPPED_TO_INADDR((struct in6_addr *)*p,
222 				    &in4);
223 				af = AF_INET;
224 				addr = &in4;
225 			} else {
226 				af = AF_INET6;
227 				addr = *p;
228 			}
229 		} else {
230 			af = AF_INET;
231 			addr = *p;
232 		}
233 		res = inet_ntop(af, addr, obuf, sizeof (obuf));
234 		if (res == NULL)
235 			return (NSS_STR_PARSE_PARSE);
236 
237 		if ((n = snprintf(s, l, "%s", res)) >= l)
238 			return (NSS_STR_PARSE_ERANGE);
239 		l -= n;
240 		s += n;
241 		if (hp->h_name != NULL && *hp->h_name != '\0') {
242 			if ((n = snprintf(s, l, " %s", hp->h_name)) >= l)
243 				return (NSS_STR_PARSE_ERANGE);
244 			l -= n;
245 			s += n;
246 		}
247 		if (p == hp->h_addr_list) {
248 			for (q = hp->h_aliases; q && *q; q++) {
249 				if ((n = snprintf(s, l, " %s", *q)) >= l)
250 					return (NSS_STR_PARSE_ERANGE);
251 				l -= n;
252 				s += n;
253 			}
254 		}
255 	}
256 
257 	ap->returnlen = s - ap->buf.buffer;
258 	return (NSS_STR_PARSE_SUCCESS);
259 }
260 
261 nss_backend_t *
262 _nss_dns_constr(dns_backend_op_t ops[], int n_ops)
263 {
264 	dns_backend_ptr_t	be;
265 
266 	if ((be = (dns_backend_ptr_t)malloc(sizeof (*be))) == 0)
267 		return (0);
268 
269 	be->ops = ops;
270 	be->n_ops = n_ops;
271 	return ((nss_backend_t *)be);
272 }
273 
274 /*
275  * __res_ndestroy is a simplified version of the non-public function
276  * res_ndestroy in libresolv.so.2. Before res_ndestroy can be made
277  * public, __res_ndestroy will be used to make sure the memory pointed
278  * by statp->_u._ext.ext is freed after res_nclose() is called.
279  */
280 static void
281 __res_ndestroy(res_state statp) {
282 	res_nclose(statp);
283 	if (statp->_u._ext.ext != NULL)
284 		free(statp->_u._ext.ext);
285 }
286 
287 /*
288  * name_is_alias(aliases_ptr, name_ptr)
289  * Verify name matches an alias in the provided aliases list.
290  *
291  * Within DNS there should be only one canonical name, aliases should
292  * all refer to the one canonical.  However alias chains do occur and
293  * pre BIND 9 servers may also respond with multiple CNAMEs.  This
294  * routine checks if a given name has been provided as a CNAME in the
295  * response.  This assumes that the chains have been sent in-order.
296  *
297  * INPUT:
298  *  aliases_ptr: space separated list of alias names.
299  *  name_ptr: name to look for in aliases_ptr list.
300  * RETURNS: NSS_SUCCESS or NSS_NOTFOUND
301  *  NSS_SUCCESS indicates that the name is listed in the collected aliases.
302  */
303 static nss_status_t
304 name_is_alias(char *aliases_ptr, char *name_ptr) {
305 	char *host_ptr;
306 	/* Loop through alias string and compare it against host string. */
307 	while (*aliases_ptr != '\0') {
308 		host_ptr = name_ptr;
309 
310 		/* Compare name with alias. */
311 		while (tolower(*host_ptr) == tolower(*aliases_ptr) &&
312 		    *host_ptr != '\0') {
313 			host_ptr++;
314 			aliases_ptr++;
315 		}
316 
317 		/*
318 		 * If name was exhausted and the next character in the
319 		 * alias is either the end-of-string or space
320 		 * character then we have a match.
321 		 */
322 		if (*host_ptr == '\0' &&
323 		    (*aliases_ptr == '\0' || *aliases_ptr == ' ')) {
324 			return (NSS_SUCCESS);
325 		}
326 
327 		/* Alias did not match, step over remainder of alias. */
328 		while (*aliases_ptr != ' ' && *aliases_ptr != '\0')
329 			aliases_ptr++;
330 		/* Step over separator character. */
331 		while (*aliases_ptr == ' ') aliases_ptr++;
332 	}
333 	return (NSS_NOTFOUND);
334 }
335 
336 /*
337  * nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode)
338  *      nss2 get hosts/ipnodes with ttl backend DNS search engine.
339  *
340  * This API is given a pointer to a packed buffer, and the buffer size
341  * It's job is to perform the appropriate res_nsearch, extract the
342  * results and build a unmarshalled hosts/ipnodes result buffer.
343  * Additionally in the extended results a nssuint_t ttl is placed.
344  * This ttl is the lessor of the ttl's extracted from the result.
345  *
346  * ***Currently the first version of this API only performs simple
347  *    single res_nsearch lookups for with T_A or T_AAAA results.
348  *    Other searches are deferred to the generic API w/t ttls.
349  *
350  *    This function is not a generic res_* operation.  It only performs
351  *    a single T_A or T_AAAA lookups***
352  *
353  * RETURNS:  NSS_SUCCESS or NSS_ERROR
354  *	If an NSS_ERROR result is returned, nscd is expected
355  *	to resubmit the gethosts request using the old style
356  *	nsswitch lookup format.
357  */
358 
359 nss_status_t
360 _nss_dns_gethost_withttl(void *buffer, size_t bufsize, int ipnode)
361 {
362 	/* nss buffer variables */
363 	nss_pheader_t	*pbuf = (nss_pheader_t *)buffer;
364 	nss_XbyY_args_t	arg;
365 	char		*dbname;
366 	int		dbop;
367 	nss_status_t	sret;
368 	size_t		bsize, blen;
369 	char		*bptr;
370 	/* resolver query variables */
371 	struct __res_state stat, *statp;	/* dns state block */
372 	union msg {
373 		uchar_t	buf[NS_MAXMSG];		/* max legal DNS answer size */
374 		HEADER	h;
375 	} resbuf;
376 	char aliases[NS_MAXMSG];		/* set of aliases */
377 	const char	*name;
378 	int		qtype;
379 	/* answer parsing variables */
380 	HEADER		*hp;
381 	uchar_t		*cp;	/* current location in message */
382 	uchar_t		*bom;	/* start of message */
383 	uchar_t		*eom;	/* end of message */
384 	uchar_t		*eor;	/* end of record */
385 	int		ancount, qdcount;
386 	int		type, class;
387 	nssuint_t	nttl, ttl, *pttl;	/* The purpose of this API */
388 	int		n, ret;
389 	const char	*np;
390 	/* temporary buffers */
391 	char		nbuf[INET6_ADDRSTRLEN];	/* address parser */
392 	char		host[MAXHOSTNAMELEN];	/* result host name */
393 	char		ans[MAXHOSTNAMELEN];	/* record name */
394 	char		aname[MAXHOSTNAMELEN];	/* alias result (C_NAME) */
395 	/* misc variables */
396 	int		af;
397 	char		*ap, *apc;
398 	int		hlen = 0, alen, iplen, len, isans;
399 
400 	statp = &stat;
401 	(void) memset(statp, '\0', sizeof (struct __res_state));
402 	if (res_ninit(statp) == -1)
403 		return (NSS_ERROR);
404 
405 	ap = apc = (char *)aliases;
406 	alen = 0;
407 	ttl = (nssuint_t)0xFFFFFFF;		/* start w/max, find smaller */
408 
409 	/* save space for ttl otherwise, why bother... */
410 	bsize = pbuf->data_len - sizeof (nssuint_t);
411 	bptr = (char *)buffer + pbuf->data_off;
412 	blen = 0;
413 	sret = nss_packed_getkey(buffer, bufsize, &dbname, &dbop, &arg);
414 	if (sret != NSS_SUCCESS) {
415 		__res_ndestroy(statp);
416 		return (NSS_ERROR);
417 	}
418 
419 	if (ipnode) {
420 		/* initially only handle the simple cases */
421 		if (arg.key.ipnode.flags != 0) {
422 			__res_ndestroy(statp);
423 			return (NSS_ERROR);
424 		}
425 		name = arg.key.ipnode.name;
426 		if (arg.key.ipnode.af_family == AF_INET6)
427 			qtype = T_AAAA;
428 		else
429 			qtype = T_A;
430 	} else {
431 		name = arg.key.name;
432 		qtype = T_A;
433 	}
434 	ret = res_nsearch(statp, name, C_IN, qtype, resbuf.buf, NS_MAXMSG);
435 	if (ret == -1) {
436 		if (statp->res_h_errno == HOST_NOT_FOUND) {
437 			pbuf->p_herrno = HOST_NOT_FOUND;
438 			pbuf->p_status = NSS_NOTFOUND;
439 			pbuf->data_len = 0;
440 			__res_ndestroy(statp);
441 			return (NSS_NOTFOUND);
442 		}
443 		/* else lookup error - handle in general code */
444 		__res_ndestroy(statp);
445 		return (NSS_ERROR);
446 	}
447 
448 	cp = resbuf.buf;
449 	hp = (HEADER *)&resbuf.h;
450 	bom = cp;
451 	eom = cp + ret;
452 
453 	ancount = ntohs(hp->ancount);
454 	qdcount = ntohs(hp->qdcount);
455 	cp += HFIXEDSZ;
456 	if (qdcount != 1) {
457 		__res_ndestroy(statp);
458 		return (NSS_ERROR);
459 	}
460 	n = dn_expand(bom, eom, cp, host, MAXHOSTNAMELEN);
461 	if (n < 0) {
462 		__res_ndestroy(statp);
463 		return (NSS_ERROR);
464 	} else
465 		hlen = strlen(host);
466 	/* no host name is an error, return */
467 	if (hlen <= 0) {
468 		__res_ndestroy(statp);
469 		return (NSS_ERROR);
470 	}
471 	cp += n + QFIXEDSZ;
472 	if (cp > eom) {
473 		__res_ndestroy(statp);
474 		return (NSS_ERROR);
475 	}
476 	while (ancount-- > 0 && cp < eom && blen < bsize) {
477 		n = dn_expand(bom, eom, cp, ans, MAXHOSTNAMELEN);
478 		if (n > 0) {
479 			/*
480 			 * Check that the expanded name is either the
481 			 * name we asked for or a learned alias.
482 			 */
483 			if ((isans = strncasecmp(host, ans, hlen)) != 0 &&
484 			    (alen == 0 || name_is_alias(aliases, ans)
485 			    == NSS_NOTFOUND)) {
486 				__res_ndestroy(statp);
487 				return (NSS_ERROR);	/* spoof? */
488 			}
489 		}
490 		cp += n;
491 		/* bounds check */
492 		type = ns_get16(cp);			/* type */
493 		cp += INT16SZ;
494 		class = ns_get16(cp);			/* class */
495 		cp += INT16SZ;
496 		nttl = (nssuint_t)ns_get32(cp);		/* ttl in sec */
497 		if (nttl < ttl)
498 			ttl = nttl;
499 		cp += INT32SZ;
500 		n = ns_get16(cp);			/* len */
501 		cp += INT16SZ;
502 		if (class != C_IN) {
503 			cp += n;
504 			continue;
505 		}
506 		eor = cp + n;
507 		if (type == T_CNAME) {
508 			/*
509 			 * The name looked up is really an alias and the
510 			 * canonical name should be in the RDATA.
511 			 * A canonical name may have several aliases but an
512 			 * alias should only have one canonical name.
513 			 * However multiple CNAMEs and CNAME chains do exist!
514 			 *
515 			 * Just error out on attempted buffer overflow exploit,
516 			 * generic code will syslog.
517 			 *
518 			 */
519 			n = dn_expand(bom, eor, cp, aname, MAXHOSTNAMELEN);
520 			if (n > 0 && (len = strlen(aname)) > 0) {
521 				if (isans == 0) { /* host matched ans. */
522 					/*
523 					 * Append host to alias list.
524 					 */
525 					if (alen + hlen + 2 > NS_MAXMSG) {
526 						__res_ndestroy(statp);
527 						return (NSS_ERROR);
528 					}
529 					*apc++ = ' ';
530 					alen++;
531 					(void) strlcpy(apc, host,
532 					    NS_MAXMSG - alen);
533 					alen += hlen;
534 					apc += hlen;
535 				}
536 				/*
537 				 * Overwrite host with canonical name.
538 				 */
539 				if (strlcpy(host, aname, MAXHOSTNAMELEN) >=
540 				    MAXHOSTNAMELEN) {
541 					__res_ndestroy(statp);
542 					return (NSS_ERROR);
543 				}
544 				hlen = len;
545 			}
546 			cp += n;
547 			continue;
548 		}
549 		if (type != qtype) {
550 			cp += n;
551 			continue;
552 		}
553 		/* check data size */
554 		if ((type == T_A && n != INADDRSZ) ||
555 		    (type == T_AAAA && n != IN6ADDRSZ)) {
556 			cp += n;
557 			continue;
558 		}
559 		af = (type == T_A ? AF_INET : AF_INET6);
560 		np = inet_ntop(af, (void *)cp, nbuf, INET6_ADDRSTRLEN);
561 		if (np == NULL) {
562 			__res_ndestroy(statp);
563 			return (NSS_ERROR);
564 		}
565 		cp += n;
566 		/* append IP host aliases to results */
567 		iplen = strlen(np);
568 		/* ip <SP> hostname [<SP>][aliases] */
569 		len = iplen + 2 + hlen + alen;
570 		if (alen > 0)
571 			len++;
572 		if (blen + len > bsize) {
573 			__res_ndestroy(statp);
574 			return (NSS_ERROR);
575 		}
576 		(void) strlcpy(bptr, np, bsize - blen);
577 		blen += iplen;
578 		bptr += iplen;
579 		*bptr++ = ' ';
580 		blen++;
581 		(void) strlcpy(bptr, host, bsize - blen);
582 		blen += hlen;
583 		bptr += hlen;
584 		if (alen > 0) {
585 			*bptr++ = ' ';
586 			blen++;
587 			(void) strlcpy(bptr, ap, bsize - blen);
588 			blen += alen;
589 			bptr += alen;
590 		}
591 		*bptr++ = '\n';
592 		blen++;
593 	}
594 	/* Presumably the buffer is now filled. */
595 	len = ROUND_UP(blen, sizeof (nssuint_t));
596 	/* still room? */
597 	if (len + sizeof (nssuint_t) > pbuf->data_len) {
598 		/* sigh, no, what happened? */
599 		__res_ndestroy(statp);
600 		return (NSS_ERROR);
601 	}
602 	pbuf->ext_off = pbuf->data_off + len;
603 	pbuf->ext_len = sizeof (nssuint_t);
604 	pbuf->data_len = blen;
605 	pttl = (nssuint_t *)((void *)((char *)pbuf + pbuf->ext_off));
606 	*pttl = ttl;
607 	__res_ndestroy(statp);
608 	return (NSS_SUCCESS);
609 }
610