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