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