Home | History | Annotate | Download | only in common
      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 (at) central.sun.com")
     35  *      'group@domain'		(ie. "staff (at) 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 (at) 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 (at) 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