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