xref: /titanic_50/usr/src/lib/nsswitch/files/common/files_common.c (revision 88df2d76721d60b8b7cad14f9380446d06569f7c)
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