xref: /illumos-gate/usr/src/lib/nsswitch/files/common/files_common.c (revision ca9327a6de44d69ddab3668cc1e143ce781387a3)
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