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