xref: /netbsd-src/lib/libc/gen/getusershell.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: getusershell.c,v 1.26 2006/10/15 16:14:46 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 1999, 2005 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *	This product includes software developed by the NetBSD
21  *	Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 /*
40  * Copyright (c) 1985, 1993
41  *	The Regents of the University of California.  All rights reserved.
42  *
43  * Redistribution and use in source and binary forms, with or without
44  * modification, are permitted provided that the following conditions
45  * are met:
46  * 1. Redistributions of source code must retain the above copyright
47  *    notice, this list of conditions and the following disclaimer.
48  * 2. Redistributions in binary form must reproduce the above copyright
49  *    notice, this list of conditions and the following disclaimer in the
50  *    documentation and/or other materials provided with the distribution.
51  * 3. Neither the name of the University nor the names of its contributors
52  *    may be used to endorse or promote products derived from this software
53  *    without specific prior written permission.
54  *
55  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
56  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
57  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
58  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
59  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
60  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
61  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
62  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
63  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
64  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
65  * SUCH DAMAGE.
66  */
67 
68 #include <sys/cdefs.h>
69 #if defined(LIBC_SCCS) && !defined(lint)
70 #if 0
71 static char sccsid[] = "@(#)getusershell.c	8.1 (Berkeley) 6/4/93";
72 #else
73 __RCSID("$NetBSD: getusershell.c,v 1.26 2006/10/15 16:14:46 christos Exp $");
74 #endif
75 #endif /* LIBC_SCCS and not lint */
76 
77 #include "namespace.h"
78 #include "reentrant.h"
79 
80 #include <sys/param.h>
81 #include <sys/file.h>
82 
83 #include <assert.h>
84 #include <ctype.h>
85 #include <errno.h>
86 #include <nsswitch.h>
87 #include <paths.h>
88 #include <stdarg.h>
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <string.h>
92 #include <unistd.h>
93 
94 #ifdef HESIOD
95 #include <hesiod.h>
96 #endif
97 #ifdef YP
98 #include <rpc/rpc.h>
99 #include <rpcsvc/ypclnt.h>
100 #include <rpcsvc/yp_prot.h>
101 #endif
102 
103 #ifdef __weak_alias
104 __weak_alias(endusershell,_endusershell)
105 __weak_alias(getusershell,_getusershell)
106 __weak_alias(setusershell,_setusershell)
107 #endif
108 
109 /*
110  * Local shells should NOT be added here.
111  * They should be added in /etc/shells.
112  */
113 static const char *const okshells[] = { _PATH_BSHELL, _PATH_CSHELL, NULL };
114 
115 #ifdef _REENTRANT
116 static mutex_t __shellmutex = MUTEX_INITIALIZER;
117 #endif
118 
119 static char		  curshell[MAXPATHLEN + 2];
120 
121 static const char *const *curokshell = okshells;
122 static int		  shellsfound = 0;
123 
124 		/*
125 		 *	files methods
126 		 */
127 
128 	/* state shared between files methods */
129 struct files_state {
130 	FILE	*fp;
131 };
132 
133 static struct files_state _files_state;
134 
135 
136 static int
137 _files_start(struct files_state *state)
138 {
139 
140 	_DIAGASSERT(state != NULL);
141 
142 	if (state->fp == NULL) {
143 		state->fp = fopen(_PATH_SHELLS, "r");
144 		if (state->fp == NULL)
145 			return NS_UNAVAIL;
146 	} else {
147 		rewind(state->fp);
148 	}
149 	return NS_SUCCESS;
150 }
151 
152 static int
153 _files_end(struct files_state *state)
154 {
155 
156 	_DIAGASSERT(state != NULL);
157 
158 	if (state->fp) {
159 		(void) fclose(state->fp);
160 		state->fp = NULL;
161 	}
162 	return NS_SUCCESS;
163 }
164 
165 /*ARGSUSED*/
166 static int
167 _files_setusershell(void *nsrv, void *nscb, va_list ap)
168 {
169 
170 	return _files_start(&_files_state);
171 }
172 
173 /*ARGSUSED*/
174 static int
175 _files_endusershell(void *nsrv, void *nscb, va_list ap)
176 {
177 
178 	return _files_end(&_files_state);
179 }
180 
181 /*ARGSUSED*/
182 static int
183 _files_getusershell(void *nsrv, void *nscb, va_list ap)
184 {
185 	char	**retval = va_arg(ap, char **);
186 
187 	char	*sp, *cp;
188 	int	 rv;
189 
190 	_DIAGASSERT(retval != NULL);
191 
192 	*retval = NULL;
193 	if (_files_state.fp == NULL) {	/* only start if file not open yet */
194 		rv = _files_start(&_files_state);
195 		if (rv != NS_SUCCESS)
196 			return rv;
197 	}
198 
199 	while (fgets(curshell, sizeof(curshell) - 1, _files_state.fp) != NULL) {
200 		sp = cp = curshell;
201 		while (*cp != '#' && *cp != '/' && *cp != '\0')
202 			cp++;
203 		if (*cp == '#' || *cp == '\0')
204 			continue;
205 		sp = cp;
206 		while (!isspace((unsigned char) *cp) && *cp != '#'
207 		    && *cp != '\0')
208 			cp++;
209 		*cp++ = '\0';
210 		*retval = sp;
211 		return NS_SUCCESS;
212 	}
213 
214 	return NS_NOTFOUND;
215 }
216 
217 
218 #ifdef HESIOD
219 		/*
220 		 *	dns methods
221 		 */
222 
223 	/* state shared between dns methods */
224 struct dns_state {
225 	void	*context;		/* Hesiod context */
226 	int	 num;			/* shell index, -1 if no more */
227 };
228 
229 static struct dns_state		_dns_state;
230 
231 static int
232 _dns_start(struct dns_state *state)
233 {
234 
235 	_DIAGASSERT(state != NULL);
236 
237 	state->num = 0;
238 	if (state->context == NULL) {			/* setup Hesiod */
239 		if (hesiod_init(&state->context) == -1)
240 			return NS_UNAVAIL;
241 	}
242 
243 	return NS_SUCCESS;
244 }
245 
246 static int
247 _dns_end(struct dns_state *state)
248 {
249 
250 	_DIAGASSERT(state != NULL);
251 
252 	state->num = 0;
253 	if (state->context) {
254 		hesiod_end(state->context);
255 		state->context = NULL;
256 	}
257 	return NS_SUCCESS;
258 }
259 
260 /*ARGSUSED*/
261 static int
262 _dns_setusershell(void *nsrv, void *nscb, va_list ap)
263 {
264 
265 	return _dns_start(&_dns_state);
266 }
267 
268 /*ARGSUSED*/
269 static int
270 _dns_endusershell(void *nsrv, void *nscb, va_list ap)
271 {
272 
273 	return _dns_end(&_dns_state);
274 }
275 
276 /*ARGSUSED*/
277 static int
278 _dns_getusershell(void *nsrv, void *nscb, va_list ap)
279 {
280 	char	**retval = va_arg(ap, char **);
281 
282 	char	  shellname[] = "shells-NNNNNNNNNN";
283 	char	**hp, *ep;
284 	int	  rv;
285 
286 	_DIAGASSERT(retval != NULL);
287 
288 	*retval = NULL;
289 
290 	if (_dns_state.num == -1)			/* exhausted search */
291 		return NS_NOTFOUND;
292 
293 	if (_dns_state.context == NULL) {
294 			/* only start if Hesiod not setup */
295 		rv = _dns_start(&_dns_state);
296 		if (rv != NS_SUCCESS)
297 			return rv;
298 	}
299 
300 	hp = NULL;
301 	rv = NS_NOTFOUND;
302 
303 							/* find shells-NNN */
304 	snprintf(shellname, sizeof(shellname), "shells-%d", _dns_state.num);
305 	_dns_state.num++;
306 
307 	hp = hesiod_resolve(_dns_state.context, shellname, "shells");
308 	if (hp == NULL) {
309 		if (errno == ENOENT)
310 			rv = NS_NOTFOUND;
311 		else
312 			rv = NS_UNAVAIL;
313 	} else {
314 		if ((ep = strchr(hp[0], '\n')) != NULL)
315 			*ep = '\0';			/* clear trailing \n */
316 						/* only use first result */
317 		strlcpy(curshell, hp[0], sizeof(curshell));
318 		*retval = curshell;
319 		rv = NS_SUCCESS;
320 	}
321 
322 	if (hp)
323 		hesiod_free_list(_dns_state.context, hp);
324 	if (rv != NS_SUCCESS)
325 		_dns_state.num = -1;		/* any failure halts search */
326 	return rv;
327 }
328 
329 #endif /* HESIOD */
330 
331 
332 #ifdef YP
333 		/*
334 		 *	nis methods
335 		 */
336 	/* state shared between nis methods */
337 struct nis_state {
338 	char		*domain;	/* NIS domain */
339 	int		 done;		/* non-zero if search exhausted */
340 	char		*current;	/* current first/next match */
341 	int		 currentlen;	/* length of _nis_current */
342 };
343 
344 static struct nis_state		_nis_state;
345 
346 static int
347 _nis_start(struct nis_state *state)
348 {
349 
350 	_DIAGASSERT(state != NULL);
351 
352 	state->done = 0;
353 	if (state->current) {
354 		free(state->current);
355 		state->current = NULL;
356 	}
357 	if (state->domain == NULL) {			/* setup NIS */
358 		switch (yp_get_default_domain(&state->domain)) {
359 		case 0:
360 			break;
361 		case YPERR_RESRC:
362 			return NS_TRYAGAIN;
363 		default:
364 			return NS_UNAVAIL;
365 		}
366 	}
367 	return NS_SUCCESS;
368 }
369 
370 static int
371 _nis_end(struct nis_state *state)
372 {
373 
374 	_DIAGASSERT(state != NULL);
375 
376 	if (state->domain)
377 		state->domain = NULL;
378 	state->done = 0;
379 	if (state->current)
380 		free(state->current);
381 	state->current = NULL;
382 	return NS_SUCCESS;
383 }
384 
385 /*ARGSUSED*/
386 static int
387 _nis_setusershell(void *nsrv, void *nscb, va_list ap)
388 {
389 
390 	return _nis_start(&_nis_state);
391 }
392 
393 /*ARGSUSED*/
394 static int
395 _nis_endusershell(void *nsrv, void *nscb, va_list ap)
396 {
397 
398 	return _nis_end(&_nis_state);
399 }
400 
401 /*ARGSUSED*/
402 static int
403 _nis_getusershell(void *nsrv, void *nscb, va_list ap)
404 {
405 	char	**retval = va_arg(ap, char **);
406 
407 	char	*key, *data;
408 	int	keylen, datalen, rv, nisr;
409 
410 	_DIAGASSERT(retval != NULL);
411 
412 	*retval = NULL;
413 
414 	if (_nis_state.done)				/* exhausted search */
415 		return NS_NOTFOUND;
416 	if (_nis_state.domain == NULL) {
417 					/* only start if NIS not setup */
418 		rv = _nis_start(&_nis_state);
419 		if (rv != NS_SUCCESS)
420 			return rv;
421 	}
422 
423 	key = NULL;
424 	data = NULL;
425 	rv = NS_NOTFOUND;
426 
427 	if (_nis_state.current) {			/* already searching */
428 		nisr = yp_next(_nis_state.domain, "shells",
429 		    _nis_state.current, _nis_state.currentlen,
430 		    &key, &keylen, &data, &datalen);
431 		free(_nis_state.current);
432 		_nis_state.current = NULL;
433 		switch (nisr) {
434 		case 0:
435 			_nis_state.current = key;
436 			_nis_state.currentlen = keylen;
437 			key = NULL;
438 			break;
439 		case YPERR_NOMORE:
440 			rv = NS_NOTFOUND;
441 			goto nisent_out;
442 		default:
443 			rv = NS_UNAVAIL;
444 			goto nisent_out;
445 		}
446 	} else {					/* new search */
447 		if (yp_first(_nis_state.domain, "shells",
448 		    &_nis_state.current, &_nis_state.currentlen,
449 		    &data, &datalen)) {
450 			rv = NS_UNAVAIL;
451 			goto nisent_out;
452 		}
453 	}
454 
455 	data[datalen] = '\0';				/* clear trailing \n */
456 	strlcpy(curshell, data, sizeof(curshell));
457 	*retval = curshell;
458 	rv = NS_SUCCESS;
459 
460  nisent_out:
461 	if (key)
462 		free(key);
463 	if (data)
464 		free(data);
465 	if (rv != NS_SUCCESS)			/* any failure halts search */
466 		_nis_state.done = 1;
467 	return rv;
468 }
469 
470 #endif /* YP */
471 
472 
473 		/*
474 		 *	public functions
475 		 */
476 
477 void
478 endusershell(void)
479 {
480 	static const ns_dtab dtab[] = {
481 		NS_FILES_CB(_files_endusershell, NULL)
482 		NS_DNS_CB(_dns_endusershell, NULL)
483 		NS_NIS_CB(_nis_endusershell, NULL)
484 		NS_NULL_CB
485 	};
486 
487 	mutex_lock(&__shellmutex);
488 
489 	curokshell = okshells;		/* reset okshells fallback state */
490 	shellsfound = 0;
491 
492 					/* force all endusershell() methods */
493 	(void) nsdispatch(NULL, dtab, NSDB_SHELLS, "endusershell",
494 	    __nsdefaultfiles_forceall);
495 	mutex_unlock(&__shellmutex);
496 }
497 
498 __aconst char *
499 getusershell(void)
500 {
501 	int		 rv;
502 	__aconst char	*retval;
503 
504 	static const ns_dtab dtab[] = {
505 		NS_FILES_CB(_files_getusershell, NULL)
506 		NS_DNS_CB(_dns_getusershell, NULL)
507 		NS_NIS_CB(_nis_getusershell, NULL)
508 		NS_NULL_CB
509 	};
510 
511 	mutex_lock(&__shellmutex);
512 
513 	retval = NULL;
514 	do {
515 		rv = nsdispatch(NULL, dtab, NSDB_SHELLS, "getusershell",
516 		    __nsdefaultsrc, &retval);
517 				/* loop until failure or non-blank result */
518 	} while (rv == NS_SUCCESS && retval[0] == '\0');
519 
520 	if (rv == NS_SUCCESS) {
521 		shellsfound++;
522 	} else if (shellsfound == 0) {	/* no shells; fall back to okshells */
523 		if (curokshell != NULL) {
524 			retval = __UNCONST(*curokshell);
525 			curokshell++;
526 			rv = NS_SUCCESS;
527 		}
528 	}
529 
530 	mutex_unlock(&__shellmutex);
531 	return (rv == NS_SUCCESS) ? retval : NULL;
532 }
533 
534 void
535 setusershell(void)
536 {
537 	static const ns_dtab dtab[] = {
538 		NS_FILES_CB(_files_setusershell, NULL)
539 		NS_DNS_CB(_dns_setusershell, NULL)
540 		NS_NIS_CB(_nis_setusershell, NULL)
541 		NS_NULL_CB
542 	};
543 
544 	mutex_lock(&__shellmutex);
545 
546 	curokshell = okshells;		/* reset okshells fallback state */
547 	shellsfound = 0;
548 
549 					/* force all setusershell() methods */
550 	(void) nsdispatch(NULL, dtab, NSDB_SHELLS, "setusershell",
551 	    __nsdefaultfiles_forceall);
552 	mutex_unlock(&__shellmutex);
553 }
554