getusershell.c revision 1.25 1 /* $NetBSD: getusershell.c,v 1.25 2005/11/29 03:11:59 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.25 2005/11/29 03:11:59 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 { 0 }
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 { 0 }
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 { 0 }
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