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