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