xref: /illumos-gate/usr/src/lib/nsswitch/files/common/files_common.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
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 #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 	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 	(void) _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 (filter != NULL && *filter == '\0')
213 		return (NSS_NOTFOUND);
214 	if (be->buf == 0 &&
215 		(be->buf = malloc(be->minbuf)) == 0) {
216 		return (NSS_UNAVAIL); /* really panic, malloc failed */
217 	}
218 
219 	if (check != 0 || be->f == 0) {
220 		if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS) {
221 			return (res);
222 		}
223 	}
224 
225 	res = NSS_NOTFOUND;
226 
227 	/*CONSTCOND*/
228 	while (1) {
229 		char		*instr	= be->buf;
230 		int		linelen;
231 
232 		if ((linelen = _nss_files_read_line(be->f, instr,
233 		    be->minbuf)) < 0) {
234 			/* End of file */
235 			args->returnval = 0;
236 			args->returnlen = 0;
237 			break;
238 		}
239 		if (filter != 0 && strstr(instr, filter) == 0) {
240 			/*
241 			 * Optimization:  if the entry doesn't contain the
242 			 *   filter string then it can't be the entry we want,
243 			 *   so don't bother looking more closely at it.
244 			 */
245 			continue;
246 		}
247 		if (netdb) {
248 			char		*first;
249 			char		*last;
250 
251 			if ((last = strchr(instr, '#')) == 0) {
252 				last = instr + linelen;
253 			}
254 			*last-- = '\0';		/* Nuke '\n' or #comment */
255 
256 			/*
257 			 * Skip leading whitespace.  Normally there isn't
258 			 *   any, so it's not worth calling strspn().
259 			 */
260 			for (first = instr;  isspace(*first);  first++) {
261 				;
262 			}
263 			if (*first == '\0') {
264 				continue;
265 			}
266 			/*
267 			 * Found something non-blank on the line.  Skip back
268 			 * over any trailing whitespace;  since we know
269 			 * there's non-whitespace earlier in the line,
270 			 * checking for termination is easy.
271 			 */
272 			while (isspace(*last)) {
273 				--last;
274 			}
275 
276 			linelen = last - first + 1;
277 			if (first != instr) {
278 					instr = first;
279 			}
280 		}
281 
282 		args->returnval = 0;
283 		args->returnlen = 0;
284 
285 		if (check != NULL && (*check)(args, instr, linelen) == 0)
286 			continue;
287 
288 		func = args->str2ent;
289 		parsestat = (*func)(instr, linelen, args->buf.result,
290 					args->buf.buffer, args->buf.buflen);
291 
292 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
293 			args->returnval = (args->buf.result != NULL)?
294 					args->buf.result : args->buf.buffer;
295 			args->returnlen = linelen;
296 			res = NSS_SUCCESS;
297 			break;
298 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
299 			args->erange = 1;
300 			break;
301 		} /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */
302 	}
303 
304 	/*
305 	 * stayopen is set to 0 by default in order to close the opened
306 	 * file.  Some applications may break if it is set to 1.
307 	 */
308 	if (check != 0 && !args->stayopen) {
309 		(void) _nss_files_endent(be, 0);
310 	}
311 
312 	return (res);
313 }
314 
315 /*
316  * File hashing support.  Critical for sites with large (e.g. 1000+ lines)
317  * /etc/passwd or /etc/group files.  Currently only used by getpw*() and
318  * getgr*() routines, but any files backend can use this stuff.
319  */
320 static void
321 _nss_files_hash_destroy(files_hash_t *fhp)
322 {
323 	free(fhp->fh_table);
324 	fhp->fh_table = NULL;
325 	free(fhp->fh_line);
326 	fhp->fh_line = NULL;
327 	free(fhp->fh_file_start);
328 	fhp->fh_file_start = NULL;
329 }
330 #ifdef PIC
331 /*
332  * It turns out the hashing stuff really needs to be disabled for processes
333  * other than the nscd; the consumption of swap space and memory is otherwise
334  * unacceptable when the nscd is killed w/ a large passwd file (4M) active.
335  * See 4031930 for details.
336  * So we just use this psuedo function to enable the hashing feature.  Since
337  * this function name is private, we just create a function w/ the name
338  *  __nss_use_files_hash in the nscd itself and everyone else uses the old
339  * interface.
340  * We also disable hashing for .a executables to avoid problems with large
341  * files....
342  */
343 
344 #pragma weak __nss_use_files_hash
345 
346 extern void  __nss_use_files_hash(void);
347 #endif /* pic */
348 
349 /*ARGSUSED*/
350 nss_status_t
351 _nss_files_XY_hash(files_backend_ptr_t be, nss_XbyY_args_t *args,
352 	int netdb, files_hash_t *fhp, int hashop, files_XY_check_func check)
353 {
354 	/* LINTED E_FUNC_VAR_UNUSED */
355 	int fd, retries, ht;
356 	/* LINTED E_FUNC_VAR_UNUSED */
357 	uint_t hash, line, f;
358 	/* LINTED E_FUNC_VAR_UNUSED */
359 	files_hashent_t *hp, *htab;
360 	/* LINTED E_FUNC_VAR_UNUSED */
361 	char *cp, *first, *last;
362 	/* LINTED E_FUNC_VAR_UNUSED */
363 	nss_XbyY_args_t xargs;
364 	/* LINTED E_FUNC_VAR_UNUSED */
365 	struct stat64 st;
366 
367 #ifndef PIC
368 	return (_nss_files_XY_all(be, args, netdb, 0, check));
369 }
370 #else
371 	if (__nss_use_files_hash == 0)
372 		return (_nss_files_XY_all(be, args, netdb, 0, check));
373 
374 	mutex_lock(&fhp->fh_lock);
375 retry:
376 	retries = 100;
377 	while (stat64(be->filename, &st) < 0) {
378 		/*
379 		 * On a healthy system this can't happen except during brief
380 		 * periods when the file is being modified/renamed.  Keep
381 		 * trying until things settle down, but eventually give up.
382 		 */
383 		if (--retries == 0)
384 			goto unavail;
385 		poll(0, 0, 100);
386 	}
387 
388 	if (st.st_mtim.tv_sec == fhp->fh_mtime.tv_sec &&
389 	    st.st_mtim.tv_nsec == fhp->fh_mtime.tv_nsec &&
390 	    fhp->fh_table != NULL) {
391 		htab = &fhp->fh_table[hashop * fhp->fh_size];
392 		hash = fhp->fh_hash_func[hashop](args, 1, NULL, 0);
393 		for (hp = htab[hash % fhp->fh_size].h_first; hp != NULL;
394 		    hp = hp->h_next) {
395 			if (hp->h_hash != hash)
396 				continue;
397 			line = hp - htab;
398 			if ((*check)(args, fhp->fh_line[line].l_start,
399 					fhp->fh_line[line].l_len) == 0)
400 				continue;
401 			if ((*args->str2ent)(fhp->fh_line[line].l_start,
402 			    fhp->fh_line[line].l_len, args->buf.result,
403 			    args->buf.buffer, args->buf.buflen) ==
404 			    NSS_STR_PARSE_SUCCESS) {
405 				args->returnval = (args->buf.result)?
406 					args->buf.result:args->buf.buffer;
407 				args->returnlen = fhp->fh_line[line].l_len;
408 				mutex_unlock(&fhp->fh_lock);
409 				return (NSS_SUCCESS);
410 			} else {
411 				args->erange = 1;
412 			}
413 		}
414 		args->returnval = 0;
415 		args->returnlen = 0;
416 		mutex_unlock(&fhp->fh_lock);
417 		return (NSS_NOTFOUND);
418 	}
419 
420 	_nss_files_hash_destroy(fhp);
421 
422 	if (st.st_size > SSIZE_MAX)
423 		goto unavail;
424 
425 	if ((fhp->fh_file_start = malloc((ssize_t)st.st_size + 1)) == NULL)
426 		goto unavail;
427 
428 	if ((fd = open(be->filename, O_RDONLY)) < 0)
429 		goto unavail;
430 
431 	if (read(fd, fhp->fh_file_start, (ssize_t)st.st_size) !=
432 	    (ssize_t)st.st_size) {
433 		close(fd);
434 		goto retry;
435 	}
436 
437 	close(fd);
438 
439 	fhp->fh_file_end = fhp->fh_file_start + (off_t)st.st_size;
440 	*fhp->fh_file_end = '\n';
441 	fhp->fh_mtime = st.st_mtim;
442 
443 	/*
444 	 * If the file changed since we read it, or if it's less than
445 	 * 1-2 seconds old, don't trust it; its modification may still
446 	 * be in progress.  The latter is a heuristic hack to minimize
447 	 * the likelihood of damage if someone modifies /etc/mumble
448 	 * directly (as opposed to editing and renaming a temp file).
449 	 *
450 	 * Note: the cast to u_int is there in case (1) someone rdated
451 	 * the system backwards since the last modification of /etc/mumble
452 	 * or (2) this is a diskless client whose time is badly out of sync
453 	 * with its server.  The 1-2 second age hack doesn't cover these
454 	 * cases -- oh well.
455 	 */
456 	if (stat64(be->filename, &st) < 0 ||
457 	    st.st_mtim.tv_sec != fhp->fh_mtime.tv_sec ||
458 	    st.st_mtim.tv_nsec != fhp->fh_mtime.tv_nsec ||
459 	    (uint_t)(time(0) - st.st_mtim.tv_sec + 2) < 4) {
460 		poll(0, 0, 1000);
461 		goto retry;
462 	}
463 
464 	line = 1;
465 	for (cp = fhp->fh_file_start; cp < fhp->fh_file_end; cp++)
466 		if (*cp == '\n')
467 			line++;
468 
469 	for (f = 2; f * f <= line; f++) {	/* find next largest prime */
470 		if (line % f == 0) {
471 			f = 1;
472 			line++;
473 		}
474 	}
475 
476 	fhp->fh_size = line;
477 	fhp->fh_line = malloc(line * sizeof (files_linetab_t));
478 	fhp->fh_table = calloc(line * fhp->fh_nhtab, sizeof (files_hashent_t));
479 	if (fhp->fh_line == NULL || fhp->fh_table == NULL)
480 		goto unavail;
481 
482 	line = 0;
483 	cp = fhp->fh_file_start;
484 	while (cp < fhp->fh_file_end) {
485 		first = cp;
486 		while (*cp != '\n')
487 			cp++;
488 		if (cp > first && *(cp - 1) == '\\') {
489 			memmove(first + 2, first, cp - first - 1);
490 			cp = first + 2;
491 			continue;
492 		}
493 		last = cp;
494 		*cp++ = '\0';
495 		if (netdb) {
496 			if ((last = strchr(first, '#')) == 0)
497 				last = cp - 1;
498 			*last-- = '\0';		/* nuke '\n' or #comment */
499 			while (isspace(*first))	/* nuke leading whitespace */
500 				first++;
501 			if (*first == '\0')	/* skip content-free lines */
502 				continue;
503 			while (isspace(*last))	/* nuke trailing whitespace */
504 				--last;
505 			*++last = '\0';
506 		}
507 		for (ht = 0; ht < fhp->fh_nhtab; ht++) {
508 			hp = &fhp->fh_table[ht * fhp->fh_size + line];
509 			hp->h_hash = fhp->fh_hash_func[ht](&xargs, 0, first,
510 					last - first);
511 		}
512 		fhp->fh_line[line].l_start = first;
513 		fhp->fh_line[line++].l_len = last - first;
514 	}
515 
516 	/*
517 	 * Populate the hash tables in reverse order so that the hash chains
518 	 * end up in forward order.  This ensures that hashed lookups find
519 	 * things in the same order that a linear search of the file would.
520 	 * This is essential in cases where there could be multiple matches.
521 	 * For example: until 2.7, root and smtp both had uid 0; but we
522 	 * certainly wouldn't want getpwuid(0) to return smtp.
523 	 */
524 	for (ht = 0; ht < fhp->fh_nhtab; ht++) {
525 		htab = &fhp->fh_table[ht * fhp->fh_size];
526 		for (hp = &htab[line - 1]; hp >= htab; hp--) {
527 			uint_t bucket = hp->h_hash % fhp->fh_size;
528 			hp->h_next = htab[bucket].h_first;
529 			htab[bucket].h_first = hp;
530 		}
531 	}
532 
533 	goto retry;
534 
535 unavail:
536 	_nss_files_hash_destroy(fhp);
537 	mutex_unlock(&fhp->fh_lock);
538 	return (NSS_UNAVAIL);
539 }
540 #endif /* PIC */
541 
542 nss_status_t
543 _nss_files_getent_rigid(be, a)
544 	files_backend_ptr_t	be;
545 	void			*a;
546 {
547 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
548 
549 	return (_nss_files_XY_all(be, args, 0, 0, 0));
550 }
551 
552 nss_status_t
553 _nss_files_getent_netdb(be, a)
554 	files_backend_ptr_t	be;
555 	void			*a;
556 {
557 	nss_XbyY_args_t		*args = (nss_XbyY_args_t *)a;
558 
559 	return (_nss_files_XY_all(be, args, 1, 0, 0));
560 }
561 
562 /*ARGSUSED*/
563 nss_status_t
564 _nss_files_destr(be, dummy)
565 	files_backend_ptr_t	be;
566 	void			*dummy;
567 {
568 	if (be != 0) {
569 		if (be->f != 0) {
570 			(void) _nss_files_endent(be, 0);
571 		}
572 		if (be->hashinfo != NULL) {
573 			(void) mutex_lock(&be->hashinfo->fh_lock);
574 			if (--be->hashinfo->fh_refcnt == 0)
575 				_nss_files_hash_destroy(be->hashinfo);
576 			(void) mutex_unlock(&be->hashinfo->fh_lock);
577 		}
578 		free(be);
579 	}
580 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
581 }
582 
583 nss_backend_t *
584 _nss_files_constr(ops, n_ops, filename, min_bufsize, fhp)
585 	files_backend_op_t	ops[];
586 	int			n_ops;
587 	const char		*filename;
588 	int			min_bufsize;
589 	files_hash_t		*fhp;
590 {
591 	files_backend_ptr_t	be;
592 
593 	if ((be = (files_backend_ptr_t)malloc(sizeof (*be))) == 0) {
594 		return (0);
595 	}
596 	be->ops		= ops;
597 	be->n_ops	= n_ops;
598 	be->filename	= filename;
599 	be->minbuf	= min_bufsize;
600 	be->f		= 0;
601 	be->buf		= 0;
602 	be->hashinfo	= fhp;
603 
604 	if (fhp != NULL) {
605 		(void) mutex_lock(&fhp->fh_lock);
606 		fhp->fh_refcnt++;
607 		(void) mutex_unlock(&fhp->fh_lock);
608 	}
609 
610 	return ((nss_backend_t *)be);
611 }
612 
613 int
614 _nss_files_check_name_colon(nss_XbyY_args_t *argp, const char *line,
615 	int linelen)
616 {
617 	const char	*linep, *limit;
618 	const char	*keyp = argp->key.name;
619 
620 	linep = line;
621 	limit = line + linelen;
622 	while (*keyp && linep < limit && *keyp == *linep) {
623 		keyp++;
624 		linep++;
625 	}
626 	return (linep < limit && *keyp == '\0' && *linep == ':');
627 }
628 
629 /*
630  * This routine is used to parse lines of the form:
631  * 	name number aliases
632  * It returns 1 if the key in argp matches any one of the
633  * names in the line, otherwise 0
634  * Used by rpc, networks, protocols
635  */
636 int
637 _nss_files_check_name_aliases(nss_XbyY_args_t *argp, const char *line,
638 	int linelen)
639 {
640 	const char	*limit, *linep, *keyp;
641 
642 	linep = line;
643 	limit = line + linelen;
644 	keyp = argp->key.name;
645 
646 	/* compare name */
647 	while (*keyp && linep < limit && !isspace(*linep) && *keyp == *linep) {
648 		keyp++;
649 		linep++;
650 	}
651 	if (*keyp == '\0' && linep < limit && isspace(*linep))
652 		return (1);
653 	/* skip remainder of the name, if any */
654 	while (linep < limit && !isspace(*linep))
655 		linep++;
656 	/* skip the delimiting spaces */
657 	while (linep < limit && isspace(*linep))
658 		linep++;
659 	/* compare with the aliases */
660 	while (linep < limit) {
661 		/*
662 		 * 1st pass: skip number
663 		 * Other passes: skip remainder of the alias name, if any
664 		 */
665 		while (linep < limit && !isspace(*linep))
666 			linep++;
667 		/* skip the delimiting spaces */
668 		while (linep < limit && isspace(*linep))
669 			linep++;
670 		/* compare with the alias name */
671 		keyp = argp->key.name;
672 		while (*keyp && linep < limit && !isspace(*linep) &&
673 				*keyp == *linep) {
674 			keyp++;
675 			linep++;
676 		}
677 		if (*keyp == '\0' && (linep == limit || isspace(*linep)))
678 			return (1);
679 	}
680 	return (0);
681 }
682