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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 1995-2003 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * Common code and structures used by name-service-switch "files" backends. 27 */ 28 29 #pragma ident "%Z%%M% %I% %E% SMI" 30 31 /* 32 * An implementation that used mmap() sensibly would be a wonderful thing, 33 * but this here is just yer standard fgets() thang. 34 */ 35 36 #include "files_common.h" 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <ctype.h> 41 #include <fcntl.h> 42 #include <poll.h> 43 #include <unistd.h> 44 #include <sys/stat.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 = __nsl_fopen(be->filename, "r")) == 0) { 58 return (NSS_UNAVAIL); 59 } 60 } else { 61 __nsl_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 __nsl_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 __NSL_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 = __nsl_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 = __nsl_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 char *buffer; 154 int buflen; 155 nss_status_t res; 156 157 if (be->buf == 0 && 158 (be->buf = malloc(be->minbuf)) == 0) { 159 return (NSS_UNAVAIL); 160 } 161 buffer = be->buf; 162 buflen = be->minbuf; 163 164 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 165 return (res); 166 } 167 168 res = NSS_NOTFOUND; 169 170 do { 171 int linelen; 172 173 if ((linelen = _nss_files_read_line(be->f, buffer, 174 buflen)) < 0) { 175 /* End of file */ 176 break; 177 } 178 if (filter != 0 && strstr(buffer, filter) == 0) { 179 /* 180 * Optimization: if the entry doesn't contain the 181 * filter string then it can't be the entry we want, 182 * so don't bother looking more closely at it. 183 */ 184 continue; 185 } 186 res = (*func)(buffer, linelen, args); 187 188 } while (res == NSS_NOTFOUND); 189 190 _nss_files_endent(be, 0); 191 return (res); 192 } 193 194 /* 195 * Could implement this as an iterator function on top of _nss_files_do_all(), 196 * but the shared code is small enough that it'd be pretty silly. 197 */ 198 nss_status_t 199 _nss_files_XY_all(be, args, netdb, filter, check) 200 files_backend_ptr_t be; 201 nss_XbyY_args_t *args; 202 int netdb; /* whether it uses netdb */ 203 /* format or not */ 204 const char *filter; /* advisory, to speed up */ 205 /* string search */ 206 files_XY_check_func check; /* NULL means one-shot, for getXXent */ 207 { 208 nss_status_t res; 209 int parsestat; 210 int (*func)(); 211 212 if (be->buf == 0 && 213 (be->buf = malloc(be->minbuf)) == 0) { 214 return (NSS_UNAVAIL); /* really panic, malloc failed */ 215 } 216 217 if (check != 0 || be->f == 0) { 218 if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) { 219 return (res); 220 } 221 } 222 223 res = NSS_NOTFOUND; 224 225 /*CONSTCOND*/ 226 while (1) { 227 char *instr = be->buf; 228 int linelen; 229 230 if ((linelen = _nss_files_read_line(be->f, instr, 231 be->minbuf)) < 0) { 232 /* End of file */ 233 args->returnval = 0; 234 args->erange = 0; 235 break; 236 } 237 if (filter != 0 && strstr(instr, filter) == 0) { 238 /* 239 * Optimization: if the entry doesn't contain the 240 * filter string then it can't be the entry we want, 241 * so don't bother looking more closely at it. 242 */ 243 continue; 244 } 245 if (netdb) { 246 char *first; 247 char *last; 248 249 if ((last = strchr(instr, '#')) == 0) { 250 last = instr + linelen; 251 } 252 *last-- = '\0'; /* Nuke '\n' or #comment */ 253 254 /* 255 * Skip leading whitespace. Normally there isn't 256 * any, so it's not worth calling strspn(). 257 */ 258 for (first = instr; isspace(*first); first++) { 259 ; 260 } 261 if (*first == '\0') { 262 continue; 263 } 264 /* 265 * Found something non-blank on the line. Skip back 266 * over any trailing whitespace; since we know 267 * there's non-whitespace earlier in the line, 268 * checking for termination is easy. 269 */ 270 while (isspace(*last)) { 271 --last; 272 } 273 274 linelen = last - first + 1; 275 if (first != instr) { 276 instr = first; 277 } 278 } 279 280 args->returnval = 0; 281 282 func = args->str2ent; 283 parsestat = (*func)(instr, linelen, args->buf.result, 284 args->buf.buffer, args->buf.buflen); 285 286 if (parsestat == NSS_STR_PARSE_SUCCESS) { 287 args->returnval = args->buf.result; 288 if (check == 0 || (*check)(args)) { 289 res = NSS_SUCCESS; 290 break; 291 } 292 } else if (parsestat == NSS_STR_PARSE_ERANGE) { 293 args->erange = 1; 294 break; 295 } /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */ 296 } 297 298 /* 299 * stayopen is set to 0 by default in order to close the opened 300 * file. Some applications may break if it is set to 1. 301 */ 302 if (check != 0 && !args->stayopen) { 303 (void) _nss_files_endent(be, 0); 304 } 305 306 return (res); 307 } 308 309 /* 310 * File hashing support. Critical for sites with large (e.g. 1000+ lines) 311 * /etc/passwd or /etc/group files. Currently only used by getpw*() and 312 * getgr*() routines, but any files backend can use this stuff. 313 */ 314 static void 315 _nss_files_hash_destroy(files_hash_t *fhp) 316 { 317 free(fhp->fh_table); 318 fhp->fh_table = NULL; 319 free(fhp->fh_line); 320 fhp->fh_line = NULL; 321 free(fhp->fh_file_start); 322 fhp->fh_file_start = NULL; 323 } 324 #ifdef PIC 325 /* 326 * It turns out the hashing stuff really needs to be disabled for processes 327 * other than the nscd; the consumption of swap space and memory is otherwise 328 * unacceptable when the nscd is killed w/ a large passwd file (4M) active. 329 * See 4031930 for details. 330 * So we just use this psuedo function to enable the hashing feature. Since 331 * this function name is private, we just create a function w/ the name 332 * __nss_use_files_hash in the nscd itself and everyone else uses the old 333 * interface. 334 * We also disable hashing for .a executables to avoid problems with large 335 * files.... 336 */ 337 338 #pragma weak __nss_use_files_hash 339 340 extern void __nss_use_files_hash(void); 341 #endif /* pic */ 342 343 nss_status_t 344 _nss_files_XY_hash(files_backend_ptr_t be, nss_XbyY_args_t *args, 345 int netdb, files_hash_t *fhp, int hashop, files_XY_check_func check) 346 { 347 int fd, retries, ht; 348 uint_t hash, line, f; 349 files_hashent_t *hp, *htab; 350 char *cp, *first, *last; 351 nss_XbyY_args_t xargs; 352 struct stat64 st; 353 354 #ifndef PIC 355 return (_nss_files_XY_all(be, args, netdb, 0, check)); 356 } 357 #else 358 if (__nss_use_files_hash == 0) 359 return (_nss_files_XY_all(be, args, netdb, 0, check)); 360 361 mutex_lock(&fhp->fh_lock); 362 retry: 363 retries = 100; 364 while (stat64(be->filename, &st) < 0) { 365 /* 366 * On a healthy system this can't happen except during brief 367 * periods when the file is being modified/renamed. Keep 368 * trying until things settle down, but eventually give up. 369 */ 370 if (--retries == 0) 371 goto unavail; 372 poll(0, 0, 100); 373 } 374 375 if (st.st_mtim.tv_sec == fhp->fh_mtime.tv_sec && 376 st.st_mtim.tv_nsec == fhp->fh_mtime.tv_nsec && 377 fhp->fh_table != NULL) { 378 htab = &fhp->fh_table[hashop * fhp->fh_size]; 379 hash = fhp->fh_hash_func[hashop](args, 1); 380 for (hp = htab[hash % fhp->fh_size].h_first; hp != NULL; 381 hp = hp->h_next) { 382 if (hp->h_hash != hash) 383 continue; 384 line = hp - htab; 385 if ((*args->str2ent)(fhp->fh_line[line].l_start, 386 fhp->fh_line[line].l_len, args->buf.result, 387 args->buf.buffer, args->buf.buflen) == 388 NSS_STR_PARSE_SUCCESS) { 389 args->returnval = args->buf.result; 390 if ((*check)(args)) { 391 mutex_unlock(&fhp->fh_lock); 392 return (NSS_SUCCESS); 393 } 394 } else { 395 args->erange = 1; 396 } 397 } 398 args->returnval = 0; 399 mutex_unlock(&fhp->fh_lock); 400 return (NSS_NOTFOUND); 401 } 402 403 _nss_files_hash_destroy(fhp); 404 405 if (st.st_size > SSIZE_MAX) 406 goto unavail; 407 408 if ((fhp->fh_file_start = malloc((ssize_t)st.st_size + 1)) == NULL) 409 goto unavail; 410 411 if ((fd = open(be->filename, O_RDONLY)) < 0) 412 goto unavail; 413 414 if (read(fd, fhp->fh_file_start, (ssize_t)st.st_size) != 415 (ssize_t)st.st_size) { 416 close(fd); 417 goto retry; 418 } 419 420 close(fd); 421 422 fhp->fh_file_end = fhp->fh_file_start + (off_t)st.st_size; 423 *fhp->fh_file_end = '\n'; 424 fhp->fh_mtime = st.st_mtim; 425 426 /* 427 * If the file changed since we read it, or if it's less than 428 * 1-2 seconds old, don't trust it; its modification may still 429 * be in progress. The latter is a heuristic hack to minimize 430 * the likelihood of damage if someone modifies /etc/mumble 431 * directly (as opposed to editing and renaming a temp file). 432 * 433 * Note: the cast to u_int is there in case (1) someone rdated 434 * the system backwards since the last modification of /etc/mumble 435 * or (2) this is a diskless client whose time is badly out of sync 436 * with its server. The 1-2 second age hack doesn't cover these 437 * cases -- oh well. 438 */ 439 if (stat64(be->filename, &st) < 0 || 440 st.st_mtim.tv_sec != fhp->fh_mtime.tv_sec || 441 st.st_mtim.tv_nsec != fhp->fh_mtime.tv_nsec || 442 (uint_t)(time(0) - st.st_mtim.tv_sec + 2) < 4) { 443 poll(0, 0, 1000); 444 goto retry; 445 } 446 447 line = 1; 448 for (cp = fhp->fh_file_start; cp < fhp->fh_file_end; cp++) 449 if (*cp == '\n') 450 line++; 451 452 for (f = 2; f * f <= line; f++) { /* find next largest prime */ 453 if (line % f == 0) { 454 f = 1; 455 line++; 456 } 457 } 458 459 fhp->fh_size = line; 460 fhp->fh_line = malloc(line * sizeof (files_linetab_t)); 461 fhp->fh_table = calloc(line * fhp->fh_nhtab, sizeof (files_hashent_t)); 462 if (fhp->fh_line == NULL || fhp->fh_table == NULL) 463 goto unavail; 464 465 xargs = *args; 466 xargs.buf.result = malloc(fhp->fh_resultsize + fhp->fh_bufsize); 467 if (xargs.buf.result == NULL) 468 goto unavail; 469 xargs.buf.buffer = (char *)xargs.buf.result + fhp->fh_resultsize; 470 xargs.buf.buflen = fhp->fh_bufsize; 471 xargs.returnval = xargs.buf.result; 472 473 line = 0; 474 cp = fhp->fh_file_start; 475 while (cp < fhp->fh_file_end) { 476 first = cp; 477 while (*cp != '\n') 478 cp++; 479 if (cp > first && *(cp - 1) == '\\') { 480 memmove(first + 2, first, cp - first - 1); 481 cp = first + 2; 482 continue; 483 } 484 last = cp; 485 *cp++ = '\0'; 486 if (netdb) { 487 if ((last = strchr(first, '#')) == 0) 488 last = cp - 1; 489 *last-- = '\0'; /* nuke '\n' or #comment */ 490 while (isspace(*first)) /* nuke leading whitespace */ 491 first++; 492 if (*first == '\0') /* skip content-free lines */ 493 continue; 494 while (isspace(*last)) /* nuke trailing whitespace */ 495 --last; 496 *++last = '\0'; 497 } 498 if ((*xargs.str2ent)(first, last - first, 499 xargs.buf.result, xargs.buf.buffer, xargs.buf.buflen) != 500 NSS_STR_PARSE_SUCCESS) 501 continue; 502 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 503 hp = &fhp->fh_table[ht * fhp->fh_size + line]; 504 hp->h_hash = fhp->fh_hash_func[ht](&xargs, 0); 505 } 506 fhp->fh_line[line].l_start = first; 507 fhp->fh_line[line++].l_len = last - first; 508 } 509 free(xargs.buf.result); 510 511 /* 512 * Populate the hash tables in reverse order so that the hash chains 513 * end up in forward order. This ensures that hashed lookups find 514 * things in the same order that a linear search of the file would. 515 * This is essential in cases where there could be multiple matches. 516 * For example: until 2.7, root and smtp both had uid 0; but we 517 * certainly wouldn't want getpwuid(0) to return smtp. 518 */ 519 for (ht = 0; ht < fhp->fh_nhtab; ht++) { 520 htab = &fhp->fh_table[ht * fhp->fh_size]; 521 for (hp = &htab[line - 1]; hp >= htab; hp--) { 522 uint_t bucket = hp->h_hash % fhp->fh_size; 523 hp->h_next = htab[bucket].h_first; 524 htab[bucket].h_first = hp; 525 } 526 } 527 528 goto retry; 529 530 unavail: 531 _nss_files_hash_destroy(fhp); 532 mutex_unlock(&fhp->fh_lock); 533 return (NSS_UNAVAIL); 534 } 535 #endif /* PIC */ 536 537 nss_status_t 538 _nss_files_getent_rigid(be, a) 539 files_backend_ptr_t be; 540 void *a; 541 { 542 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 543 544 return (_nss_files_XY_all(be, args, 0, 0, 0)); 545 } 546 547 nss_status_t 548 _nss_files_getent_netdb(be, a) 549 files_backend_ptr_t be; 550 void *a; 551 { 552 nss_XbyY_args_t *args = (nss_XbyY_args_t *)a; 553 554 return (_nss_files_XY_all(be, args, 1, 0, 0)); 555 } 556 557 /*ARGSUSED*/ 558 nss_status_t 559 _nss_files_destr(be, dummy) 560 files_backend_ptr_t be; 561 void *dummy; 562 { 563 if (be != 0) { 564 if (be->f != 0) { 565 _nss_files_endent(be, 0); 566 } 567 if (be->hashinfo != NULL) { 568 mutex_lock(&be->hashinfo->fh_lock); 569 if (--be->hashinfo->fh_refcnt == 0) 570 _nss_files_hash_destroy(be->hashinfo); 571 mutex_unlock(&be->hashinfo->fh_lock); 572 } 573 free(be); 574 } 575 return (NSS_SUCCESS); /* In case anyone is dumb enough to check */ 576 } 577 578 nss_backend_t * 579 _nss_files_constr(ops, n_ops, filename, min_bufsize, fhp) 580 files_backend_op_t ops[]; 581 int n_ops; 582 const char *filename; 583 int min_bufsize; 584 files_hash_t *fhp; 585 { 586 files_backend_ptr_t be; 587 588 if ((be = (files_backend_ptr_t)malloc(sizeof (*be))) == 0) { 589 return (0); 590 } 591 be->ops = ops; 592 be->n_ops = n_ops; 593 be->filename = filename; 594 be->minbuf = min_bufsize; 595 be->f = 0; 596 be->buf = 0; 597 be->hashinfo = fhp; 598 599 if (fhp != NULL) { 600 mutex_lock(&fhp->fh_lock); 601 fhp->fh_refcnt++; 602 mutex_unlock(&fhp->fh_lock); 603 } 604 605 return ((nss_backend_t *)be); 606 } 607