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