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