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