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