xref: /freebsd/usr.bin/su/su.c (revision bb15ca603fa442c72dde3f3cb8b46db6970e3950)
1 /*
2  * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
3  * All rights reserved.
4  *
5  * Portions of this software were developed for the FreeBSD Project by
6  * ThinkSec AS and NAI Labs, the Security Research Division of Network
7  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
8  * ("CBOSS"), as part of the DARPA CHATS research program.
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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 /*-
32  * Copyright (c) 1988, 1993, 1994
33  *	The Regents of the University of California.  All rights reserved.
34  *
35  * Redistribution and use in source and binary forms, with or without
36  * modification, are permitted provided that the following conditions
37  * are met:
38  * 1. Redistributions of source code must retain the above copyright
39  *    notice, this list of conditions and the following disclaimer.
40  * 2. Redistributions in binary form must reproduce the above copyright
41  *    notice, this list of conditions and the following disclaimer in the
42  *    documentation and/or other materials provided with the distribution.
43  * 4. Neither the name of the University nor the names of its contributors
44  *    may be used to endorse or promote products derived from this software
45  *    without specific prior written permission.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57  * SUCH DAMAGE.
58  */
59 
60 #ifndef lint
61 static const char copyright[] =
62 "@(#) Copyright (c) 1988, 1993, 1994\n\
63 	The Regents of the University of California.  All rights reserved.\n";
64 #endif /* not lint */
65 
66 #if 0
67 #ifndef lint
68 static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
69 #endif /* not lint */
70 #endif
71 
72 #include <sys/cdefs.h>
73 __FBSDID("$FreeBSD$");
74 
75 #include <sys/param.h>
76 #include <sys/time.h>
77 #include <sys/resource.h>
78 #include <sys/wait.h>
79 
80 #ifdef USE_BSM_AUDIT
81 #include <bsm/libbsm.h>
82 #include <bsm/audit_uevents.h>
83 #endif
84 
85 #include <err.h>
86 #include <errno.h>
87 #include <grp.h>
88 #include <login_cap.h>
89 #include <paths.h>
90 #include <pwd.h>
91 #include <signal.h>
92 #include <stdio.h>
93 #include <stdlib.h>
94 #include <string.h>
95 #include <syslog.h>
96 #include <unistd.h>
97 #include <stdarg.h>
98 
99 #include <security/pam_appl.h>
100 #include <security/openpam.h>
101 
102 #define PAM_END() do {							\
103 	int local_ret;							\
104 	if (pamh != NULL) {						\
105 		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);		\
106 		if (local_ret != PAM_SUCCESS)				\
107 			syslog(LOG_ERR, "pam_setcred: %s",		\
108 				pam_strerror(pamh, local_ret));		\
109 		if (asthem) {						\
110 			local_ret = pam_close_session(pamh, 0);		\
111 			if (local_ret != PAM_SUCCESS)			\
112 				syslog(LOG_ERR, "pam_close_session: %s",\
113 					pam_strerror(pamh, local_ret));	\
114 		}							\
115 		local_ret = pam_end(pamh, local_ret);			\
116 		if (local_ret != PAM_SUCCESS)				\
117 			syslog(LOG_ERR, "pam_end: %s",			\
118 				pam_strerror(pamh, local_ret));		\
119 	}								\
120 } while (0)
121 
122 
123 #define PAM_SET_ITEM(what, item) do {					\
124 	int local_ret;							\
125 	local_ret = pam_set_item(pamh, what, item);			\
126 	if (local_ret != PAM_SUCCESS) {					\
127 		syslog(LOG_ERR, "pam_set_item(" #what "): %s",		\
128 			pam_strerror(pamh, local_ret));			\
129 		errx(1, "pam_set_item(" #what "): %s",			\
130 			pam_strerror(pamh, local_ret));			\
131 		/* NOTREACHED */					\
132 	}								\
133 } while (0)
134 
135 enum tristate { UNSET, YES, NO };
136 
137 static pam_handle_t *pamh = NULL;
138 static char	**environ_pam;
139 
140 static char	*ontty(void);
141 static int	chshell(const char *);
142 static void	usage(void) __dead2;
143 static void	export_pam_environment(void);
144 static int	ok_to_export(const char *);
145 
146 extern char	**environ;
147 
148 int
149 main(int argc, char *argv[])
150 {
151 	static char	*cleanenv;
152 	struct passwd	*pwd = NULL;
153 	struct pam_conv	conv = { openpam_ttyconv, NULL };
154 	enum tristate	iscsh;
155 	login_cap_t	*lc;
156 	union {
157 		const char	**a;
158 		char		* const *b;
159 	}		np;
160 	uid_t		ruid;
161 	pid_t		child_pid, child_pgrp, pid;
162 	int		asme, ch, asthem, fastlogin, prio, i, retcode,
163 			statusp, setmaclabel;
164 	u_int		setwhat;
165 	char		*username, *class, shellbuf[MAXPATHLEN];
166 	const char	*p, *user, *shell, *mytty, **nargv;
167 	const void	*v;
168 	struct sigaction sa, sa_int, sa_quit, sa_pipe;
169 	int temp, fds[2];
170 #ifdef USE_BSM_AUDIT
171 	const char	*aerr;
172 	au_id_t		 auid;
173 #endif
174 
175 	shell = class = cleanenv = NULL;
176 	asme = asthem = fastlogin = statusp = 0;
177 	user = "root";
178 	iscsh = UNSET;
179 	setmaclabel = 0;
180 
181 	while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
182 		switch ((char)ch) {
183 		case 'f':
184 			fastlogin = 1;
185 			break;
186 		case '-':
187 		case 'l':
188 			asme = 0;
189 			asthem = 1;
190 			break;
191 		case 'm':
192 			asme = 1;
193 			asthem = 0;
194 			break;
195 		case 's':
196 			setmaclabel = 1;
197 			break;
198 		case 'c':
199 			class = optarg;
200 			break;
201 		case '?':
202 		default:
203 			usage();
204 		/* NOTREACHED */
205 		}
206 
207 	if (optind < argc)
208 		user = argv[optind++];
209 
210 	if (user == NULL)
211 		usage();
212 	/* NOTREACHED */
213 
214 	/*
215 	 * Try to provide more helpful debugging output if su(1) is running
216 	 * non-setuid, or was run from a file system not mounted setuid.
217 	 */
218 	if (geteuid() != 0)
219 		errx(1, "not running setuid");
220 
221 #ifdef USE_BSM_AUDIT
222 	if (getauid(&auid) < 0 && errno != ENOSYS) {
223 		syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
224 		errx(1, "Permission denied");
225 	}
226 #endif
227 	if (strlen(user) > MAXLOGNAME - 1) {
228 #ifdef USE_BSM_AUDIT
229 		if (audit_submit(AUE_su, auid,
230 		    EPERM, 1, "username too long: '%s'", user))
231 			errx(1, "Permission denied");
232 #endif
233 		errx(1, "username too long");
234 	}
235 
236 	nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
237 	if (nargv == NULL)
238 		errx(1, "malloc failure");
239 
240 	nargv[argc + 3] = NULL;
241 	for (i = argc; i >= optind; i--)
242 		nargv[i + 3] = argv[i];
243 	np.a = &nargv[i + 3];
244 
245 	argv += optind;
246 
247 	errno = 0;
248 	prio = getpriority(PRIO_PROCESS, 0);
249 	if (errno)
250 		prio = 0;
251 
252 	setpriority(PRIO_PROCESS, 0, -2);
253 	openlog("su", LOG_CONS, LOG_AUTH);
254 
255 	/* get current login name, real uid and shell */
256 	ruid = getuid();
257 	username = getlogin();
258 	if (username != NULL)
259 		pwd = getpwnam(username);
260 	if (pwd == NULL || pwd->pw_uid != ruid)
261 		pwd = getpwuid(ruid);
262 	if (pwd == NULL) {
263 #ifdef USE_BSM_AUDIT
264 		if (audit_submit(AUE_su, auid, EPERM, 1,
265 		    "unable to determine invoking subject: '%s'", username))
266 			errx(1, "Permission denied");
267 #endif
268 		errx(1, "who are you?");
269 	}
270 
271 	username = strdup(pwd->pw_name);
272 	if (username == NULL)
273 		err(1, "strdup failure");
274 
275 	if (asme) {
276 		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
277 			/* must copy - pwd memory is recycled */
278 			shell = strncpy(shellbuf, pwd->pw_shell,
279 			    sizeof(shellbuf));
280 			shellbuf[sizeof(shellbuf) - 1] = '\0';
281 		}
282 		else {
283 			shell = _PATH_BSHELL;
284 			iscsh = NO;
285 		}
286 	}
287 
288 	/* Do the whole PAM startup thing */
289 	retcode = pam_start("su", user, &conv, &pamh);
290 	if (retcode != PAM_SUCCESS) {
291 		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
292 		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
293 	}
294 
295 	PAM_SET_ITEM(PAM_RUSER, username);
296 
297 	mytty = ttyname(STDERR_FILENO);
298 	if (!mytty)
299 		mytty = "tty";
300 	PAM_SET_ITEM(PAM_TTY, mytty);
301 
302 	retcode = pam_authenticate(pamh, 0);
303 	if (retcode != PAM_SUCCESS) {
304 #ifdef USE_BSM_AUDIT
305 		if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
306 		    username, user, mytty))
307 			errx(1, "Permission denied");
308 #endif
309 		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
310 		    username, user, mytty);
311 		errx(1, "Sorry");
312 	}
313 #ifdef USE_BSM_AUDIT
314 	if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
315 		errx(1, "Permission denied");
316 #endif
317 	retcode = pam_get_item(pamh, PAM_USER, &v);
318 	if (retcode == PAM_SUCCESS)
319 		user = v;
320 	else
321 		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
322 		    pam_strerror(pamh, retcode));
323 	pwd = getpwnam(user);
324 	if (pwd == NULL) {
325 #ifdef USE_BSM_AUDIT
326 		if (audit_submit(AUE_su, auid, EPERM, 1,
327 		    "unknown subject: %s", user))
328 			errx(1, "Permission denied");
329 #endif
330 		errx(1, "unknown login: %s", user);
331 	}
332 
333 	retcode = pam_acct_mgmt(pamh, 0);
334 	if (retcode == PAM_NEW_AUTHTOK_REQD) {
335 		retcode = pam_chauthtok(pamh,
336 			PAM_CHANGE_EXPIRED_AUTHTOK);
337 		if (retcode != PAM_SUCCESS) {
338 #ifdef USE_BSM_AUDIT
339 			aerr = pam_strerror(pamh, retcode);
340 			if (aerr == NULL)
341 				aerr = "Unknown PAM error";
342 			if (audit_submit(AUE_su, auid, EPERM, 1,
343 			    "pam_chauthtok: %s", aerr))
344 				errx(1, "Permission denied");
345 #endif
346 			syslog(LOG_ERR, "pam_chauthtok: %s",
347 			    pam_strerror(pamh, retcode));
348 			errx(1, "Sorry");
349 		}
350 	}
351 	if (retcode != PAM_SUCCESS) {
352 #ifdef USE_BSM_AUDIT
353 		if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
354 		    pam_strerror(pamh, retcode)))
355 			errx(1, "Permission denied");
356 #endif
357 		syslog(LOG_ERR, "pam_acct_mgmt: %s",
358 			pam_strerror(pamh, retcode));
359 		errx(1, "Sorry");
360 	}
361 
362 	/* get target login information */
363 	if (class == NULL)
364 		lc = login_getpwclass(pwd);
365 	else {
366 		if (ruid != 0) {
367 #ifdef USE_BSM_AUDIT
368 			if (audit_submit(AUE_su, auid, EPERM, 1,
369 			    "only root may use -c"))
370 				errx(1, "Permission denied");
371 #endif
372 			errx(1, "only root may use -c");
373 		}
374 		lc = login_getclass(class);
375 		if (lc == NULL)
376 			errx(1, "unknown class: %s", class);
377 	}
378 
379 	/* if asme and non-standard target shell, must be root */
380 	if (asme) {
381 		if (ruid != 0 && !chshell(pwd->pw_shell))
382 			errx(1, "permission denied (shell)");
383 	}
384 	else if (pwd->pw_shell && *pwd->pw_shell) {
385 		shell = pwd->pw_shell;
386 		iscsh = UNSET;
387 	}
388 	else {
389 		shell = _PATH_BSHELL;
390 		iscsh = NO;
391 	}
392 
393 	/* if we're forking a csh, we want to slightly muck the args */
394 	if (iscsh == UNSET) {
395 		p = strrchr(shell, '/');
396 		if (p)
397 			++p;
398 		else
399 			p = shell;
400 		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
401 	}
402 	setpriority(PRIO_PROCESS, 0, prio);
403 
404 	/*
405 	 * PAM modules might add supplementary groups in pam_setcred(), so
406 	 * initialize them first.
407 	 */
408 	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
409 		err(1, "setusercontext");
410 
411 	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
412 	if (retcode != PAM_SUCCESS) {
413 		syslog(LOG_ERR, "pam_setcred: %s",
414 		    pam_strerror(pamh, retcode));
415 		errx(1, "failed to establish credentials.");
416 	}
417 	if (asthem) {
418 		retcode = pam_open_session(pamh, 0);
419 		if (retcode != PAM_SUCCESS) {
420 			syslog(LOG_ERR, "pam_open_session: %s",
421 			    pam_strerror(pamh, retcode));
422 			errx(1, "failed to open session.");
423 		}
424 	}
425 
426 	/*
427 	 * We must fork() before setuid() because we need to call
428 	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
429 	 */
430 	sa.sa_flags = SA_RESTART;
431 	sa.sa_handler = SIG_IGN;
432 	sigemptyset(&sa.sa_mask);
433 	sigaction(SIGINT, &sa, &sa_int);
434 	sigaction(SIGQUIT, &sa, &sa_quit);
435 	sigaction(SIGPIPE, &sa, &sa_pipe);
436 	sa.sa_handler = SIG_DFL;
437 	sigaction(SIGTSTP, &sa, NULL);
438 	statusp = 1;
439 	if (pipe(fds) == -1) {
440 		PAM_END();
441 		err(1, "pipe");
442 	}
443 	child_pid = fork();
444 	switch (child_pid) {
445 	default:
446 		sa.sa_handler = SIG_IGN;
447 		sigaction(SIGTTOU, &sa, NULL);
448 		close(fds[0]);
449 		setpgid(child_pid, child_pid);
450 		if (tcgetpgrp(STDERR_FILENO) == getpgrp())
451 			tcsetpgrp(STDERR_FILENO, child_pid);
452 		close(fds[1]);
453 		sigaction(SIGPIPE, &sa_pipe, NULL);
454 		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
455 			if (WIFSTOPPED(statusp)) {
456 				child_pgrp = getpgid(child_pid);
457 				if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
458 					tcsetpgrp(STDERR_FILENO, getpgrp());
459 				kill(getpid(), SIGSTOP);
460 				if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
461 					child_pgrp = getpgid(child_pid);
462 					tcsetpgrp(STDERR_FILENO, child_pgrp);
463 				}
464 				kill(child_pid, SIGCONT);
465 				statusp = 1;
466 				continue;
467 			}
468 			break;
469 		}
470 		tcsetpgrp(STDERR_FILENO, getpgrp());
471 		if (pid == -1)
472 			err(1, "waitpid");
473 		PAM_END();
474 		exit(WEXITSTATUS(statusp));
475 	case -1:
476 		PAM_END();
477 		err(1, "fork");
478 	case 0:
479 		close(fds[1]);
480 		read(fds[0], &temp, 1);
481 		close(fds[0]);
482 		sigaction(SIGPIPE, &sa_pipe, NULL);
483 		sigaction(SIGINT, &sa_int, NULL);
484 		sigaction(SIGQUIT, &sa_quit, NULL);
485 
486 		/*
487 		 * Set all user context except for: Environmental variables
488 		 * Umask Login records (wtmp, etc) Path
489 		 */
490 		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
491 			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
492 			   LOGIN_SETMAC);
493 		/*
494 		 * If -s is present, also set the MAC label.
495 		 */
496 		if (setmaclabel)
497 			setwhat |= LOGIN_SETMAC;
498 		/*
499 		 * Don't touch resource/priority settings if -m has been used
500 		 * or -l and -c hasn't, and we're not su'ing to root.
501 		 */
502 		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
503 			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
504 		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
505 			err(1, "setusercontext");
506 
507 		if (!asme) {
508 			if (asthem) {
509 				p = getenv("TERM");
510 				environ = &cleanenv;
511 			}
512 
513 			if (asthem || pwd->pw_uid)
514 				setenv("USER", pwd->pw_name, 1);
515 			setenv("HOME", pwd->pw_dir, 1);
516 			setenv("SHELL", shell, 1);
517 
518 			if (asthem) {
519 				/*
520 				 * Add any environmental variables that the
521 				 * PAM modules may have set.
522 				 */
523 				environ_pam = pam_getenvlist(pamh);
524 				if (environ_pam)
525 					export_pam_environment();
526 
527 				/* set the su'd user's environment & umask */
528 				setusercontext(lc, pwd, pwd->pw_uid,
529 					LOGIN_SETPATH | LOGIN_SETUMASK |
530 					LOGIN_SETENV);
531 				if (p)
532 					setenv("TERM", p, 1);
533 
534 				p = pam_getenv(pamh, "HOME");
535 				if (chdir(p ? p : pwd->pw_dir) < 0)
536 					errx(1, "no directory");
537 			}
538 		}
539 		login_close(lc);
540 
541 		if (iscsh == YES) {
542 			if (fastlogin)
543 				*np.a-- = "-f";
544 			if (asme)
545 				*np.a-- = "-m";
546 		}
547 		/* csh strips the first character... */
548 		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
549 
550 		if (ruid != 0)
551 			syslog(LOG_NOTICE, "%s to %s%s", username, user,
552 			    ontty());
553 
554 		execv(shell, np.b);
555 		err(1, "%s", shell);
556 	}
557 }
558 
559 static void
560 export_pam_environment(void)
561 {
562 	char	**pp;
563 	char	*p;
564 
565 	for (pp = environ_pam; *pp != NULL; pp++) {
566 		if (ok_to_export(*pp)) {
567 			p = strchr(*pp, '=');
568 			*p = '\0';
569 			setenv(*pp, p + 1, 1);
570 		}
571 		free(*pp);
572 	}
573 }
574 
575 /*
576  * Sanity checks on PAM environmental variables:
577  * - Make sure there is an '=' in the string.
578  * - Make sure the string doesn't run on too long.
579  * - Do not export certain variables.  This list was taken from the
580  *   Solaris pam_putenv(3) man page.
581  * Note that if the user is chrooted, PAM may have a better idea than we
582  * do of where her home directory is.
583  */
584 static int
585 ok_to_export(const char *s)
586 {
587 	static const char *noexport[] = {
588 		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
589 		"IFS", "PATH", NULL
590 	};
591 	const char **pp;
592 	size_t n;
593 
594 	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
595 		return 0;
596 	if (strncmp(s, "LD_", 3) == 0)
597 		return 0;
598 	for (pp = noexport; *pp != NULL; pp++) {
599 		n = strlen(*pp);
600 		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
601 			return 0;
602 	}
603 	return 1;
604 }
605 
606 static void
607 usage(void)
608 {
609 
610 	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
611 	exit(1);
612 	/* NOTREACHED */
613 }
614 
615 static int
616 chshell(const char *sh)
617 {
618 	int r;
619 	char *cp;
620 
621 	r = 0;
622 	setusershell();
623 	while ((cp = getusershell()) != NULL && !r)
624 	    r = (strcmp(cp, sh) == 0);
625 	endusershell();
626 	return r;
627 }
628 
629 static char *
630 ontty(void)
631 {
632 	char *p;
633 	static char buf[MAXPATHLEN + 4];
634 
635 	buf[0] = 0;
636 	p = ttyname(STDERR_FILENO);
637 	if (p)
638 		snprintf(buf, sizeof(buf), " on %s", p);
639 	return buf;
640 }
641