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