1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * PSARC/2004/154 nfsmapid DNS enhancements implementation. 28 * 29 * As per RFC 3530, file owner and group attributes in version 4 of the 30 * NFS protocol are no longer exchanged between client and server as 32 31 * bit integral values. Instead, owner and group file attributes are 32 * exchanged between client and server as UTF8 strings of form 33 * 34 * 'user@domain' (ie. "joeblow@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 void *defp; 934 935 /* 936 * If we can't get stats for the config file, then 937 * zap the NFS domain info. If mtime hasn't changed, 938 * then there's no work to do, so just return. 939 */ 940 if (get_mtime(NFSADMIN, &ntime) != 0) { 941 ZAP_DOMAIN(nfs); 942 return; 943 } 944 945 if (TIMESTRUC_EQ(ntime, nfs_mtime)) 946 return; 947 948 /* 949 * Get NFSMAPID_DOMAIN value from /etc/default/nfs for now. 950 * Note: defread_r() returns a ptr to libc internal malloc. 951 */ 952 if ((defp = defopen_r(NFSADMIN)) != NULL) { 953 char *dp = NULL; 954 #ifdef DEBUG 955 char *whoami = "get_nfs_domain"; 956 char orig[NS_MAXCDNAME] = {0}; 957 #endif 958 ndomain = defread_r("NFSMAPID_DOMAIN=", defp); 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 defclose_r(defp); 976 return; 977 } 978 } 979 defclose_r(defp); 980 #ifdef DEBUG 981 if (orig[0] != '\0') { 982 syslog(LOG_ERR, gettext("%s: Invalid domain name \"%s\"" 983 " found in configuration file."), whoami, orig); 984 } 985 #endif 986 } 987 988 /* 989 * So the NFS config file changed but it couldn't be opened or 990 * it didn't specify NFSMAPID_DOMAIN or it specified an invalid 991 * NFSMAPID_DOMAIN. Time to zap current NFS domain info. 992 */ 993 ZAP_DOMAIN(nfs); 994 } 995 996 static void 997 get_dns_domain(void) 998 { 999 timestruc_t ntime = {0}; 1000 1001 /* 1002 * If we can't get stats for the config file, then 1003 * zap the DNS domain info. If mtime hasn't changed, 1004 * then there's no work to do, so just return. 1005 */ 1006 errno = 0; 1007 if (get_mtime(_PATH_RESCONF, &ntime) != 0) { 1008 switch (errno) { 1009 case ENOENT: 1010 /* 1011 * The resolver defaults to obtaining the 1012 * domain off of the NIS domainname(1M) if 1013 * /etc/resolv.conf does not exist, so we 1014 * move forward. 1015 */ 1016 break; 1017 1018 default: 1019 ZAP_DOMAIN(dns); 1020 return; 1021 } 1022 } else if (TIMESTRUC_EQ(ntime, dns_mtime)) 1023 return; 1024 1025 /* 1026 * Re-initialize resolver to zap DNS domain from previous 1027 * resolv_init() calls. 1028 */ 1029 (void) resolv_init(); 1030 1031 /* 1032 * Update cached DNS domain. No need for validation since 1033 * domain comes from resolver. If resolver doesn't return the 1034 * domain, then zap the DNS domain. This shouldn't ever happen, 1035 * and if it does, the machine has bigger problems (so no need 1036 * to generate a message that says DNS appears to be broken). 1037 */ 1038 (void) rw_rdlock(&s_dns_data_lock); 1039 if (sysdns_domain[0] != '\0') { 1040 (void) strncpy(dns_domain, sysdns_domain, NS_MAXCDNAME); 1041 dns_domain_len = strlen(sysdns_domain); 1042 (void) rw_unlock(&s_dns_data_lock); 1043 dns_mtime = ntime; 1044 resolv_destroy(); 1045 return; 1046 } 1047 (void) rw_unlock(&s_dns_data_lock); 1048 1049 ZAP_DOMAIN(dns); 1050 1051 resolv_destroy(); 1052 1053 } 1054 1055 /* 1056 * PSARC 2005/487 Contracted Sun Private Interface 1057 * mapid_stdchk_domain() 1058 * Changes must be reviewed by Solaris File Sharing 1059 * Changes must be communicated to contract-2005-487-01@sun.com 1060 * 1061 * Based on the recommendations from RFC1033 and RFC1035, check 1062 * if a given domain name string is valid. Return values are: 1063 * 1064 * 1 = valid domain name 1065 * 0 = invalid domain name (or invalid embedded character) 1066 * -1 = domain length > NS_MAXCDNAME 1067 */ 1068 int 1069 mapid_stdchk_domain(const char *ds) 1070 { 1071 int i; 1072 size_t len; 1073 1074 if (ds[0] == '\0') 1075 return (0); 1076 else 1077 len = strlen(ds) - 1; 1078 1079 /* 1080 * 1st _AND_ last char _must_ be alphanumeric. 1081 * We check for other valid chars below. 1082 */ 1083 if ((!isalpha(ds[0]) && !isdigit(ds[0])) || 1084 (!isalpha(ds[len]) && !isdigit(ds[len]))) 1085 return (0); 1086 1087 for (i = 0; *ds && i <= NS_MAXCDNAME; i++, ds++) { 1088 if (!isalpha(*ds) && !isdigit(*ds) && 1089 (*ds != '.') && (*ds != '-') && (*ds != '_')) 1090 return (0); 1091 } 1092 return (i == (NS_MAXCDNAME + 1) ? -1 : 1); 1093 } 1094 1095 /* 1096 * PSARC 2005/487 Consolidation Private 1097 * mapid_reeval_domain() 1098 * Changes must be reviewed by Solaris File Sharing 1099 */ 1100 void 1101 mapid_reeval_domain(cb_t *arg) 1102 { 1103 char *domain = NULL; 1104 1105 get_nfs_domain(); 1106 if (nfs_domain_len != 0) { 1107 domain = nfs_domain; 1108 goto dsync; 1109 } 1110 1111 get_dns_txt_domain(arg); 1112 if (dns_txt_domain_len != 0) 1113 domain = dns_txt_domain; 1114 else { 1115 /* 1116 * We're either here because: 1117 * 1118 * . NFSMAPID_DOMAIN was not set in /etc/default/nfs 1119 * . No suitable DNS TXT resource record exists 1120 * . DNS server is not responding to requests 1121 * 1122 * in either case, we want to default to using the 1123 * system configured DNS domain. If this fails, then 1124 * dns_domain will be empty and dns_domain_len will 1125 * be 0. 1126 */ 1127 get_dns_domain(); 1128 domain = dns_domain; 1129 } 1130 1131 dsync: 1132 domain_sync(arg, domain); 1133 } 1134 1135 /* 1136 * PSARC 2005/487 Consolidation Private 1137 * mapid_get_domain() 1138 * Changes must be reviewed by Solaris File Sharing 1139 * 1140 * The use of TSD in mapid_get_domain() diverges slightly from the typical 1141 * TSD use, since here, the benefit of doing TSD is mostly to allocate 1142 * a per-thread buffer that will be utilized by other up-calls to the 1143 * daemon. 1144 * 1145 * In doors, the thread used for the upcall never really exits, hence 1146 * the typical destructor function defined via thr_keycreate() will 1147 * never be called. Thus, we only use TSD to allocate the per-thread 1148 * buffer and fill it up w/the configured 'mapid_domain' on each call. 1149 * This still alleviates the problem of having the caller free any 1150 * malloc'd space. 1151 */ 1152 char * 1153 mapid_get_domain(void) 1154 { 1155 void *tsd = NULL; 1156 1157 (void) thr_getspecific(s_thr_key, &tsd); 1158 if (tsd == NULL) { 1159 tsd = malloc(NS_MAXCDNAME+1); 1160 if (tsd != NULL) { 1161 (void) rw_rdlock(&mapid_domain_lock); 1162 (void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME); 1163 (void) rw_unlock(&mapid_domain_lock); 1164 (void) thr_setspecific(s_thr_key, tsd); 1165 } 1166 } else { 1167 (void) rw_rdlock(&mapid_domain_lock); 1168 (void) strncpy((char *)tsd, mapid_domain, NS_MAXCDNAME); 1169 (void) rw_unlock(&mapid_domain_lock); 1170 } 1171 return ((char *)tsd); 1172 } 1173 1174 /* 1175 * PSARC 2005/487 Contracted Sun Private Interface 1176 * mapid_derive_domain() 1177 * Changes must be reviewed by Solaris File Sharing 1178 * Changes must be communicated to contract-2005-487-01@sun.com 1179 * 1180 * This interface is called solely via sysidnfs4 iff no 1181 * NFSMAPID_DOMAIN was found. So, there is no ill effect 1182 * of having the reeval function call get_nfs_domain(). 1183 */ 1184 char * 1185 mapid_derive_domain(void) 1186 { 1187 cb_t cb = {0}; 1188 1189 _lib_init(); 1190 mapid_reeval_domain(&cb); 1191 return (mapid_get_domain()); 1192 } 1193 1194 void 1195 _lib_init(void) 1196 { 1197 (void) resolv_init(); 1198 (void) rwlock_init(&mapid_domain_lock, USYNC_THREAD, NULL); 1199 (void) thr_keycreate(&s_thr_key, NULL); 1200 lib_init_done++; 1201 resolv_destroy(); 1202 } 1203