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