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