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