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