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 "user" backends.
26  * Much of this was taken directly from the files_common.c source.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 /*
32  * An implementation that used mmap() sensibly would be a wonderful thing,
33  *   but this here is just yer standard fgets() thang.
34  */
35 
36 #include "user_common.h"
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <ctype.h>
40 #include <fcntl.h>
41 #include <poll.h>
42 #include <unistd.h>
43 #include <sys/stat.h>
44 #include <string.h>
45 
46 /*ARGSUSED*/
47 nss_status_t
_nss_user_setent(be,dummy)48 _nss_user_setent(be, dummy)
49 	user_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
_nss_user_endent(be,dummy)68 _nss_user_endent(be, dummy)
69 	user_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_user_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
_nss_user_read_line(f,buffer,buflen)95 _nss_user_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 /*
145  * Could implement this as an iterator function on top of _nss_user_do_all(),
146  *   but the shared code is small enough that it'd be pretty silly.
147  */
148 nss_status_t
_nss_user_XY_all(be,args,netdb,filter,check)149 _nss_user_XY_all(be, args, netdb, filter, check)
150 	user_backend_ptr_t	be;
151 	nss_XbyY_args_t		*args;
152 	int			netdb;		/* whether it uses netdb */
153 						/* format or not */
154 	const char		*filter;	/* advisory, to speed up */
155 						/* string search */
156 	user_XY_check_func	check;	/* NULL means one-shot, for getXXent */
157 {
158 	nss_status_t		res;
159 	int	parsestat;
160 
161 	if (be->buf == 0 &&
162 		(be->buf = malloc(be->minbuf)) == 0) {
163 		return (NSS_UNAVAIL); /* really panic, malloc failed */
164 	}
165 
166 	if (check != 0 || be->f == 0) {
167 		if ((res = _nss_user_setent(be, 0)) != NSS_SUCCESS) {
168 			return (res);
169 		}
170 	}
171 
172 	res = NSS_NOTFOUND;
173 
174 	/*CONSTCOND*/
175 	while (1) {
176 		char		*instr	= be->buf;
177 		int		linelen;
178 
179 		if ((linelen = _nss_user_read_line(be->f, instr,
180 		    be->minbuf)) < 0) {
181 			/* End of file */
182 			args->returnval = 0;
183 			args->erange    = 0;
184 			break;
185 		}
186 		if (filter != 0 && strstr(instr, filter) == 0) {
187 			/*
188 			 * Optimization:  if the entry doesn't contain the
189 			 *   filter string then it can't be the entry we want,
190 			 *   so don't bother looking more closely at it.
191 			 */
192 			continue;
193 		}
194 		if (netdb) {
195 			char		*first;
196 			char		*last;
197 
198 			if ((last = strchr(instr, '#')) == 0) {
199 				last = instr + linelen;
200 			}
201 			*last-- = '\0';		/* Nuke '\n' or #comment */
202 
203 			/*
204 			 * Skip leading whitespace.  Normally there isn't
205 			 *   any, so it's not worth calling strspn().
206 			 */
207 			for (first = instr;  isspace(*first);  first++) {
208 				;
209 			}
210 			if (*first == '\0') {
211 				continue;
212 			}
213 			/*
214 			 * Found something non-blank on the line.  Skip back
215 			 * over any trailing whitespace;  since we know
216 			 * there's non-whitespace earlier in the line,
217 			 * checking for termination is easy.
218 			 */
219 			while (isspace(*last)) {
220 				--last;
221 			}
222 
223 			linelen = last - first + 1;
224 			if (first != instr) {
225 					instr = first;
226 			}
227 		}
228 
229 		args->returnval = 0;
230 		parsestat = (*args->str2ent)(instr, linelen, args->buf.result,
231 				args->buf.buffer, args->buf.buflen);
232 
233 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
234 			args->returnval = args->buf.result;
235 			if (check == 0 || (*check)(args)) {
236 				res = NSS_SUCCESS;
237 				break;
238 			}
239 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
240 			args->erange = 1;	/* should we just skip this */
241 						/* one long line ?? */
242 		} /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */
243 	}
244 
245 	/*
246 	 * stayopen is set to 0 by default in order to close the opened
247 	 * file.  Some applications may break if it is set to 1.
248 	 */
249 	if (check != 0 && !args->stayopen) {
250 		(void) _nss_user_endent(be, 0);
251 	}
252 
253 	return (res);
254 }
255 
256 
257 /*ARGSUSED*/
258 nss_status_t
_nss_user_destr(be,dummy)259 _nss_user_destr(be, dummy)
260 	user_backend_ptr_t	be;
261 	void			*dummy;
262 {
263 	if (be != 0) {
264 		if (be->f != 0) {
265 			(void) _nss_user_endent(be, 0);
266 		}
267 		free((char *)be->filename);
268 		free(be);
269 	}
270 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
271 }
272 
273 nss_backend_t *
_nss_user_constr(ops,n_ops,filename,min_bufsize)274 _nss_user_constr(ops, n_ops, filename, min_bufsize)
275 	user_backend_op_t	ops[];
276 	int			n_ops;
277 	const char		*filename;
278 	int			min_bufsize;
279 {
280 	user_backend_ptr_t	be;
281 
282 	if ((be = (user_backend_ptr_t)malloc(sizeof (*be))) == 0) {
283 		return (0);
284 	}
285 	be->ops		= ops;
286 	be->n_ops	= n_ops;
287 	if ((be->filename = strdup(filename)) == NULL) {
288 		free(be);
289 		return (NULL);
290 	}
291 	be->minbuf	= min_bufsize;
292 	be->f		= 0;
293 	be->buf		= 0;
294 
295 	return ((nss_backend_t *)be);
296 }
297