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