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