xref: /illumos-gate/usr/src/lib/nsswitch/user/common/user_common.c (revision 012e6ce759c490003aed29439cc47d3d73a99ad3)
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 /*
30  * An implementation that used mmap() sensibly would be a wonderful thing,
31  *   but this here is just yer standard fgets() thang.
32  */
33 
34 #include "user_common.h"
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <ctype.h>
38 #include <fcntl.h>
39 #include <poll.h>
40 #include <unistd.h>
41 #include <sys/stat.h>
42 #include <string.h>
43 
44 /*ARGSUSED*/
45 nss_status_t
46 _nss_user_setent(be, dummy)
47 	user_backend_ptr_t	be;
48 	void			*dummy;
49 {
50 	if (be->f == 0) {
51 		if (be->filename == 0) {
52 			/* Backend isn't initialized properly? */
53 			return (NSS_UNAVAIL);
54 		}
55 		if ((be->f = fopen(be->filename, "rF")) == 0) {
56 			return (NSS_UNAVAIL);
57 		}
58 	} else {
59 		rewind(be->f);
60 	}
61 	return (NSS_SUCCESS);
62 }
63 
64 /*ARGSUSED*/
65 nss_status_t
66 _nss_user_endent(be, dummy)
67 	user_backend_ptr_t	be;
68 	void			*dummy;
69 {
70 	if (be->f != 0) {
71 		(void) fclose(be->f);
72 		be->f = 0;
73 	}
74 	if (be->buf != 0) {
75 		free(be->buf);
76 		be->buf = 0;
77 	}
78 	return (NSS_SUCCESS);
79 }
80 
81 /*
82  * This routine reads a line, including the processing of continuation
83  * characters.  It always leaves (or inserts) \n\0 at the end of the line.
84  * It returns the length of the line read, excluding the \n\0.  Who's idea
85  * was this?
86  * Returns -1 on EOF.
87  *
88  * Note that since each concurrent call to _nss_user_read_line has
89  * it's own FILE pointer, we can use getc_unlocked w/o difficulties,
90  * a substantial performance win.
91  */
92 int
93 _nss_user_read_line(f, buffer, buflen)
94 	FILE			*f;
95 	char			*buffer;
96 	int			buflen;
97 {
98 	int			linelen;	/* 1st unused slot in buffer */
99 	int			c;
100 
101 	/*CONSTCOND*/
102 	while (1) {
103 		linelen = 0;
104 		while (linelen < buflen - 1) {	/* "- 1" saves room for \n\0 */
105 			switch (c = getc_unlocked(f)) {
106 			case EOF:
107 				if (linelen == 0 ||
108 				    buffer[linelen - 1] == '\\') {
109 					return (-1);
110 				} else {
111 					buffer[linelen    ] = '\n';
112 					buffer[linelen + 1] = '\0';
113 					return (linelen);
114 				}
115 			case '\n':
116 				if (linelen > 0 &&
117 				    buffer[linelen - 1] == '\\') {
118 					--linelen;  /* remove the '\\' */
119 				} else {
120 					buffer[linelen    ] = '\n';
121 					buffer[linelen + 1] = '\0';
122 					return (linelen);
123 				}
124 				break;
125 			default:
126 				buffer[linelen++] = c;
127 			}
128 		}
129 		/* Buffer overflow -- eat rest of line and loop again */
130 		/* ===> Should syslog() */
131 		do {
132 			c = getc_unlocked(f);
133 			if (c == EOF) {
134 				return (-1);
135 			}
136 		} while (c != '\n');
137 	}
138 	/*NOTREACHED*/
139 }
140 
141 
142 /*
143  * Could implement this as an iterator function on top of _nss_user_do_all(),
144  *   but the shared code is small enough that it'd be pretty silly.
145  */
146 nss_status_t
147 _nss_user_XY_all(be, args, netdb, filter, check)
148 	user_backend_ptr_t	be;
149 	nss_XbyY_args_t		*args;
150 	int			netdb;		/* whether it uses netdb */
151 						/* format or not */
152 	const char		*filter;	/* advisory, to speed up */
153 						/* string search */
154 	user_XY_check_func	check;	/* NULL means one-shot, for getXXent */
155 {
156 	nss_status_t		res;
157 	int	parsestat;
158 
159 	if (be->buf == 0 &&
160 		(be->buf = malloc(be->minbuf)) == 0) {
161 		return (NSS_UNAVAIL); /* really panic, malloc failed */
162 	}
163 
164 	if (check != 0 || be->f == 0) {
165 		if ((res = _nss_user_setent(be, 0)) != NSS_SUCCESS) {
166 			return (res);
167 		}
168 	}
169 
170 	res = NSS_NOTFOUND;
171 
172 	/*CONSTCOND*/
173 	while (1) {
174 		char		*instr	= be->buf;
175 		int		linelen;
176 
177 		if ((linelen = _nss_user_read_line(be->f, instr,
178 		    be->minbuf)) < 0) {
179 			/* End of file */
180 			args->returnval = 0;
181 			args->erange    = 0;
182 			break;
183 		}
184 		if (filter != 0 && strstr(instr, filter) == 0) {
185 			/*
186 			 * Optimization:  if the entry doesn't contain the
187 			 *   filter string then it can't be the entry we want,
188 			 *   so don't bother looking more closely at it.
189 			 */
190 			continue;
191 		}
192 		if (netdb) {
193 			char		*first;
194 			char		*last;
195 
196 			if ((last = strchr(instr, '#')) == 0) {
197 				last = instr + linelen;
198 			}
199 			*last-- = '\0';		/* Nuke '\n' or #comment */
200 
201 			/*
202 			 * Skip leading whitespace.  Normally there isn't
203 			 *   any, so it's not worth calling strspn().
204 			 */
205 			for (first = instr;  isspace(*first);  first++) {
206 				;
207 			}
208 			if (*first == '\0') {
209 				continue;
210 			}
211 			/*
212 			 * Found something non-blank on the line.  Skip back
213 			 * over any trailing whitespace;  since we know
214 			 * there's non-whitespace earlier in the line,
215 			 * checking for termination is easy.
216 			 */
217 			while (isspace(*last)) {
218 				--last;
219 			}
220 
221 			linelen = last - first + 1;
222 			if (first != instr) {
223 					instr = first;
224 			}
225 		}
226 
227 		args->returnval = 0;
228 		parsestat = (*args->str2ent)(instr, linelen, args->buf.result,
229 				args->buf.buffer, args->buf.buflen);
230 
231 		if (parsestat == NSS_STR_PARSE_SUCCESS) {
232 			args->returnval = args->buf.result;
233 			if (check == 0 || (*check)(args)) {
234 				res = NSS_SUCCESS;
235 				break;
236 			}
237 		} else if (parsestat == NSS_STR_PARSE_ERANGE) {
238 			args->erange = 1;	/* should we just skip this */
239 						/* one long line ?? */
240 		} /* else if (parsestat == NSS_STR_PARSE_PARSE) don't care ! */
241 	}
242 
243 	/*
244 	 * stayopen is set to 0 by default in order to close the opened
245 	 * file.  Some applications may break if it is set to 1.
246 	 */
247 	if (check != 0 && !args->stayopen) {
248 		(void) _nss_user_endent(be, 0);
249 	}
250 
251 	return (res);
252 }
253 
254 
255 /*ARGSUSED*/
256 nss_status_t
257 _nss_user_destr(be, dummy)
258 	user_backend_ptr_t	be;
259 	void			*dummy;
260 {
261 	if (be != 0) {
262 		if (be->f != 0) {
263 			(void) _nss_user_endent(be, 0);
264 		}
265 		free((char *)be->filename);
266 		free(be);
267 	}
268 	return (NSS_SUCCESS);	/* In case anyone is dumb enough to check */
269 }
270 
271 nss_backend_t *
272 _nss_user_constr(ops, n_ops, filename, min_bufsize)
273 	user_backend_op_t	ops[];
274 	int			n_ops;
275 	const char		*filename;
276 	int			min_bufsize;
277 {
278 	user_backend_ptr_t	be;
279 
280 	if ((be = (user_backend_ptr_t)malloc(sizeof (*be))) == 0) {
281 		return (0);
282 	}
283 	be->ops		= ops;
284 	be->n_ops	= n_ops;
285 	if ((be->filename = strdup(filename)) == NULL) {
286 		free(be);
287 		return (NULL);
288 	}
289 	be->minbuf	= min_bufsize;
290 	be->f		= 0;
291 	be->buf		= 0;
292 
293 	return ((nss_backend_t *)be);
294 }
295