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 3530, 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(1M). 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 != NULL) {
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(1M) 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