1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * Common code and structures used by name-service-switch "files" backends. 26 */ 27 28 /* 29 * An implementation that used mmap() sensibly would be a wonderful thing, 30 * but this here is just yer standard fgets() thang. 31 */ 32 33 #include "files_common.h" 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <ctype.h> 38 #include <fcntl.h> 39 #include <poll.h> 40 #include <unistd.h> 41 #include <sys/stat.h> 42 #include <sys/mman.h> 43 44 /*ARGSUSED*/ 45 nss_status_t 46 _nss_files_setent(be, dummy) 47 files_backend_ptr_t be; 48 void *dummy; 49 { 50 if (be->f == 0) { 51 if (be->filename == 0) { 52 /* Backend isn't initialized properly? */ 53 return (NSS_UNAVAIL); 54 } 55 if ((be->f = fopen(be->filename, "rF")) == 0) { 56 return (NSS_UNAVAIL); 57 } 58 } else { 59 rewind(be->f); 60 } 61 return (NSS_SUCCESS); 62 } 63 64 /*ARGSUSED*/ 65 nss_status_t 66 _nss_files_endent(be, dummy) 67 files_backend_ptr_t be; 68 void *dummy; 69 { 70 if (be->f != 0) { 71 (void) fclose(be->f); 72 be->f = 0; 73 } 74 if (be->buf != 0) { 75 free(be->buf); 76 be->buf = 0; 77 } 78 return (NSS_SUCCESS); 79 } 80 81 /* 82 * This routine reads a line, including the processing of continuation 83 * characters. It always leaves (or inserts) \n\0 at the end of the line. 84 * It returns the length of the line read, excluding the \n\0. Who's idea 85 * was this? 86 * Returns -1 on EOF. 87 * 88 * Note that since each concurrent call to _nss_files_read_line has 89 * it's own FILE pointer, we can use getc_unlocked w/o difficulties, 90 * a substantial performance win. 91 */ 92 int 93 _nss_files_read_line(f, buffer, buflen) 94 FILE *f; 95 char *buffer; 96 int buflen; 97 { 98 int linelen; /* 1st unused slot in buffer */ 99 int c; 100 101 /*CONSTCOND*/ 102 while (1) { 103 linelen = 0; 104 while (linelen < buflen - 1) { /* "- 1" saves room for \n\0 */ 105 switch (c = getc_unlocked(f)) { 106 case EOF: 107 if (linelen == 0 || 108 buffer[linelen - 1] == '\\') { 109 return (-1); 110 } else { 111 buffer[linelen ] = '\n'; 112 buffer[linelen + 1] = '\0'; 113 return (linelen); 114 } 115 case '\n': 116 if (linelen > 0 && 117 buffer[linelen - 1] == '\\') { 118 --linelen; /* remove the '\\' */ 119 } else { 120 buffer[linelen ] = '\n'; 121 buffer[linelen + 1] = '\0'; 122 return (linelen); 123 } 124 break; 125 default: 126 buffer[linelen++] = c; 127 } 128 } 129 /* Buffer overflow -- eat rest of line and loop again */ 130 /* ===> Should syslog() */ 131 do { 132 c = getc_unlocked(f); 133 if (c == EOF) { 134 return (-1); 135 } 136 } while (c != '\n'); 137 } 138 /*NOTREACHED*/ 139 } 140 141 /* 142 * used only for getgroupbymem() now. 143 */ 144 nss_status_t 145 _nss_files_do_all(be, args, filter, func) 146 files_backend_ptr_t be; 147 void *args; 148 const char *filter; 149 files_do_all_func_t func; 150 { 151 long grlen; 152 char *buffer; 153 int buflen; 154 nss_status_t res; 155 156 if (be->buf == 0) { 157 if ((grlen = sysconf(_SC_GETGR_R_SIZE_MAX)) > 0) 158 be->minbuf = grlen; 159 if ((be->buf = malloc(be->minbuf)) == 0) 160 return (NSS_UNAVAIL); 161 } 162 buffer = be->buf; 163 buflen = be->minbuf; 164 165 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 166 return (res); 167 } 168 169 res = NSS_NOTFOUND; 170 171 do { 172 int linelen; 173 174 if ((linelen = _nss_files_read_line(be->f, buffer, 175 buflen)) < 0) { 176 /* End of file */ 177 break; 178 } 179 if (filter != 0 && strstr(buffer, filter) == 0) { 180 /* 181 * Optimization: if the entry doesn't contain the 182 * filter string then it can't be the entry we want, 183 * so don't bother looking more closely at it. 184 */ 185 continue; 186 } 187 res = (*func)(buffer, linelen, args); 188 189 } while (res == NSS_NOTFOUND); 190 191 (void) _nss_files_endent(be, 0); 192 return (res); 193 } 194 195 /* 196 * Could implement this as an iterator function on top of _nss_files_do_all(), 197 * but the shared code is small enough that it'd be pretty silly. 198 */ 199 nss_status_t 200 _nss_files_XY_all(be, args, netdb, filter, check) 201 files_backend_ptr_t be; 202 nss_XbyY_args_t *args; 203 int netdb; /* whether it uses netdb */ 204 /* format or not */ 205 const char *filter; /* advisory, to speed up */ 206 /* string search */ 207 files_XY_check_func check; /* NULL means one-shot, for getXXent */ 208 { 209 char *r; 210 nss_status_t res; 211 int parsestat; 212 int (*func)(); 213 214 if (filter != NULL && *filter == '\0') 215 return (NSS_NOTFOUND); 216 if (be->buf == 0 || (be->minbuf < args->buf.buflen)) { 217 if (be->minbuf < args->buf.buflen) { 218 if (be->buf == 0) { 219 be->minbuf = args->buf.buflen; 220 } else if ( 221 (r = realloc(be->buf, args->buf.buflen)) != NULL) { 222 be->buf = r; 223 be->minbuf = args->buf.buflen; 224 } 225 } 226 if (be->buf == 0 && 227 (be->buf = malloc(be->minbuf)) == 0) 228 return (NSS_UNAVAIL); 229 } 230 231 if (check != 0 || be->f == 0) { 232 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 233 return (res); 234 } 235 } 236 237 res = NSS_NOTFOUND; 238 239 /*CONSTCOND*/ 240 while (1) { 241 char *instr = be->buf; 242 int linelen; 243 244 if ((linelen = _nss_files_read_line(be->f, instr, 245 be->minbuf)) < 0) { 246 /* End of file */ 247 args->returnval = 0; 248 args->returnlen = 0; 249 break; 250 } 251 if (filter != 0 && strstr(instr, filter) == 0) { 252 /* 253 * Optimization: if the entry doesn't contain the 254 * filter string then it can't be the entry we want, 255 * so don't bother looking more closely at it. 256 */ 257 continue; 258 } 259 if (netdb) { 260 char *first; 261 char *last; 262 263 if ((last = strchr(instr, '#')) == 0) { 264 last = instr + linelen; 265 } 266 *last-- = '\0'; /* Nuke '\n' or #comment */ 267 268 /* 269 * Skip leading whitespace. Normally there isn't 270 * any, so it's not worth calling strspn(). 271 */ 272 for (first = instr; isspace(*first); first++) { 273 ; 274 } 275 if (*first == '\0') { 276 continue; 277 } 278 /* 279 * Found something non-blank on the line. Skip back 280 * over any trailing whitespace; since we know 281 * there's non-whitespace earlier in the line, 282 * checking for termination is easy. 283 */ 284 while (isspace(*last)) { 285 --last; 286 } 287 288 linelen = last - first + 1; 289 if (first != instr) { 290 instr = first; 291 } 292 } 293 294 args->returnval = 0; 295 args->returnlen = 0; 296 297 if (check != NULL && (*check)(args, instr, linelen) == 0) 298 continue; 299 300 parsestat = NSS_STR_PARSE_SUCCESS; 301 if (be->filename != NULL) { 302 /* 303 * Special case for passwd and group wherein we 304 * replace uids/gids > MAXUID by ID_NOBODY 305 * because files backend does not support 306 * ephemeral ids. 307 */ 308 if (strcmp(be->filename, PF_PATH) == 0) 309 parsestat = validate_passwd_ids(instr, 310 &linelen, be->minbuf, 2); 311 else if (strcmp(be->filename, GF_PATH) == 0) 312 parsestat = validate_group_ids(instr, 313 &linelen, be->minbuf, 2, check); 314 } 315 316 if (parsestat == NSS_STR_PARSE_SUCCESS) { 317 func = args->str2ent; 318 parsestat = (*func)(instr, linelen, args->buf.result, 319 args->buf.buffer, args->buf.buflen); 320 } 321 322 if (parsestat == NSS_STR_PARSE_SUCCESS) { 323 args->returnval = (args->buf.result != NULL)? 324 args->buf.result : args->buf.buffer; 325 args->returnlen = linelen; 326 res = NSS_SUCCESS; 327 break; 328 } else if (parsestat == NSS_STR_PARSE_ERANGE) { 329 args->erange = 1; 330 break; 331 } /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */ 332 } 333 334 /* 335 * stayopen is set to 0 by default in order to close the opened 336 * file. Some applications may break if it is set to 1. 337 */ 338 if (check != 0 && !args->stayopen) { 339 (void) _nss_files_endent(be, 0); 340 } 341 342 return (res); 343 } 344 345 /* 346 * File hashing support. Critical for sites with large (e.g. 1000+ lines) 347 * /etc/passwd or /etc/group files. Currently only used by getpw*() and 348 * getgr*() routines, but any files backend can use this stuff. 349 */ 350 static void 351 _nss_files_hash_destroy(files_hash_t *fhp) 352 { 353 free(fhp->fh_table); 354 fhp->fh_table = NULL; 355 free(fhp->fh_line); 356 fhp->fh_line = NULL; 357 free(fhp->fh_file_start); 358 fhp->fh_file_start = NULL; 359 } 360 #ifdef PIC 361 /* 362 * It turns out the hashing stuff really needs to be disabled for processes 363 * other than the nscd; the consumption of swap space and memory is otherwise 364 * unacceptable when the nscd is killed w/ a large passwd file (4M) active. 365 * See 4031930 for details. 366 * So we just use this psuedo function to enable the hashing feature. Since 367 * this function name is private, we just create a function w/ the name 368 * __nss_use_files_hash in the nscd itself and everyone else uses the old 369 * interface. 370 * We also disable hashing for .a executables to avoid problems with large 371 * files.... 372 */ 373 374 #pragma weak __nss_use_files_hash 375 376 extern void __nss_use_files_hash(void); 377 #endif /* pic */ 378 379 /*ARGSUSED*/ 380 nss_status_t 381 _nss_files_XY_hash(files_backend_ptr_t be, nss_XbyY_args_t *args, 382 int netdb, files_hash_t *fhp, int hashop, files_XY_check_func check) 383 { 384 /* LINTED E_FUNC_VAR_UNUSED */ 385 int fd, retries, ht, stat; 386 /* LINTED E_FUNC_VAR_UNUSED */ 387 uint_t hash, line, f; 388 /* LINTED E_FUNC_VAR_UNUSED */ 389 files_hashent_t *hp, *htab; 390 /* LINTED E_FUNC_VAR_UNUSED */ 391 char *cp, *first, *last; 392 /* LINTED E_FUNC_VAR_UNUSED */ 393 nss_XbyY_args_t xargs; 394 /* LINTED E_FUNC_VAR_UNUSED */ 395 struct stat64 st; 396 397 #ifndef PIC 398 return (_nss_files_XY_all(be, args, netdb, 0, check)); 399 } 400 #else 401 if (__nss_use_files_hash == 0) 402 return (_nss_files_XY_all(be, args, netdb, 0, check)); 403 404 mutex_lock(&fhp->fh_lock); 405 retry: 406 retries = 100; 407 while (stat64(be->filename, &st) < 0) { 408 /* 409 * On a healthy system this can't happen except during brief 410 * periods when the file is being modified/renamed. Keep 411 * trying until things settle down, but eventually give up. 412 */ 413 if (--retries == 0) 414 goto unavail; 415 poll(0, 0, 100); 416 } 417 418 if (st.st_mtim.tv_sec == fhp->fh_mtime.tv_sec && 419 st.st_mtim.tv_nsec == fhp->fh_mtime.tv_nsec && 420 fhp->fh_table != NULL) { 421 htab = &fhp->fh_table[hashop * fhp->fh_size]; 422 hash = fhp->fh_hash_func[hashop](args, 1, NULL, 0); 423 for (hp = htab[hash % fhp->fh_size].h_first; hp != NULL; 424 hp = hp->h_next) { 425 if (hp->h_hash != hash) 426 continue; 427 line = hp - htab; 428 if ((*check)(args, fhp->fh_line[line].l_start, 429 fhp->fh_line[line].l_len) == 0) 430 continue; 431 432 if (be->filename != NULL) { 433 stat = NSS_STR_PARSE_SUCCESS; 434 if (strcmp(be->filename, PF_PATH) == 0) 435 stat = validate_passwd_ids( 436 fhp->fh_line[line].l_start, 437 &fhp->fh_line[line].l_len, 438 fhp->fh_line[line].l_len + 1, 439 1); 440 else if (strcmp(be->filename, GF_PATH) == 0) 441 stat = validate_group_ids( 442 fhp->fh_line[line].l_start, 443 &fhp->fh_line[line].l_len, 444 fhp->fh_line[line].l_len + 1, 445 1, check); 446 if (stat != NSS_STR_PARSE_SUCCESS) { 447 if (stat == NSS_STR_PARSE_ERANGE) 448 args->erange = 1; 449 continue; 450 } 451 } 452 453 if ((*args->str2ent)(fhp->fh_line[line].l_start, 454 fhp->fh_line[line].l_len, args->buf.result, 455 args->buf.buffer, args->buf.buflen) == 456 NSS_STR_PARSE_SUCCESS) { 457 args->returnval = (args->buf.result)? 458 args->buf.result:args->buf.buffer; 459 args->returnlen = fhp->fh_line[line].l_len; 460 mutex_unlock(&fhp->fh_lock); 461 return (NSS_SUCCESS); 462 } else { 463 args->erange = 1; 464 } 465 } 466 args->returnval = 0; 467 args->returnlen = 0; 468 mutex_unlock(&fhp->fh_lock); 469 return (NSS_NOTFOUND); 470 } 471 472 _nss_files_hash_destroy(fhp); 473 474 if (st.st_size > SSIZE_MAX) 475 goto unavail; 476 477 if ((fhp->fh_file_start = malloc((ssize_t)st.st_size + 1)) == NULL) 478 goto unavail; 479 480 if ((fd = open(be->filename, O_RDONLY)) < 0) 481 goto unavail; 482 483 if (read(fd, fhp->fh_file_start, (ssize_t)st.st_size) != 484 (ssize_t)st.st_size) { 485 close(fd); 486 goto retry; 487 } 488 489 close(fd); 490 491 fhp->fh_file_end = fhp->fh_file_start + (off_t)st.st_size; 492 *fhp->fh_file_end = '\n'; 493 fhp->fh_mtime = st.st_mtim; 494 495 /* 496 * If the file changed since we read it, or if it's less than 497 * 1-2 seconds old, don't trust it; its modification may still 498 * be in progress. The latter is a heuristic hack to minimize 499 * the likelihood of damage if someone modifies /etc/mumble 500 * directly (as opposed to editing and renaming a temp file). 501 * 502 * Note: the cast to u_int is there in case (1) someone rdated 503 * the system backwards since the last modification of /etc/mumble 504 * or (2) this is a diskless client whose time is badly out of sync 505 * with its server. The 1-2 second age hack doesn't cover these 506 * cases -- oh well. 507 */ 508 if (stat64(be->filename, &st) < 0 || 509 st.st_mtim.tv_sec != fhp->fh_mtime.tv_sec || 510 st.st_mtim.tv_nsec != fhp->fh_mtime.tv_nsec || 511 (uint_t)(time(0) - st.st_mtim.tv_sec + 2) < 4) { 512 poll(0, 0, 1000); 513 goto retry; 514 } 515 516 line = 1; 517 for (cp = fhp->fh_file_start; cp < fhp->fh_file_end; cp++) 518 if (*cp == '\n') 519 line++; 520 521 for (f = 2; f * f <= line; f++) { /* find next largest prime */ 522 if (line % f == 0) { 523 f = 1; 524 line++; 525 } 526 } 527 528 fhp->fh_size = line; 529 fhp->fh_line = malloc(line * sizeof (files_linetab_t)); 530 fhp->fh_table = calloc(line * fhp->fh_nhtab, sizeof (files_hashent_t)); 531 if (fhp->fh_line == NULL || fhp->fh_table == NULL) 532 goto unavail; 533 534 line = 0; 535 cp = fhp->fh_file_start; 536 while (cp < fhp->fh_file_end) { 537 first = cp; 538 while (*cp != '\n') 539 cp++; 540 if (cp > first && *(cp - 1) == '\\') { 541 memmove(first + 2, first, cp - first - 1); 542 cp = first + 2; 543 continue; 544 } 545 last = cp; 546 *cp++ = '\0'; 547 if (netdb) { 548 if ((last = strchr(first, '#')) == 0) 549 last = cp - 1; 550 *last-- = '\0'; /* nuke '\n' or #comment */ 551 while (isspace(*first)) /* nuke leading whitespace */ 552 first++; 553 if (*first == '\0') /* skip content-free lines */ 554 continue; 555 while (isspace(*last)) /* nuke trailing whitespace */ 556 --last; 557 *++last = '\0'; 558 } 559 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 560 hp = &fhp->fh_table[ht * fhp->fh_size + line]; 561 hp->h_hash = fhp->fh_hash_func[ht](&xargs, 0, first, 562 last - first); 563 } 564 fhp->fh_line[line].l_start = first; 565 fhp->fh_line[line++].l_len = last - first; 566 } 567 568 /* 569 * Populate the hash tables in reverse order so that the hash chains 570 * end up in forward order. This ensures that hashed lookups find 571 * things in the same order that a linear search of the file would. 572 * This is essential in cases where there could be multiple matches. 573 * For example: until 2.7, root and smtp both had uid 0; but we 574 * certainly wouldn't want getpwuid(0) to return smtp. 575 */ 576 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 577 htab = &fhp->fh_table[ht * fhp->fh_size]; 578 for (hp = &htab[line - 1]; hp >= htab; hp--) { 579 uint_t bucket = hp->h_hash % fhp->fh_size; 580 hp->h_next = htab[bucket].h_first; 581 htab[bucket].h_first = hp; 582 } 583 } 584 585 goto retry; 586 587 unavail: 588 _nss_files_hash_destroy(fhp); 589 mutex_unlock(&fhp->fh_lock); 590 return (NSS_UNAVAIL); 591 } 592 #endif /* PIC */ 593 594 nss_status_t 595 _nss_files_getent_rigid(be, a) 596 files_backend_ptr_t be; 597 void *a; 598 { 599 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 600 601 return (_nss_files_XY_all(be, args, 0, 0, 0)); 602 } 603 604 nss_status_t 605 _nss_files_getent_netdb(be, a) 606 files_backend_ptr_t be; 607 void *a; 608 { 609 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 610 611 return (_nss_files_XY_all(be, args, 1, 0, 0)); 612 } 613 614 /*ARGSUSED*/ 615 nss_status_t 616 _nss_files_destr(be, dummy) 617 files_backend_ptr_t be; 618 void *dummy; 619 { 620 if (be != 0) { 621 if (be->f != 0) { 622 (void) _nss_files_endent(be, 0); 623 } 624 if (be->hashinfo != NULL) { 625 (void) mutex_lock(&be->hashinfo->fh_lock); 626 if (--be->hashinfo->fh_refcnt == 0) 627 _nss_files_hash_destroy(be->hashinfo); 628 (void) mutex_unlock(&be->hashinfo->fh_lock); 629 } 630 free(be); 631 } 632 return (NSS_SUCCESS); /* In case anyone is dumb enough to check */ 633 } 634 635 nss_backend_t * 636 _nss_files_constr(ops, n_ops, filename, min_bufsize, fhp) 637 files_backend_op_t ops[]; 638 int n_ops; 639 const char *filename; 640 int min_bufsize; 641 files_hash_t *fhp; 642 { 643 files_backend_ptr_t be; 644 645 if ((be = (files_backend_ptr_t)malloc(sizeof (*be))) == 0) { 646 return (0); 647 } 648 be->ops = ops; 649 be->n_ops = n_ops; 650 be->filename = filename; 651 be->minbuf = min_bufsize; 652 be->f = 0; 653 be->buf = 0; 654 be->hashinfo = fhp; 655 656 if (fhp != NULL) { 657 (void) mutex_lock(&fhp->fh_lock); 658 fhp->fh_refcnt++; 659 (void) mutex_unlock(&fhp->fh_lock); 660 } 661 662 return ((nss_backend_t *)be); 663 } 664 665 int 666 _nss_files_check_name_colon(nss_XbyY_args_t *argp, const char *line, 667 int linelen) 668 { 669 const char *linep, *limit; 670 const char *keyp = argp->key.name; 671 672 linep = line; 673 limit = line + linelen; 674 while (*keyp && linep < limit && *keyp == *linep) { 675 keyp++; 676 linep++; 677 } 678 return (linep < limit && *keyp == '\0' && *linep == ':'); 679 } 680 681 /* 682 * This routine is used to parse lines of the form: 683 * name number aliases 684 * It returns 1 if the key in argp matches any one of the 685 * names in the line, otherwise 0 686 * Used by rpc, networks, protocols 687 */ 688 int 689 _nss_files_check_name_aliases(nss_XbyY_args_t *argp, const char *line, 690 int linelen) 691 { 692 const char *limit, *linep, *keyp; 693 694 linep = line; 695 limit = line + linelen; 696 keyp = argp->key.name; 697 698 /* compare name */ 699 while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) { 700 keyp++; 701 linep++; 702 } 703 if (*keyp == '\0' && linep < limit && isspace(*linep)) 704 return (1); 705 /* skip remainder of the name, if any */ 706 while (linep < limit && !isspace(*linep)) 707 linep++; 708 /* skip the delimiting spaces */ 709 while (linep < limit && isspace(*linep)) 710 linep++; 711 /* compare with the aliases */ 712 while (linep < limit) { 713 /* 714 * 1st pass: skip number 715 * Other passes: skip remainder of the alias name, if any 716 */ 717 while (linep < limit && !isspace(*linep)) 718 linep++; 719 /* skip the delimiting spaces */ 720 while (linep < limit && isspace(*linep)) 721 linep++; 722 /* compare with the alias name */ 723 keyp = argp->key.name; 724 while (*keyp && linep < limit && !isspace(*linep) && 725 *keyp == *linep) { 726 keyp++; 727 linep++; 728 } 729 if (*keyp == '\0' && (linep == limit || isspace(*linep))) 730 return (1); 731 } 732 return (0); 733 } 734