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