xref: /titanic_50/usr/src/lib/libmapid/common/mapid.c (revision 1e1ddd6cc98ab5af8293f7ebd132be62900730fd)
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 2006 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  * PSARC/2004/154 nfsmapid DNS enhancements implementation.
30  *
31  * As per RFC 3530, file owner and group attributes in version 4 of the
32  * NFS protocol are no longer exchanged between client and server as 32
33  * bit integral values. Instead, owner and group file attributes are
34  * exchanged between client and server as UTF8 strings of form
35  *
36  *      'user@domain'		(ie. "joeblow@central.sun.com")
37  *      'group@domain'		(ie. "staff@central.sun.com")
38  *
39  * This NFSv4 feature is far beyond anything NFSv2/v3 ever provided, as
40  * being able to describe a user with a unique string identifier provides
41  * a much more powerful and administrative friendly way of dealing with
42  * overlaps in the uid/gid number spaces. That notwithstanding, dealing
43  * with issues of correctly mapping user and group ownership in a cross-
44  * domain environment has proven a difficult problem to solve, since
45  * dealing with different permutations of client naming configurations
46  * (ie. NIS only, LDAP only, etc.) have bloated the problem. Thus, users
47  * utilizing clients and servers that have the 'domain' portion of the
48  * UTF8 attribute string configured differently than its peer server and
49  * client accordingly, will experience watching their files owned by the
50  * 'nobody' user and group. This is due to the fact that the 'domain's
51  * don't match and the nfsmapid daemon treats the attribute strings as
52  * unknown user(s) or group(s) (even though the actual uid/gid's may exist
53  * in the executing daemon's system). Please refer to PSARC/2004/154 for
54  * further background and motivation for these enhancements.
55  *
56  * The latest implementation of the nfsmapid daemon relies on a DNS TXT
57  * record. The behavior of nfsmapid is to first use the NFSMAPID_DOMAIN
58  * configuration option in /etc/default/nfs. If the option has not been
59  * set, then the nfsmapid daemon queries the configured DNS domain server
60  * for the _nfsv4idmapdomain TXT record. If the record exists, then the
61  * record's value is used as the 'domain' portion of the UTF8 attribute
62  * strings. If the TXT record has not been configured in the DNS server,
63  * then the daemon falls back to using the DNS domain name itself as the
64  * 'domain' portion of the attribute strings. Lastly, if the configured
65  * DNS server is unresponsive, the nfsmapid daemon falls back to using
66  * the DNS domain name as the 'domain' portion of the attribute strings,
67  * and fires up a query thread to keep contacting the DNS server until
68  * it responds with either a TXT record, or a lack thereof, in which
69  * case, nfsmapid just continues to utilize the DNS domain name.
70  */
71 #define	__LIBMAPID_IMPL
72 #include <nfs/mapid.h>
73 #pragma	init(_lib_init)
74 
75 /*
76  * DEBUG Only
77  * Decode any resolver errors and print out message to log
78  */
79 static int
80 resolv_error(void)
81 {
82 #ifndef	DEBUG
83 
84 	return (h_errno);
85 
86 #else	/* DEBUG */
87 
88 	static uint64_t	 msg_done[NS_ERRS] = {0};
89 
90 	switch (h_errno) {
91 	case NETDB_INTERNAL:
92 		syslog(LOG_ERR, EMSG_NETDB_INTERNAL, strerror(errno));
93 		break;
94 
95 	case HOST_NOT_FOUND:
96 		(void) rw_rdlock(&s_dns_impl_lock);
97 		msg_done[h_errno]++;
98 		if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
99 			syslog(LOG_ERR, EMSG_HOST_NOT_FOUND, s_dname);
100 		(void) rw_unlock(&s_dns_impl_lock);
101 		break;
102 
103 	case TRY_AGAIN:
104 		/*
105 		 * Nameserver is not responding.
106 		 * Try again after a given timeout.
107 		 */
108 		(void) rw_rdlock(&s_dns_impl_lock);
109 		msg_done[h_errno]++;
110 		if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
111 			syslog(LOG_ERR, EMSG_TRY_AGAIN, s_dname);
112 		(void) rw_unlock(&s_dns_impl_lock);
113 		break;
114 
115 	case NO_RECOVERY:
116 		/*
117 		 * This msg only really happens once, due
118 		 * to s_dns_disabled flag (see below)
119 		 */
120 		syslog(LOG_ERR, EMSG_NO_RECOVERY, hstrerror(h_errno));
121 		break;
122 
123 	case NO_DATA:
124 		/*
125 		 * No entries in the nameserver for
126 		 * the specific record or record type.
127 		 */
128 		(void) rw_rdlock(&s_dns_impl_lock);
129 		msg_done[h_errno]++;
130 		if (!(msg_done[h_errno] % NFSMAPID_SLOG_RATE))
131 			syslog(LOG_ERR, EMSG_NO_DATA, NFSMAPID_DNS_RR, s_dname);
132 		(void) rw_unlock(&s_dns_impl_lock);
133 		break;
134 
135 	case NETDB_SUCCESS:
136 	default:
137 		break;
138 	}
139 	return (h_errno);
140 
141 #endif	/* DEBUG */
142 }
143 
144 /*
145  * Reset the global state variables used for the TXT record.
146  * Having these values reset to zero helps nfsmapid confirm
147  * that a valid DNS TXT record was not found; in which case,
148  * it would fall back to using the configured DNS domain name.
149  *
150  * If a valid DNS TXT record _was_ found, but subsequent contact
151  * to the DNS server is somehow hindered, the previous DNS TXT
152  * RR value continues to be used. Thus, in such instances, we
153  * forego clearing the global config variables so nfsmapid can
154  * continue to use a valid DNS TXT RR while contact to the DNS
155  * server is reestablished.
156  */
157 static void
158 resolv_txt_reset(void)
159 {
160 	(void) rw_wrlock(&s_dns_impl_lock);
161 	bzero(s_txt_rr, sizeof (s_txt_rr));
162 	(void) rw_unlock(&s_dns_impl_lock);
163 
164 	(void) rw_wrlock(&s_dns_data_lock);
165 	if (!dns_txt_cached) {
166 		dns_txt_domain_len = 0;
167 		bzero(dns_txt_domain, DNAMEMAX);
168 	}
169 	(void) rw_unlock(&s_dns_data_lock);
170 }
171 
172 /*
173  * Initialize resolver and populate &s_res struct
174  *
175  * DNS Domain is saved off sysdns_domain in case we
176  * need to fall back to using the DNS domain name as
177  * the v4 attribute string domain.
178  */
179 static int
180 resolv_init(void)
181 {
182 	size_t			len;
183 	int			n;
184 	struct __res_state	res;
185 
186 	(void) mutex_lock(&s_res_lock);
187 	bzero(&s_res, sizeof (struct __res_state));
188 	n = h_errno = errno = 0;
189 	if ((n = res_ninit(&s_res)) < 0) {
190 		(void) mutex_unlock(&s_res_lock);
191 		(void) resolv_error();
192 		return (n);
193 	}
194 	res = s_res;
195 	(void) mutex_unlock(&s_res_lock);
196 
197 	len = strlen(res.defdname) + 1;
198 	(void) rw_wrlock(&s_dns_impl_lock);
199 	bzero(s_dname, sizeof (s_dname));
200 	(void) snprintf(s_dname, len, "%s", res.defdname);
201 	(void) rw_unlock(&s_dns_impl_lock);
202 
203 	(void) rw_wrlock(&s_dns_data_lock);
204 	(void) snprintf(sysdns_domain, len, "%s", res.defdname);
205 	(void) rw_unlock(&s_dns_data_lock);
206 
207 	return (0);
208 }
209 
210 /*
211  * Search criteria assumptions:
212  *
213  * The onus will fall on the sysadmins to correctly configure the TXT
214  * record in the DNS domain where the box currently resides in order
215  * for the record to be found. However, if they sysadmin chooses to
216  * add the 'search' key to /etc/resolv.conf, then resolv_search()
217  * _will_ traverse up the DNS tree as specified in the 'search' key.
218  * Otherwise, we'll default the domain to the DNS domain itself.
219  */
220 static int
221 resolv_search(void)
222 {
223 	int			len;
224 	ans_t			ans = {0};
225 	struct __res_state	res;
226 	int			type = T_TXT;
227 	int			class = C_IN;
228 
229 	(void) mutex_lock(&s_res_lock);
230 	res = s_res;
231 	(void) mutex_unlock(&s_res_lock);
232 
233 	/*
234 	 * Avoid holding locks across the res_nsearch() call to
235 	 * prevent stalling threads during network partitions.
236 	 */
237 	len = h_errno = errno = 0;
238 	if ((len = res_nsearch(&res, NFSMAPID_DNS_RR, class, type,
239 	    ans.buf, sizeof (ans))) < 0)
240 		return (resolv_error());
241 
242 	(void) rw_wrlock(&s_dns_impl_lock);
243 	s_ans = ans;
244 	s_anslen = len;
245 	(void) rw_unlock(&s_dns_impl_lock);
246 
247 	return (NETDB_SUCCESS);
248 }
249 
250 /*
251  * Skip one DNS record
252  */
253 static uchar_t  *
254 resolv_skip_rr(uchar_t *p, uchar_t *eom)
255 {
256 	int	t;
257 	int	dlen;
258 
259 	/*
260 	 * Skip compressed name
261 	 */
262 	errno = 0;
263 	if ((t = dn_skipname(p, eom)) < 0) {
264 #ifdef	DEBUG
265 		syslog(LOG_ERR, "%s", strerror(errno));
266 #endif
267 		return (NULL);
268 	}
269 
270 	/*
271 	 * Advance pointer and make sure
272 	 * we're still within the message
273 	 */
274 	p += t;
275 	if ((p + RRFIXEDSZ) > eom)
276 		return (NULL);
277 
278 	/*
279 	 * Now, just skip over the rr fields
280 	 */
281 	p += INT16SZ;	/* type */
282 	p += INT16SZ;	/* class */
283 	p += INT32SZ;	/* ttl */
284 	dlen = ns_get16(p);
285 	p += INT16SZ;
286 	p += dlen;	/* dlen */
287 	if (p > eom)
288 		return (NULL);
289 
290 	return (p);
291 }
292 
293 /*
294  * Process one TXT record.
295  *
296  * nfsmapid queries the DNS server for the specific _nfsv4idmapdomain
297  * TXT record. Thus, if the TXT record exists, the answer section of
298  * the DNS response carries the TXT record's value. Thus, we check that
299  * the value is indeed a valid domain and set the modular s_txt_rr
300  * global to the domain value.
301  */
302 static void
303 resolve_process_txt(uchar_t *p, int dlen)
304 {
305 	char		*rr_base = (char *)(p + 1);
306 	char		*rr_end = (char *)(p + dlen);
307 	size_t		 len = rr_end - rr_base;
308 #ifdef	DEBUG
309 	static uint64_t	 msg_done = 0;
310 #endif
311 	char		 tmp_txt_rr[DNAMEMAX];
312 
313 	if (len >= DNAMEMAX)
314 		return;		/* process next TXT RR */
315 
316 	/*
317 	 * make sure we have a clean buf since
318 	 * we may've processed several TXT rr's
319 	 */
320 	(void) rw_wrlock(&s_dns_impl_lock);
321 	bzero(s_txt_rr, sizeof (s_txt_rr));
322 	(void) rw_unlock(&s_dns_impl_lock);
323 
324 	(void) strncpy(tmp_txt_rr, rr_base, len);
325 	tmp_txt_rr[len] = '\0';
326 
327 	/*
328 	 * If there is a record and it's a valid domain, we're done.
329 	 */
330 	if (rr_base[0] != '\0' && mapid_stdchk_domain(tmp_txt_rr) > 0) {
331 		(void) rw_wrlock(&s_dns_impl_lock);
332 		(void) strncpy(s_txt_rr, rr_base, len);
333 		(void) rw_unlock(&s_dns_impl_lock);
334 #ifdef	DEBUG
335 		syslog(LOG_ERR, "TXT (Rec):\t%s", s_txt_rr);
336 
337 	} else if (!(msg_done++ % NFSMAPID_SLOG_RATE)) {
338 		/*
339 		 * Otherwise, log the error
340 		 */
341 		(void) rw_rdlock(&s_dns_impl_lock);
342 		syslog(LOG_ERR, EMSG_DNS_RR_INVAL, NFSMAPID_DNS_RR, s_dname);
343 		(void) rw_unlock(&s_dns_impl_lock);
344 #endif
345 	}
346 }
347 
348 /*
349  * Decode any answer received from the DNS server. This interface is
350  * capable of much more than just decoding TXT records. We maintain
351  * focus on TXT rr's for now, but this will probably change once we
352  * get the IETF approved application specific DNS RR.
353  *
354  * Here's an example of the TXT record we're decoding (as would appear
355  * in the DNS zone file):
356  *
357  *            _nfsv4idmapdomain    IN    TXT    "sun.com"
358  *
359  * Once the IETF application specific DNS RR is granted, we should only
360  * be changing the record flavor, but all should pretty much stay the
361  * same.
362  */
363 static void
364 resolv_decode(void)
365 {
366 	uchar_t		*buf;
367 	HEADER		*hp;
368 	uchar_t		 name[DNAMEMAX];
369 	uchar_t		*eom;
370 	uchar_t		*p;
371 	int		 n;
372 	uint_t		 qd_cnt;
373 	uint_t		 an_cnt;
374 	uint_t		 ns_cnt;
375 	uint_t		 ar_cnt;
376 	uint_t		 cnt;
377 	uint_t		 type;
378 	int		 dlen;
379 	ans_t		 answer = {0};
380 	int		 answer_len = 0;
381 
382 	/*
383 	 * Check the HEADER for any signs of errors
384 	 * and extract the answer counts for later.
385 	 */
386 	(void) rw_rdlock(&s_dns_impl_lock);
387 	answer = s_ans;
388 	answer_len = s_anslen;
389 	(void) rw_unlock(&s_dns_impl_lock);
390 
391 	buf = (uchar_t *)&answer.buf;
392 	hp = (HEADER *)&answer.hdr;
393 	eom = (uchar_t *)(buf + answer_len);
394 	if (hp->rcode !=  NOERROR) {
395 #ifdef	DEBUG
396 		syslog(LOG_ERR, "errno: %s", strerror(errno));
397 		syslog(LOG_ERR, "h_errno: %s", hstrerror(h_errno));
398 #endif
399 		return;
400 	}
401 	qd_cnt = ntohs(hp->qdcount);
402 	an_cnt = ntohs(hp->ancount);
403 	ns_cnt = ntohs(hp->nscount);
404 	ar_cnt = ntohs(hp->arcount);
405 
406 	/*
407 	 * skip query entries
408 	 */
409 	p = (uchar_t *)(buf + HFIXEDSZ);
410 	errno = 0;
411 	while (qd_cnt-- > 0) {
412 		n = dn_skipname(p, eom);
413 		if (n < 0) {
414 #ifdef	DEBUG
415 			syslog(LOG_ERR, "%s", strerror(errno));
416 #endif
417 			return;
418 		}
419 		p += n;
420 		p += INT16SZ;	/* type */
421 		p += INT16SZ;	/* class */
422 	}
423 
424 #ifdef	DEBUG
425 	/*
426 	 * If debugging... print query only once.
427 	 * NOTE: Don't advance pointer... this is done
428 	 *	 in while() loop on a per record basis !
429 	 */
430 	n = h_errno = errno = 0;
431 	n = dn_expand(buf, eom, p, (char *)name, sizeof (name));
432 	if (n < 0) {
433 		(void) resolv_error();
434 		return;
435 	}
436 	syslog(LOG_ERR, "Query:\t\t%-30s", name);
437 #endif
438 
439 	/*
440 	 * Process actual answer(s).
441 	 */
442 	cnt = an_cnt;
443 	while (cnt-- > 0 && p < eom) {
444 		/* skip the name field */
445 		n = dn_expand(buf, eom, p, (char *)name, sizeof (name));
446 		if (n < 0) {
447 			(void) resolv_error();
448 			return;
449 		}
450 		p += n;
451 
452 		if ((p + 3 * INT16SZ + INT32SZ) > eom)
453 			return;
454 
455 		type = ns_get16(p);
456 		p += INT16SZ;
457 		p += INT16SZ + INT32SZ;	/* skip class & ttl */
458 		dlen = ns_get16(p);
459 		p += INT16SZ;
460 
461 		if ((p + dlen) > eom)
462 			return;
463 
464 		switch (type) {
465 			case T_TXT:
466 				resolve_process_txt(p, dlen);
467 				break;
468 
469 			default:
470 				/*
471 				 * Advance to next answer record for any
472 				 * other record types. Again, this will
473 				 * probably change (see block comment).
474 				 */
475 				p += dlen;
476 				break;
477 		}
478 	}
479 
480 	/*
481 	 * Skip name server and additional records for now.
482 	 */
483 	cnt = ns_cnt + ar_cnt;
484 	if (cnt > 0) {
485 		while (--cnt != 0 && p < eom) {
486 			p = resolv_skip_rr(p, eom);
487 			if (p == NULL)
488 				return;
489 		}
490 	}
491 }
492 
493 /*
494  * If a valid TXT record entry exists, s_txt_rr contains the domain
495  * value (as set in resolv_process_txt) and we extract the value into
496  * dns_txt_domain (the exported global). If there was _no_ valid TXT
497  * entry, we simply return and check_domain() will default to the
498  * DNS domain since we did resolv_txt_reset() first.
499  */
500 static void
501 resolv_get_txt_data()
502 {
503 	(void) rw_rdlock(&s_dns_impl_lock);
504 	if (s_txt_rr[0] != '\0') {
505 		(void) rw_wrlock(&s_dns_data_lock);
506 		(void) snprintf(dns_txt_domain, strlen(s_txt_rr) + 1, "%s",
507 								s_txt_rr);
508 		dns_txt_domain_len = strlen(dns_txt_domain);
509 		dns_txt_cached = 1;
510 		(void) rw_unlock(&s_dns_data_lock);
511 	}
512 	(void) rw_unlock(&s_dns_impl_lock);
513 }
514 
515 static void
516 domain_sync(cb_t *argp, char *dname)
517 {
518 	int	dlen = 0;
519 	void	*(*fcn)(void *) = NULL;
520 	int	sighup = 0;
521 	int	domchg = 0;
522 
523 	/*
524 	 * Make sure values passed are sane and initialize accordingly.
525 	 */
526 	if (dname != NULL)
527 		dlen = strlen(dname);
528 	if (argp) {
529 		if (argp->fcn)
530 			fcn = argp->fcn;
531 		if (argp->signal)
532 			sighup = argp->signal;
533 	}
534 
535 	/*
536 	 * Update the library's mapid_domain variable if 'dname' is different.
537 	 */
538 	if (dlen != 0 && strncasecmp(dname, mapid_domain, NS_MAXCDNAME)) {
539 		(void) rw_wrlock(&mapid_domain_lock);
540 		(void) strncpy(mapid_domain, dname, NS_MAXCDNAME);
541 		mapid_domain_len = dlen;
542 		(void) rw_unlock(&mapid_domain_lock);
543 		domchg++;
544 	}
545 
546 	/*
547 	 * If the caller gave us a valid callback routine, we
548 	 * instantiate it to announce the domain change, but
549 	 * only if either the domain changed _or_ the caller
550 	 * was issued a SIGHUP.
551 	 */
552 	if (fcn != NULL && (sighup || domchg))
553 		(void) fcn((void *)mapid_domain);
554 }
555 
556 /*
557  * Thread to keep pinging  DNS  server for  TXT  record if nfsmapid's
558  * initial attempt at contact with server failed. We could potentially
559  * have a substantial number of NFSv4 clients and having all of them
560  * hammering on an already unresponsive DNS server would not help
561  * things. So, we limit the number of live query threads to at most
562  * 1 at any one time to keep things from getting out of hand.
563  */
564 /* ARGSUSED */
565 static void *
566 resolv_query_thread(void *arg)
567 {
568 	unsigned int	 nap_time;
569 
570 #ifdef	DEBUG
571 	char		*whoami = "query_thread";
572 
573 	syslog(LOG_ERR, "%s active !", whoami);
574 #endif
575 	(void) rw_rdlock(&s_dns_impl_lock);
576 	nap_time = s_dns_tout;
577 	(void) rw_unlock(&s_dns_impl_lock);
578 
579 	for (;;) {
580 		(void) sleep(nap_time);
581 
582 		resolv_txt_reset();
583 		(void) resolv_init();
584 		switch (resolv_search()) {
585 		case NETDB_SUCCESS:
586 			resolv_decode();
587 			resolv_get_txt_data();
588 
589 			/*
590 			 * This is a bit different than what we
591 			 * do in get_dns_txt_domain(), where we
592 			 * simply return and let the caller
593 			 * access dns_txt_domain directly.
594 			 *
595 			 * Here we invoke the callback routine
596 			 * provided by the caller to the
597 			 * mapid_reeval_domain() interface via
598 			 * the cb_t's fcn param.
599 			 */
600 			domain_sync((cb_t *)arg, dns_txt_domain);
601 			goto thr_okay;
602 
603 		case NO_DATA:
604 			/*
605 			 * DNS is up now, but does not have
606 			 * the NFSV4IDMAPDOMAIN TXT record.
607 			 */
608 #ifdef	DEBUG
609 			syslog(LOG_ERR, "%s: DNS has no TXT Record", whoami);
610 #endif
611 			goto thr_reset;
612 
613 		case NO_RECOVERY:
614 			/*
615 			 * Non-Recoverable error occurred. No sense
616 			 * in keep pinging the DNS server at this
617 			 * point, so we disable any further contact.
618 			 */
619 #ifdef	DEBUG
620 			syslog(LOG_ERR, EMSG_DNS_DISABLE, whoami);
621 #endif
622 			(void) rw_wrlock(&s_dns_impl_lock);
623 			s_dns_disabled = TRUE;
624 			(void) rw_unlock(&s_dns_impl_lock);
625 			goto thr_reset;
626 
627 		case HOST_NOT_FOUND:
628 			/*
629 			 * Authoritative NS not responding...
630 			 * keep trying for non-authoritative reply
631 			 */
632 			/*FALLTHROUGH*/
633 
634 		case TRY_AGAIN:
635 			/* keep trying */
636 #ifdef	DEBUG
637 			syslog(LOG_ERR, "%s: retrying...", whoami);
638 #endif
639 			break;
640 
641 		case NETDB_INTERNAL:
642 		default:
643 #ifdef	DEBUG
644 			syslog(LOG_ERR, "%s: Internal resolver error: %s",
645 			    whoami, strerror(errno));
646 #endif
647 			goto thr_reset;
648 		}
649 	}
650 thr_reset:
651 	(void) rw_wrlock(&s_dns_data_lock);
652 	dns_txt_cached = 0;
653 	(void) rw_unlock(&s_dns_data_lock);
654 	resolv_txt_reset();
655 
656 thr_okay:
657 	/* mark thread as done */
658 	(void) rw_wrlock(&s_dns_impl_lock);
659 	s_dns_qthr_created = FALSE;
660 	(void) rw_unlock(&s_dns_impl_lock);
661 
662 	(void) thr_exit(NULL);
663 	/*NOTREACHED*/
664 	return (NULL);
665 }
666 
667 /*
668  * nfsmapid's interface into the resolver for getting the TXT record.
669  *
670  * Key concepts:
671  *
672  * o If the DNS server is available and the TXT record is found, we
673  *   simply decode the output and fill the exported dns_txt_domain
674  *   global, so our caller can configure the daemon appropriately.
675  *
676  * o If the TXT record is not found, then having done resolv_txt_reset()
677  *   first will allow our caller to recognize that the exported globals
678  *   are empty and thus configure nfsmapid to use the default DNS domain.
679  *
680  * o Having no /etc/resolv.conf file is pretty much a show stopper, since
681  *   there is no name server address information. We return since we've
682  *   already have reset the TXT global state.
683  *
684  * o If a previous call to the DNS server resulted in an unrecoverable
685  *   error, then we disable further contact to the DNS server and return.
686  *   Having the TXT global state already reset guarantees that our caller
687  *   will fall back to the right configuration.
688  *
689  * o Query thread creation is throttled by s_dns_qthr_created. We mitigate
690  *   the problem of an already unresponsive DNS server by allowing at most
691  *   1 outstanding query thread since we could potentially have a substantial
692  *   amount of clients hammering on the same DNS server attempting to get
693  *   the TXT record.
694  */
695 static void
696 get_dns_txt_domain(cb_t *argp)
697 {
698 	int		err;
699 #ifdef	DEBUG
700 	static uint64_t	msg_done = 0;
701 	char		*whoami = "get_dns_txt_domain";
702 #endif
703 	long		thr_flags = THR_DETACHED;
704 	struct stat	st;
705 
706 	/*
707 	 * We reset TXT variables first in case /etc/resolv.conf
708 	 * is missing or we've had unrecoverable resolver errors,
709 	 * we'll default to get_dns_domain(). If a previous DNS
710 	 * TXT RR was found, don't clear it until we're certain
711 	 * that contact can be made to the DNS server (see block
712 	 * comment atop resolv_txt_reset). If we're responding to
713 	 * a SIGHUP signal, force a reset of the cached copy.
714 	 */
715 	if (argp && argp->signal) {
716 		(void) rw_wrlock(&s_dns_data_lock);
717 		dns_txt_cached = 0;
718 		(void) rw_unlock(&s_dns_data_lock);
719 	}
720 	resolv_txt_reset();
721 
722 	errno = 0;
723 	if (stat(_PATH_RESCONF, &st) < 0 && errno == ENOENT) {
724 		/*
725 		 * If /etc/resolv.conf is not there, then we'll
726 		 * get the domain from domainname(1M). No real
727 		 * reason to query DNS or fire a thread since we
728 		 * have no nameserver addresses.
729 		 */
730 		goto txtclear;
731 	}
732 
733 	(void) rw_rdlock(&s_dns_impl_lock);
734 	if (s_dns_disabled) {
735 		/*
736 		 * If there were non-recoverable problems with DNS,
737 		 * we have stopped querying DNS entirely. See
738 		 * NO_RECOVERY clause below.
739 		 */
740 #ifdef	DEBUG
741 		syslog(LOG_ERR, "%s: DNS queries disabled", whoami);
742 #endif
743 		(void) rw_unlock(&s_dns_impl_lock);
744 		return;
745 	}
746 	(void) rw_unlock(&s_dns_impl_lock);
747 
748 	(void) resolv_init();
749 	switch (resolv_search()) {
750 	case NETDB_SUCCESS:
751 		/*
752 		 * If there _is_ a TXT record, we let
753 		 * our caller set the global state.
754 		 */
755 		resolv_decode();
756 		resolv_get_txt_data();
757 		return;
758 
759 	case TRY_AGAIN:
760 		if (argp == NULL || argp->fcn == NULL)
761 			/*
762 			 * If no valid argument was passed or
763 			 * callback defined, don't fire thread
764 			 */
765 			return;
766 
767 		(void) rw_wrlock(&s_dns_impl_lock);
768 		if (s_dns_qthr_created) {
769 			/*
770 			 * We may have lots of clients, so we don't
771 			 * want to bog down the DNS server with tons
772 			 * of requests... lest it becomes even more
773 			 * unresponsive, so limit 1 thread to query
774 			 * DNS at a time.
775 			 */
776 #ifdef	DEBUG
777 			syslog(LOG_ERR, "%s: query thread already active",
778 			    whoami);
779 #endif
780 			(void) rw_unlock(&s_dns_impl_lock);
781 				return;
782 		}
783 
784 		/*
785 		 * DNS did not respond ! Set timeout and kick off
786 		 * thread to try op again after s_dns_tout seconds.
787 		 * We've made sure that we don't have an already
788 		 * running thread above.
789 		 */
790 		s_dns_tout = NFSMAPID_DNS_TOUT_SECS;
791 		err = thr_create(NULL, 0, resolv_query_thread, (void *)argp,
792 		    thr_flags, &s_dns_qthread);
793 		if (!err) {
794 			s_dns_qthr_created = TRUE;
795 		}
796 #ifdef DEBUG
797 		else {
798 			msg_done++;
799 			if (!(msg_done % NFSMAPID_SLOG_RATE))
800 				syslog(LOG_ERR, EMSG_DNS_THREAD_ERROR);
801 		}
802 #endif
803 		(void) rw_unlock(&s_dns_impl_lock);
804 		return;
805 
806 	case NO_RECOVERY:
807 #ifdef	DEBUG
808 		syslog(LOG_ERR, EMSG_DNS_DISABLE, whoami);
809 #endif
810 		(void) rw_wrlock(&s_dns_impl_lock);
811 		s_dns_disabled = TRUE;
812 		(void) rw_unlock(&s_dns_impl_lock);
813 
814 		/*FALLTHROUGH*/
815 
816 	default:
817 		/*
818 		 * For any other errors... DNS is responding, but
819 		 * either it has no data, or some other problem is
820 		 * occuring. At any rate, the TXT domain should not
821 		 * be used, so we default to the DNS domain.
822 		 */
823 		break;
824 	}
825 
826 txtclear:
827 	(void) rw_wrlock(&s_dns_data_lock);
828 	dns_txt_cached = 0;
829 	(void) rw_unlock(&s_dns_data_lock);
830 	resolv_txt_reset();
831 }
832 
833 static int
834 get_mtime(const char *fname, timestruc_t *mtim)
835 {
836 	struct stat st;
837 	int err;
838 
839 	if ((err = stat(fname, &st)) != 0)
840 		return (err);
841 
842 	*mtim = st.st_mtim;
843 	return (0);
844 }
845 
846 
847 /*
848  * trim_wspace is a destructive interface; it is up to
849  * the caller to save off an original copy if needed.
850  */
851 static char *
852 trim_wspace(char *dp)
853 {
854 	char	*r;
855 	char	*ndp;
856 
857 	/*
858 	 * Any empty domain is not valid
859 	 */
860 	if (dp == NULL)
861 		return (NULL);
862 
863 	/*
864 	 * Skip leading blanks
865 	 */
866 	for (ndp = dp; *ndp != '\0'; ndp++) {
867 		if (!isspace(*ndp))
868 			break;
869 	}
870 
871 	/*
872 	 * If we reached the end of the string w/o
873 	 * finding a non-blank char, return error
874 	 */
875 	if (*ndp == '\0')
876 		return (NULL);
877 
878 	/*
879 	 * Find next blank in string
880 	 */
881 	for (r = ndp; *r != '\0'; r++) {
882 		if (isspace(*r))
883 			break;
884 	}
885 
886 	/*
887 	 * No more blanks found, we are done
888 	 */
889 	if (*r == '\0')
890 		return (ndp);
891 
892 	/*
893 	 * Terminate string at blank
894 	 */
895 	*r++ = '\0';
896 
897 	/*
898 	 * Skip any trailing spaces
899 	 */
900 	while (*r != '\0') {
901 		/*
902 		 * If a non-blank is found, it is an
903 		 * illegal domain (embedded blanks).
904 		 */
905 		if (!isspace(*r))
906 			return (NULL);
907 		r++;
908 	}
909 	return (ndp);
910 }
911 
912 static void
913 get_nfs_domain(void)
914 {
915 	char		*ndomain;
916 	timestruc_t	 ntime;
917 
918 	/*
919 	 * If we can't get stats for the config file, then
920 	 * zap the NFS domain info.  If mtime hasn't changed,
921 	 * then there's no work to do, so just return.
922 	 */
923 	if (get_mtime(NFSADMIN, &ntime) != 0) {
924 		ZAP_DOMAIN(nfs);
925 		return;
926 	}
927 
928 	if (TIMESTRUC_EQ(ntime, nfs_mtime))
929 		return;
930 
931 	/*
932 	 * Get NFSMAPID_DOMAIN value from /etc/default/nfs for now.
933 	 * Note: defread() returns a ptr to TSD.
934 	 */
935 	if (defopen(NFSADMIN) == 0) {
936 		char	*dp = NULL;
937 #ifdef	DEBUG
938 		char	*whoami = "get_nfs_domain";
939 		char	 orig[NS_MAXCDNAME] = {0};
940 #endif
941 		ndomain = (char *)defread("NFSMAPID_DOMAIN=");
942 		(void) defopen(NULL);
943 #ifdef	DEBUG
944 		if (ndomain)
945 			(void) strncpy(orig, ndomain, NS_MAXCDNAME);
946 #endif
947 		/*
948 		 * NFSMAPID_DOMAIN was set, so it's time for validation. If
949 		 * it's okay, then update NFS domain and return. If not,
950 		 * bail (syslog in DEBUG). We make nfsmapid more a bit
951 		 * more forgiving of trailing and leading white space.
952 		 */
953 		if ((dp = trim_wspace(ndomain)) != NULL) {
954 			if (mapid_stdchk_domain(dp) > 0) {
955 				nfs_domain_len = strlen(dp);
956 				(void) strncpy(nfs_domain, dp, NS_MAXCDNAME);
957 				nfs_domain[NS_MAXCDNAME] = '\0';
958 				nfs_mtime = ntime;
959 				return;
960 			}
961 		}
962 #ifdef	DEBUG
963 		if (orig[0] != '\0') {
964 			syslog(LOG_ERR, gettext("%s: Invalid domain name \"%s\""
965 				" found in configuration file."), whoami, orig);
966 		}
967 #endif
968 	}
969 
970 	/*
971 	 * So the NFS config file changed but it couldn't be opened or
972 	 * it didn't specify NFSMAPID_DOMAIN or it specified an invalid
973 	 * NFSMAPID_DOMAIN.  Time to zap current NFS domain info.
974 	 */
975 	ZAP_DOMAIN(nfs);
976 }
977 
978 static void
979 get_dns_domain(void)
980 {
981 	timestruc_t	 ntime = {0};
982 
983 	/*
984 	 * If we can't get stats for the config file, then
985 	 * zap the DNS domain info.  If mtime hasn't changed,
986 	 * then there's no work to do, so just return.
987 	 */
988 	errno = 0;
989 	if (get_mtime(_PATH_RESCONF, &ntime) != 0) {
990 		switch (errno) {
991 			case ENOENT:
992 				/*
993 				 * The resolver defaults to obtaining the
994 				 * domain off of the NIS domainname(1M) if
995 				 * /etc/resolv.conf does not exist, so we
996 				 * move forward.
997 				 */
998 				break;
999 
1000 			default:
1001 				ZAP_DOMAIN(dns);
1002 				return;
1003 		}
1004 	} else if (TIMESTRUC_EQ(ntime, dns_mtime))
1005 		return;
1006 
1007 	/*
1008 	 * Re-initialize resolver to zap DNS domain from previous
1009 	 * resolv_init() calls.
1010 	 */
1011 	(void) resolv_init();
1012 
1013 	/*
1014 	 * Update cached DNS domain.  No need for validation since
1015 	 * domain comes from resolver.  If resolver doesn't return the
1016 	 * domain, then zap the DNS domain.  This shouldn't ever happen,
1017 	 * and if it does, the machine has bigger problems (so no need
1018 	 * to generate a message that says DNS appears to be broken).
1019 	 */
1020 	(void) rw_rdlock(&s_dns_data_lock);
1021 	if (sysdns_domain[0] != '\0') {
1022 		(void) strncpy(dns_domain, sysdns_domain, NS_MAXCDNAME);
1023 		dns_domain_len = strlen(sysdns_domain);
1024 		(void) rw_unlock(&s_dns_data_lock);
1025 		dns_mtime = ntime;
1026 		return;
1027 	}
1028 	(void) rw_unlock(&s_dns_data_lock);
1029 
1030 	ZAP_DOMAIN(dns);
1031 }
1032 
1033 /*
1034  * PSARC 2005/487 Contracted Sun Private Interface
1035  * mapid_stdchk_domain()
1036  * Changes must be reviewed by Solaris File Sharing
1037  * Changes must be communicated to contract-2005-487-01@sun.com
1038  *
1039  * Based on the recommendations from RFC1033 and RFC1035, check
1040  * if a given domain name string is valid. Return values are:
1041  *
1042  *       1 = valid domain name
1043  *       0 = invalid domain name (or invalid embedded character)
1044  *      -1 = domain length > NS_MAXCDNAME
1045  */
1046 int
1047 mapid_stdchk_domain(const char *ds)
1048 {
1049 	int	i;
1050 	size_t	len;
1051 
1052 	if (ds[0] == '\0')
1053 		return (0);
1054 	else
1055 		len = strlen(ds) - 1;
1056 
1057 	/*
1058 	 * 1st _AND_ last char _must_ be alphanumeric.
1059 	 * We check for other valid chars below.
1060 	 */
1061 	if ((!isalpha(ds[0]) && !isdigit(ds[0])) ||
1062 	    (!isalpha(ds[len]) && !isdigit(ds[len])))
1063 		return (0);
1064 
1065 	for (i = 0; *ds && i <= NS_MAXCDNAME; i++, ds++) {
1066 		if (!isalpha(*ds) && !isdigit(*ds) &&
1067 		    (*ds != '.') && (*ds != '-') && (*ds != '_'))
1068 			return (0);
1069 	}
1070 	return (i == (NS_MAXCDNAME + 1) ? -1 : 1);
1071 }
1072 
1073 /*
1074  * PSARC 2005/487 Consolidation Private
1075  * mapid_reeval_domain()
1076  * Changes must be reviewed by Solaris File Sharing
1077  */
1078 void
1079 mapid_reeval_domain(cb_t *arg)
1080 {
1081 	char	*domain = NULL;
1082 
1083 	get_nfs_domain();
1084 	if (nfs_domain_len != 0) {
1085 		domain = nfs_domain;
1086 		goto dsync;
1087 	}
1088 
1089 	get_dns_txt_domain(arg);
1090 	if (dns_txt_domain_len != 0)
1091 		domain = dns_txt_domain;
1092 	else {
1093 		/*
1094 		 * We're either here because:
1095 		 *
1096 		 *  . NFSMAPID_DOMAIN was not set in /etc/default/nfs
1097 		 *  . No suitable DNS TXT resource record exists
1098 		 *  . DNS server is not responding to requests
1099 		 *
1100 		 * in either case, we want to default to using the
1101 		 * system configured DNS domain. If this fails, then
1102 		 * dns_domain will be empty and dns_domain_len will
1103 		 * be 0.
1104 		 */
1105 		get_dns_domain();
1106 		domain = dns_domain;
1107 	}
1108 
1109 dsync:
1110 	domain_sync(arg, domain);
1111 }
1112 
1113 /*
1114  * PSARC 2005/487 Consolidation Private
1115  * mapid_get_domain()
1116  * Changes must be reviewed by Solaris File Sharing
1117  *
1118  * The use of TSD in mapid_get_domain() diverges slightly from the typical
1119  * TSD use, since here, the benefit of doing TSD is mostly to allocate
1120  * a per-thread buffer that will be utilized by other up-calls to the
1121  * daemon.
1122  *
1123  * In doors, the thread used for the upcall never really exits, hence
1124  * the typical destructor function defined via thr_keycreate() will
1125  * never be called. Thus, we only use TSD to allocate the per-thread
1126  * buffer and fill it up w/the configured 'mapid_domain' on each call.
1127  * This still alleviates the problem of having the caller free any
1128  * malloc'd space.
1129  */
1130 char *
1131 mapid_get_domain(void)
1132 {
1133 	void	*tsd = NULL;
1134 
1135 	(void) thr_getspecific(s_thr_key, &tsd);
1136 	if (tsd == NULL) {
1137 		tsd = malloc(NS_MAXCDNAME+1);
1138 		if (tsd != NULL) {
1139 			(void) rw_rdlock(&mapid_domain_lock);
1140 			(void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME);
1141 			(void) rw_unlock(&mapid_domain_lock);
1142 			(void) thr_setspecific(s_thr_key, tsd);
1143 		}
1144 	} else {
1145 		(void) rw_rdlock(&mapid_domain_lock);
1146 		(void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME);
1147 		(void) rw_unlock(&mapid_domain_lock);
1148 	}
1149 	return ((char *)tsd);
1150 }
1151 
1152 /*
1153  * PSARC 2005/487 Contracted Sun Private Interface
1154  * mapid_derive_domain()
1155  * Changes must be reviewed by Solaris File Sharing
1156  * Changes must be communicated to contract-2005-487-01@sun.com
1157  *
1158  * This interface is called solely via sysidnfs4 iff no
1159  * NFSMAPID_DOMAIN was found. So, there is no ill effect
1160  * of having the reeval function call get_nfs_domain().
1161  */
1162 char *
1163 mapid_derive_domain(void)
1164 {
1165 	cb_t	cb = {0};
1166 
1167 	_lib_init();
1168 	mapid_reeval_domain(&cb);
1169 	return (mapid_get_domain());
1170 }
1171 
1172 void
1173 _lib_init(void)
1174 {
1175 	(void) resolv_init();
1176 	(void) rwlock_init(&mapid_domain_lock, USYNC_THREAD, NULL);
1177 	(void) thr_keycreate(&s_thr_key, NULL);
1178 	lib_init_done++;
1179 }
1180