1/* $NetBSD: getusershell.c,v 1.27 2008/04/28 20:22:59 martin 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.27 2008/04/28 20:22:59 martin 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, sizeof(curshell) - 1, _files_state.fp) != NULL) { 193 sp = cp = curshell; 194 while (*cp != '#' && *cp != '/' && *cp != '\0') 195 cp++; 196 if (*cp == '#' || *cp == '\0') 197 continue; 198 sp = cp; 199 while (!isspace((unsigned char) *cp) && *cp != '#' 200 && *cp != '\0') 201 cp++; 202 *cp++ = '\0'; 203 *retval = sp; 204 return NS_SUCCESS; 205 } 206 207 return NS_NOTFOUND; 208} 209 210 211#ifdef HESIOD 212 /* 213 * dns methods 214 */ 215 216 /* state shared between dns methods */ 217struct dns_state { 218 void *context; /* Hesiod context */ 219 int num; /* shell index, -1 if no more */ 220}; 221 222static struct dns_state _dns_state; 223 224static int 225_dns_start(struct dns_state *state) 226{ 227 228 _DIAGASSERT(state != NULL); 229 230 state->num = 0; 231 if (state->context == NULL) { /* setup Hesiod */ 232 if (hesiod_init(&state->context) == -1) 233 return NS_UNAVAIL; 234 } 235 236 return NS_SUCCESS; 237} 238 239static int 240_dns_end(struct dns_state *state) 241{ 242 243 _DIAGASSERT(state != NULL); 244 245 state->num = 0; 246 if (state->context) { 247 hesiod_end(state->context); 248 state->context = NULL; 249 } 250 return NS_SUCCESS; 251} 252 253/*ARGSUSED*/ 254static int 255_dns_setusershell(void *nsrv, void *nscb, va_list ap) 256{ 257 258 return _dns_start(&_dns_state); 259} 260 261/*ARGSUSED*/ 262static int 263_dns_endusershell(void *nsrv, void *nscb, va_list ap) 264{ 265 266 return _dns_end(&_dns_state); 267} 268 269/*ARGSUSED*/ 270static int 271_dns_getusershell(void *nsrv, void *nscb, va_list ap) 272{ 273 char **retval = va_arg(ap, char **); 274 275 char shellname[] = "shells-NNNNNNNNNN"; 276 char **hp, *ep; 277 int rv; 278 279 _DIAGASSERT(retval != NULL); 280 281 *retval = NULL; 282 283 if (_dns_state.num == -1) /* exhausted search */ 284 return NS_NOTFOUND; 285 286 if (_dns_state.context == NULL) { 287 /* only start if Hesiod not setup */ 288 rv = _dns_start(&_dns_state); 289 if (rv != NS_SUCCESS) 290 return rv; 291 } 292 293 hp = NULL; 294 rv = NS_NOTFOUND; 295 296 /* find shells-NNN */ 297 snprintf(shellname, sizeof(shellname), "shells-%d", _dns_state.num); 298 _dns_state.num++; 299 300 hp = hesiod_resolve(_dns_state.context, shellname, "shells"); 301 if (hp == NULL) { 302 if (errno == ENOENT) 303 rv = NS_NOTFOUND; 304 else 305 rv = NS_UNAVAIL; 306 } else { 307 if ((ep = strchr(hp[0], '\n')) != NULL) 308 *ep = '\0'; /* clear trailing \n */ 309 /* only use first result */ 310 strlcpy(curshell, hp[0], sizeof(curshell)); 311 *retval = curshell; 312 rv = NS_SUCCESS; 313 } 314 315 if (hp) 316 hesiod_free_list(_dns_state.context, hp); 317 if (rv != NS_SUCCESS) 318 _dns_state.num = -1; /* any failure halts search */ 319 return rv; 320} 321 322#endif /* HESIOD */ 323 324 325#ifdef YP 326 /* 327 * nis methods 328 */ 329 /* state shared between nis methods */ 330struct nis_state { 331 char *domain; /* NIS domain */ 332 int done; /* non-zero if search exhausted */ 333 char *current; /* current first/next match */ 334 int currentlen; /* length of _nis_current */ 335}; 336 337static struct nis_state _nis_state; 338 339static int 340_nis_start(struct nis_state *state) 341{ 342 343 _DIAGASSERT(state != NULL); 344 345 state->done = 0; 346 if (state->current) { 347 free(state->current); 348 state->current = NULL; 349 } 350 if (state->domain == NULL) { /* setup NIS */ 351 switch (yp_get_default_domain(&state->domain)) { 352 case 0: 353 break; 354 case YPERR_RESRC: 355 return NS_TRYAGAIN; 356 default: 357 return NS_UNAVAIL; 358 } 359 } 360 return NS_SUCCESS; 361} 362 363static int 364_nis_end(struct nis_state *state) 365{ 366 367 _DIAGASSERT(state != NULL); 368 369 if (state->domain) 370 state->domain = NULL; 371 state->done = 0; 372 if (state->current) 373 free(state->current); 374 state->current = NULL; 375 return NS_SUCCESS; 376} 377 378/*ARGSUSED*/ 379static int 380_nis_setusershell(void *nsrv, void *nscb, va_list ap) 381{ 382 383 return _nis_start(&_nis_state); 384} 385 386/*ARGSUSED*/ 387static int 388_nis_endusershell(void *nsrv, void *nscb, va_list ap) 389{ 390 391 return _nis_end(&_nis_state); 392} 393 394/*ARGSUSED*/ 395static int 396_nis_getusershell(void *nsrv, void *nscb, va_list ap) 397{ 398 char **retval = va_arg(ap, char **); 399 400 char *key, *data; 401 int keylen, datalen, rv, nisr; 402 403 _DIAGASSERT(retval != NULL); 404 405 *retval = NULL; 406 407 if (_nis_state.done) /* exhausted search */ 408 return NS_NOTFOUND; 409 if (_nis_state.domain == NULL) { 410 /* only start if NIS not setup */ 411 rv = _nis_start(&_nis_state); 412 if (rv != NS_SUCCESS) 413 return rv; 414 } 415 416 key = NULL; 417 data = NULL; 418 rv = NS_NOTFOUND; 419 420 if (_nis_state.current) { /* already searching */ 421 nisr = yp_next(_nis_state.domain, "shells", 422 _nis_state.current, _nis_state.currentlen, 423 &key, &keylen, &data, &datalen); 424 free(_nis_state.current); 425 _nis_state.current = NULL; 426 switch (nisr) { 427 case 0: 428 _nis_state.current = key; 429 _nis_state.currentlen = keylen; 430 key = NULL; 431 break; 432 case YPERR_NOMORE: 433 rv = NS_NOTFOUND; 434 goto nisent_out; 435 default: 436 rv = NS_UNAVAIL; 437 goto nisent_out; 438 } 439 } else { /* new search */ 440 if (yp_first(_nis_state.domain, "shells", 441 &_nis_state.current, &_nis_state.currentlen, 442 &data, &datalen)) { 443 rv = NS_UNAVAIL; 444 goto nisent_out; 445 } 446 } 447 448 data[datalen] = '\0'; /* clear trailing \n */ 449 strlcpy(curshell, data, sizeof(curshell)); 450 *retval = curshell; 451 rv = NS_SUCCESS; 452 453 nisent_out: 454 if (key) 455 free(key); 456 if (data) 457 free(data); 458 if (rv != NS_SUCCESS) /* any failure halts search */ 459 _nis_state.done = 1; 460 return rv; 461} 462 463#endif /* YP */ 464 465 466 /* 467 * public functions 468 */ 469 470void 471endusershell(void) 472{ 473 static const ns_dtab dtab[] = { 474 NS_FILES_CB(_files_endusershell, NULL) 475 NS_DNS_CB(_dns_endusershell, NULL) 476 NS_NIS_CB(_nis_endusershell, NULL) 477 NS_NULL_CB 478 }; 479 480 mutex_lock(&__shellmutex); 481 482 curokshell = okshells; /* reset okshells fallback state */ 483 shellsfound = 0; 484 485 /* force all endusershell() methods */ 486 (void) nsdispatch(NULL, dtab, NSDB_SHELLS, "endusershell", 487 __nsdefaultfiles_forceall); 488 mutex_unlock(&__shellmutex); 489} 490 491__aconst char * 492getusershell(void) 493{ 494 int rv; 495 __aconst char *retval; 496 497 static const ns_dtab dtab[] = { 498 NS_FILES_CB(_files_getusershell, NULL) 499 NS_DNS_CB(_dns_getusershell, NULL) 500 NS_NIS_CB(_nis_getusershell, NULL) 501 NS_NULL_CB 502 }; 503 504 mutex_lock(&__shellmutex); 505 506 retval = NULL; 507 do { 508 rv = nsdispatch(NULL, dtab, NSDB_SHELLS, "getusershell", 509 __nsdefaultsrc, &retval); 510 /* loop until failure or non-blank result */ 511 } while (rv == NS_SUCCESS && retval[0] == '\0'); 512 513 if (rv == NS_SUCCESS) { 514 shellsfound++; 515 } else if (shellsfound == 0) { /* no shells; fall back to okshells */ 516 if (curokshell != NULL) { 517 retval = __UNCONST(*curokshell); 518 curokshell++; 519 rv = NS_SUCCESS; 520 } 521 } 522 523 mutex_unlock(&__shellmutex); 524 return (rv == NS_SUCCESS) ? retval : NULL; 525} 526 527void 528setusershell(void) 529{ 530 static const ns_dtab dtab[] = { 531 NS_FILES_CB(_files_setusershell, NULL) 532 NS_DNS_CB(_dns_setusershell, NULL) 533 NS_NIS_CB(_nis_setusershell, NULL) 534 NS_NULL_CB 535 }; 536 537 mutex_lock(&__shellmutex); 538 539 curokshell = okshells; /* reset okshells fallback state */ 540 shellsfound = 0; 541 542 /* force all setusershell() methods */ 543 (void) nsdispatch(NULL, dtab, NSDB_SHELLS, "setusershell", 544 __nsdefaultfiles_forceall); 545 mutex_unlock(&__shellmutex); 546} 547