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