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