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