su.c revision 163665
197403Sobrien/*
297403Sobrien * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
397403Sobrien * All rights reserved.
497403Sobrien *
597403Sobrien * Portions of this software were developed for the FreeBSD Project by
697403Sobrien * ThinkSec AS and NAI Labs, the Security Research Division of Network
797403Sobrien * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
897403Sobrien * ("CBOSS"), as part of the DARPA CHATS research program.
997403Sobrien *
1097403Sobrien * Redistribution and use in source and binary forms, with or without
1197403Sobrien * modification, are permitted provided that the following conditions
1297403Sobrien * are met:
1397403Sobrien * 1. Redistributions of source code must retain the above copyright
1497403Sobrien *    notice, this list of conditions and the following disclaimer.
1597403Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1697403Sobrien *    notice, this list of conditions and the following disclaimer in the
1797403Sobrien *    documentation and/or other materials provided with the distribution.
1897403Sobrien *
1997403Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2097403Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2197403Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2297403Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2397403Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2497403Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2597403Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2697403Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2797403Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2897403Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2997403Sobrien * SUCH DAMAGE.
3097403Sobrien */
3197403Sobrien/*-
3297403Sobrien * Copyright (c) 1988, 1993, 1994
3397403Sobrien *	The Regents of the University of California.  All rights reserved.
3497403Sobrien *
3597403Sobrien * Redistribution and use in source and binary forms, with or without
3697403Sobrien * modification, are permitted provided that the following conditions
3797403Sobrien * are met:
3897403Sobrien * 1. Redistributions of source code must retain the above copyright
3997403Sobrien *    notice, this list of conditions and the following disclaimer.
4097403Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4197403Sobrien *    notice, this list of conditions and the following disclaimer in the
4297403Sobrien *    documentation and/or other materials provided with the distribution.
4397403Sobrien * 3. All advertising materials mentioning features or use of this software
4497403Sobrien *    must display the following acknowledgement:
4597403Sobrien *	This product includes software developed by the University of
4697403Sobrien *	California, Berkeley and its contributors.
4797403Sobrien * 4. Neither the name of the University nor the names of its contributors
4897403Sobrien *    may be used to endorse or promote products derived from this software
4997403Sobrien *    without specific prior written permission.
5097403Sobrien *
5197403Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
5297403Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5397403Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5497403Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
5597403Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
5697403Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
5797403Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
5897403Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
5997403Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6097403Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6197403Sobrien * SUCH DAMAGE.
6297403Sobrien */
6397403Sobrien
6497403Sobrien#ifndef lint
6597403Sobrienstatic const char copyright[] =
6697403Sobrien"@(#) Copyright (c) 1988, 1993, 1994\n\
6797403Sobrien	The Regents of the University of California.  All rights reserved.\n";
6897403Sobrien#endif /* not lint */
6997403Sobrien
7097403Sobrien#if 0
7197403Sobrien#ifndef lint
7297403Sobrienstatic char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
7397403Sobrien#endif /* not lint */
7497403Sobrien#endif
7597403Sobrien
7697403Sobrien#include <sys/cdefs.h>
7797403Sobrien__FBSDID("$FreeBSD: head/usr.bin/su/su.c 163665 2006-10-24 17:41:28Z sobomax $");
7897403Sobrien
7997403Sobrien#include <sys/param.h>
8097403Sobrien#include <sys/time.h>
8197403Sobrien#include <sys/resource.h>
8297403Sobrien#include <sys/wait.h>
8397403Sobrien
8497403Sobrien#ifdef USE_BSM_AUDIT
8597403Sobrien#include <bsm/libbsm.h>
8697403Sobrien#include <bsm/audit_uevents.h>
8797403Sobrien#endif
8897403Sobrien
8997403Sobrien#include <err.h>
9097403Sobrien#include <errno.h>
9197403Sobrien#include <grp.h>
9297403Sobrien#include <login_cap.h>
9397403Sobrien#include <paths.h>
9497403Sobrien#include <pwd.h>
9597403Sobrien#include <signal.h>
9697403Sobrien#include <stdio.h>
9797403Sobrien#include <stdlib.h>
9897403Sobrien#include <string.h>
9997403Sobrien#include <syslog.h>
10097403Sobrien#include <unistd.h>
10197403Sobrien#include <stdarg.h>
10297403Sobrien
10397403Sobrien#include <security/pam_appl.h>
10497403Sobrien#include <security/openpam.h>
10597403Sobrien
10697403Sobrien#define PAM_END() do {							\
10797403Sobrien	int local_ret;							\
10897403Sobrien	if (pamh != NULL) {						\
10997403Sobrien		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);		\
11097403Sobrien		if (local_ret != PAM_SUCCESS)				\
11197403Sobrien			syslog(LOG_ERR, "pam_setcred: %s",		\
11297403Sobrien				pam_strerror(pamh, local_ret));		\
11397403Sobrien		if (asthem) {						\
11497403Sobrien			local_ret = pam_close_session(pamh, 0);		\
11597403Sobrien			if (local_ret != PAM_SUCCESS)			\
11697403Sobrien				syslog(LOG_ERR, "pam_close_session: %s",\
11797403Sobrien					pam_strerror(pamh, local_ret));	\
11897403Sobrien		}							\
11997403Sobrien		local_ret = pam_end(pamh, local_ret);			\
12097403Sobrien		if (local_ret != PAM_SUCCESS)				\
12197403Sobrien			syslog(LOG_ERR, "pam_end: %s",			\
12297403Sobrien				pam_strerror(pamh, local_ret));		\
12397403Sobrien	}								\
12497403Sobrien} while (0)
12597403Sobrien
12697403Sobrien
12797403Sobrien#define PAM_SET_ITEM(what, item) do {					\
12897403Sobrien	int local_ret;							\
12997403Sobrien	local_ret = pam_set_item(pamh, what, item);			\
13097403Sobrien	if (local_ret != PAM_SUCCESS) {					\
13197403Sobrien		syslog(LOG_ERR, "pam_set_item(" #what "): %s",		\
13297403Sobrien			pam_strerror(pamh, local_ret));			\
13397403Sobrien		errx(1, "pam_set_item(" #what "): %s",			\
13497403Sobrien			pam_strerror(pamh, local_ret));			\
13597403Sobrien		/* NOTREACHED */					\
13697403Sobrien	}								\
13797403Sobrien} while (0)
13897403Sobrien
13997403Sobrienenum tristate { UNSET, YES, NO };
14097403Sobrien
14197403Sobrienstatic pam_handle_t *pamh = NULL;
14297403Sobrienstatic char	**environ_pam;
14397403Sobrien
14497403Sobrienstatic char	*ontty(void);
14597403Sobrienstatic int	chshell(const char *);
14697403Sobrienstatic void	usage(void) __dead2;
14797403Sobrienstatic void	export_pam_environment(void);
14897403Sobrienstatic int	ok_to_export(const char *);
14997403Sobrien
15097403Sobrienextern char	**environ;
15197403Sobrien
15297403Sobrienint
15397403Sobrienmain(int argc, char *argv[])
15497403Sobrien{
15597403Sobrien	static char	*cleanenv;
15697403Sobrien	struct passwd	*pwd;
15797403Sobrien	struct pam_conv	conv = { openpam_ttyconv, NULL };
15897403Sobrien	enum tristate	iscsh;
15997403Sobrien	login_cap_t	*lc;
16097403Sobrien	union {
16197403Sobrien		const char	**a;
16297403Sobrien		char		* const *b;
16397403Sobrien	}		np;
16497403Sobrien	uid_t		ruid;
16597403Sobrien	pid_t		child_pid, child_pgrp, pid;
16697403Sobrien	int		asme, ch, asthem, fastlogin, prio, i, retcode,
16797403Sobrien			statusp, setmaclabel;
16897403Sobrien	u_int		setwhat;
16997403Sobrien	char		*username, *class, shellbuf[MAXPATHLEN];
17097403Sobrien	const char	*p, *user, *shell, *mytty, **nargv;
17197403Sobrien	struct sigaction sa, sa_int, sa_quit, sa_pipe;
17297403Sobrien	int temp, fds[2];
17397403Sobrien#ifdef USE_BSM_AUDIT
17497403Sobrien	const char	*aerr;
17597403Sobrien	au_id_t		 auid;
17697403Sobrien#endif
17797403Sobrien
17897403Sobrien	shell = class = cleanenv = NULL;
17997403Sobrien	asme = asthem = fastlogin = statusp = 0;
18097403Sobrien	user = "root";
18197403Sobrien	iscsh = UNSET;
18297403Sobrien	setmaclabel = 0;
18397403Sobrien
18497403Sobrien	while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
18597403Sobrien		switch ((char)ch) {
18697403Sobrien		case 'f':
18797403Sobrien			fastlogin = 1;
18897403Sobrien			break;
18997403Sobrien		case '-':
19097403Sobrien		case 'l':
19197403Sobrien			asme = 0;
19297403Sobrien			asthem = 1;
19397403Sobrien			break;
19497403Sobrien		case 'm':
19597403Sobrien			asme = 1;
19697403Sobrien			asthem = 0;
19797403Sobrien			break;
19897403Sobrien		case 's':
19997403Sobrien			setmaclabel = 1;
20097403Sobrien			break;
20197403Sobrien		case 'c':
20297403Sobrien			class = optarg;
20397403Sobrien			break;
20497403Sobrien		case '?':
20597403Sobrien		default:
20697403Sobrien			usage();
20797403Sobrien		/* NOTREACHED */
20897403Sobrien		}
20997403Sobrien
21097403Sobrien	if (optind < argc)
21197403Sobrien		user = argv[optind++];
21297403Sobrien
21397403Sobrien	if (user == NULL)
21497403Sobrien		usage();
21597403Sobrien	/* NOTREACHED */
21697403Sobrien
21797403Sobrien	/*
21897403Sobrien	 * Try to provide more helpful debugging output if su(1) is running
21997403Sobrien	 * non-setuid, or was run from a file system not mounted setuid.
22097403Sobrien	 */
22197403Sobrien	if (geteuid() != 0)
22297403Sobrien		errx(1, "not running setuid");
22397403Sobrien
22497403Sobrien#ifdef USE_BSM_AUDIT
22597403Sobrien	if (getauid(&auid) < 0 && errno != ENOSYS) {
22697403Sobrien		syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
22797403Sobrien		errx(1, "Permission denied");
22897403Sobrien	}
22997403Sobrien#endif
23097403Sobrien	if (strlen(user) > MAXLOGNAME - 1) {
23197403Sobrien#ifdef USE_BSM_AUDIT
23297403Sobrien		if (audit_submit(AUE_su, auid,
23397403Sobrien		    1, EPERM, "username too long: '%s'", user))
23497403Sobrien			errx(1, "Permission denied");
23597403Sobrien#endif
23697403Sobrien		errx(1, "username too long");
23797403Sobrien	}
23897403Sobrien
23997403Sobrien	nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
24097403Sobrien	if (nargv == NULL)
24197403Sobrien		errx(1, "malloc failure");
24297403Sobrien
24397403Sobrien	nargv[argc + 3] = NULL;
24497403Sobrien	for (i = argc; i >= optind; i--)
24597403Sobrien		nargv[i + 3] = argv[i];
24697403Sobrien	np.a = &nargv[i + 3];
24797403Sobrien
24897403Sobrien	argv += optind;
24997403Sobrien
25097403Sobrien	errno = 0;
25197403Sobrien	prio = getpriority(PRIO_PROCESS, 0);
25297403Sobrien	if (errno)
25397403Sobrien		prio = 0;
25497403Sobrien
25597403Sobrien	setpriority(PRIO_PROCESS, 0, -2);
25697403Sobrien	openlog("su", LOG_CONS, LOG_AUTH);
25797403Sobrien
25897403Sobrien	/* get current login name, real uid and shell */
25997403Sobrien	ruid = getuid();
26097403Sobrien	username = getlogin();
26197403Sobrien	pwd = getpwnam(username);
26297403Sobrien	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
26397403Sobrien		pwd = getpwuid(ruid);
26497403Sobrien	if (pwd == NULL) {
26597403Sobrien#ifdef USE_BSM_AUDIT
26697403Sobrien		if (audit_submit(AUE_su, auid, 1, EPERM,
26797403Sobrien		    "unable to determine invoking subject: '%s'", username))
26897403Sobrien			errx(1, "Permission denied");
26997403Sobrien#endif
27097403Sobrien		errx(1, "who are you?");
27197403Sobrien	}
27297403Sobrien
27397403Sobrien	username = strdup(pwd->pw_name);
27497403Sobrien	if (username == NULL)
27597403Sobrien		err(1, "strdup failure");
27697403Sobrien
27797403Sobrien	if (asme) {
27897403Sobrien		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
27997403Sobrien			/* must copy - pwd memory is recycled */
28097403Sobrien			shell = strncpy(shellbuf, pwd->pw_shell,
28197403Sobrien			    sizeof(shellbuf));
28297403Sobrien			shellbuf[sizeof(shellbuf) - 1] = '\0';
28397403Sobrien		}
28497403Sobrien		else {
28597403Sobrien			shell = _PATH_BSHELL;
28697403Sobrien			iscsh = NO;
28797403Sobrien		}
28897403Sobrien	}
28997403Sobrien
29097403Sobrien	/* Do the whole PAM startup thing */
29197403Sobrien	retcode = pam_start("su", user, &conv, &pamh);
29297403Sobrien	if (retcode != PAM_SUCCESS) {
29397403Sobrien		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
29497403Sobrien		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
29597403Sobrien	}
29697403Sobrien
29797403Sobrien	PAM_SET_ITEM(PAM_RUSER, username);
29897403Sobrien
29997403Sobrien	mytty = ttyname(STDERR_FILENO);
30097403Sobrien	if (!mytty)
30197403Sobrien		mytty = "tty";
30297403Sobrien	PAM_SET_ITEM(PAM_TTY, mytty);
30397403Sobrien
30497403Sobrien	retcode = pam_authenticate(pamh, 0);
30597403Sobrien	if (retcode != PAM_SUCCESS) {
30697403Sobrien#ifdef USE_BSM_AUDIT
30797403Sobrien		if (audit_submit(AUE_su, auid, 1, EPERM, "bad su %s to %s on %s",
30897403Sobrien		    username, user, mytty))
30997403Sobrien			errx(1, "Permission denied");
31097403Sobrien#endif
31197403Sobrien		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
31297403Sobrien		    username, user, mytty);
31397403Sobrien		errx(1, "Sorry");
31497403Sobrien	}
31597403Sobrien#ifdef USE_BSM_AUDIT
31697403Sobrien	if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
31797403Sobrien		errx(1, "Permission denied");
31897403Sobrien#endif
31997403Sobrien	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
32097403Sobrien	if (retcode == PAM_SUCCESS)
32197403Sobrien		user = p;
32297403Sobrien	else
32397403Sobrien		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
32497403Sobrien		    pam_strerror(pamh, retcode));
32597403Sobrien	pwd = getpwnam(user);
32697403Sobrien	if (pwd == NULL) {
32797403Sobrien#ifdef USE_BSM_AUDIT
32897403Sobrien		if (audit_submit(AUE_su, auid, 1, EPERM,
32997403Sobrien		    "unknown subject: %s", user))
33097403Sobrien			errx(1, "Permission denied");
33197403Sobrien#endif
33297403Sobrien		errx(1, "unknown login: %s", user);
33397403Sobrien	}
33497403Sobrien
33597403Sobrien	retcode = pam_acct_mgmt(pamh, 0);
33697403Sobrien	if (retcode == PAM_NEW_AUTHTOK_REQD) {
33797403Sobrien		retcode = pam_chauthtok(pamh,
33897403Sobrien			PAM_CHANGE_EXPIRED_AUTHTOK);
33997403Sobrien		if (retcode != PAM_SUCCESS) {
34097403Sobrien#ifdef USE_BSM_AUDIT
34197403Sobrien			aerr = pam_strerror(pamh, retcode);
34297403Sobrien			if (aerr == NULL)
34397403Sobrien				aerr = "Unknown PAM error";
34497403Sobrien			if (audit_submit(AUE_su, auid, 1, EPERM,
34597403Sobrien			    "pam_chauthtok: %s", aerr))
34697403Sobrien				errx(1, "Permission denied");
34797403Sobrien#endif
34897403Sobrien			syslog(LOG_ERR, "pam_chauthtok: %s",
34997403Sobrien			    pam_strerror(pamh, retcode));
35097403Sobrien			errx(1, "Sorry");
35197403Sobrien		}
35297403Sobrien	}
35397403Sobrien	if (retcode != PAM_SUCCESS) {
35497403Sobrien#ifdef USE_BSM_AUDIT
35597403Sobrien		if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s",
35697403Sobrien		    pam_strerror(pamh, retcode)))
35797403Sobrien			errx(1, "Permission denied");
35897403Sobrien#endif
35997403Sobrien		syslog(LOG_ERR, "pam_acct_mgmt: %s",
36097403Sobrien			pam_strerror(pamh, retcode));
36197403Sobrien		errx(1, "Sorry");
36297403Sobrien	}
36397403Sobrien
36497403Sobrien	/* get target login information */
36597403Sobrien	if (class == NULL)
36697403Sobrien		lc = login_getpwclass(pwd);
36797403Sobrien	else {
36897403Sobrien		if (ruid != 0) {
36997403Sobrien#ifdef USE_BSM_AUDIT
37097403Sobrien			if (audit_submit(AUE_su, auid, 1, EPERM,
37197403Sobrien			    "only root may use -c"))
37297403Sobrien				errx(1, "Permission denied");
37397403Sobrien#endif
37497403Sobrien			errx(1, "only root may use -c");
37597403Sobrien		}
37697403Sobrien		lc = login_getclass(class);
37797403Sobrien		if (lc == NULL)
37897403Sobrien			errx(1, "unknown class: %s", class);
37997403Sobrien	}
38097403Sobrien
38197403Sobrien	/* if asme and non-standard target shell, must be root */
38297403Sobrien	if (asme) {
38397403Sobrien		if (ruid != 0 && !chshell(pwd->pw_shell))
38497403Sobrien			errx(1, "permission denied (shell)");
38597403Sobrien	}
38697403Sobrien	else if (pwd->pw_shell && *pwd->pw_shell) {
38797403Sobrien		shell = pwd->pw_shell;
38897403Sobrien		iscsh = UNSET;
38997403Sobrien	}
39097403Sobrien	else {
39197403Sobrien		shell = _PATH_BSHELL;
39297403Sobrien		iscsh = NO;
39397403Sobrien	}
39497403Sobrien
39597403Sobrien	/* if we're forking a csh, we want to slightly muck the args */
39697403Sobrien	if (iscsh == UNSET) {
39797403Sobrien		p = strrchr(shell, '/');
39897403Sobrien		if (p)
39997403Sobrien			++p;
40097403Sobrien		else
40197403Sobrien			p = shell;
40297403Sobrien		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
40397403Sobrien	}
40497403Sobrien	setpriority(PRIO_PROCESS, 0, prio);
40597403Sobrien
40697403Sobrien	/*
40797403Sobrien	 * PAM modules might add supplementary groups in pam_setcred(), so
40897403Sobrien	 * initialize them first.
40997403Sobrien	 */
41097403Sobrien	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
41197403Sobrien		err(1, "setusercontext");
41297403Sobrien
41397403Sobrien	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
41497403Sobrien	if (retcode != PAM_SUCCESS) {
41597403Sobrien		syslog(LOG_ERR, "pam_setcred: %s",
41697403Sobrien		    pam_strerror(pamh, retcode));
41797403Sobrien		errx(1, "failed to establish credentials.");
41897403Sobrien	}
41997403Sobrien	if (asthem) {
42097403Sobrien		retcode = pam_open_session(pamh, 0);
42197403Sobrien		if (retcode != PAM_SUCCESS) {
42297403Sobrien			syslog(LOG_ERR, "pam_open_session: %s",
42397403Sobrien			    pam_strerror(pamh, retcode));
42497403Sobrien			errx(1, "failed to open session.");
42597403Sobrien		}
42697403Sobrien	}
42797403Sobrien
42897403Sobrien	/*
42997403Sobrien	 * We must fork() before setuid() because we need to call
43097403Sobrien	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
43197403Sobrien	 */
43297403Sobrien	sa.sa_flags = SA_RESTART;
43397403Sobrien	sa.sa_handler = SIG_IGN;
43497403Sobrien	sigemptyset(&sa.sa_mask);
43597403Sobrien	sigaction(SIGINT, &sa, &sa_int);
43697403Sobrien	sigaction(SIGQUIT, &sa, &sa_quit);
43797403Sobrien	sigaction(SIGPIPE, &sa, &sa_pipe);
43897403Sobrien	sa.sa_handler = SIG_DFL;
43997403Sobrien	sigaction(SIGTSTP, &sa, NULL);
44097403Sobrien	statusp = 1;
44197403Sobrien	if (pipe(fds) == -1) {
44297403Sobrien		PAM_END();
44397403Sobrien		err(1, "pipe");
44497403Sobrien	}
44597403Sobrien	child_pid = fork();
44697403Sobrien	switch (child_pid) {
44797403Sobrien	default:
44897403Sobrien		sa.sa_handler = SIG_IGN;
44997403Sobrien		sigaction(SIGTTOU, &sa, NULL);
45097403Sobrien		close(fds[0]);
45197403Sobrien		setpgid(child_pid, child_pid);
45297403Sobrien		if (tcgetpgrp(STDERR_FILENO) == getpgrp())
45397403Sobrien			tcsetpgrp(STDERR_FILENO, child_pid);
45497403Sobrien		close(fds[1]);
45597403Sobrien		sigaction(SIGPIPE, &sa_pipe, NULL);
45697403Sobrien		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
45797403Sobrien			if (WIFSTOPPED(statusp)) {
45897403Sobrien				child_pgrp = getpgid(child_pid);
45997403Sobrien				if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
46097403Sobrien					tcsetpgrp(STDERR_FILENO, getpgrp());
46197403Sobrien				kill(getpid(), SIGSTOP);
46297403Sobrien				if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
46397403Sobrien					child_pgrp = getpgid(child_pid);
46497403Sobrien					tcsetpgrp(STDERR_FILENO, child_pgrp);
46597403Sobrien				}
46697403Sobrien				kill(child_pid, SIGCONT);
46797403Sobrien				statusp = 1;
46897403Sobrien				continue;
46997403Sobrien			}
47097403Sobrien			break;
47197403Sobrien		}
47297403Sobrien		child_pgrp = getpgid(child_pid);
47397403Sobrien		if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
47497403Sobrien			tcsetpgrp(STDERR_FILENO, getpgrp());
47597403Sobrien		if (pid == -1)
47697403Sobrien			err(1, "waitpid");
47797403Sobrien		PAM_END();
47897403Sobrien		exit(WEXITSTATUS(statusp));
47997403Sobrien	case -1:
48097403Sobrien		PAM_END();
48197403Sobrien		err(1, "fork");
48297403Sobrien	case 0:
48397403Sobrien		close(fds[1]);
48497403Sobrien		read(fds[0], &temp, 1);
48597403Sobrien		close(fds[0]);
48697403Sobrien		sigaction(SIGPIPE, &sa_pipe, NULL);
48797403Sobrien		sigaction(SIGINT, &sa_int, NULL);
48897403Sobrien		sigaction(SIGQUIT, &sa_quit, NULL);
48997403Sobrien
49097403Sobrien		/*
49197403Sobrien		 * Set all user context except for: Environmental variables
49297403Sobrien		 * Umask Login records (wtmp, etc) Path
49397403Sobrien		 */
49497403Sobrien		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
49597403Sobrien			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
49697403Sobrien			   LOGIN_SETMAC);
49797403Sobrien		/*
49897403Sobrien		 * If -s is present, also set the MAC label.
49997403Sobrien		 */
50097403Sobrien		if (setmaclabel)
50197403Sobrien			setwhat |= LOGIN_SETMAC;
50297403Sobrien		/*
50397403Sobrien		 * Don't touch resource/priority settings if -m has been used
50497403Sobrien		 * or -l and -c hasn't, and we're not su'ing to root.
50597403Sobrien		 */
50697403Sobrien		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
50797403Sobrien			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
50897403Sobrien		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
50997403Sobrien			err(1, "setusercontext");
51097403Sobrien
51197403Sobrien		if (!asme) {
51297403Sobrien			if (asthem) {
51397403Sobrien				p = getenv("TERM");
51497403Sobrien				environ = &cleanenv;
51597403Sobrien			}
51697403Sobrien
51797403Sobrien			if (asthem || pwd->pw_uid)
51897403Sobrien				setenv("USER", pwd->pw_name, 1);
51997403Sobrien			setenv("HOME", pwd->pw_dir, 1);
52097403Sobrien			setenv("SHELL", shell, 1);
52197403Sobrien
52297403Sobrien			if (asthem) {
52397403Sobrien				/*
52497403Sobrien				 * Add any environmental variables that the
52597403Sobrien				 * PAM modules may have set.
52697403Sobrien				 */
52797403Sobrien				environ_pam = pam_getenvlist(pamh);
52897403Sobrien				if (environ_pam)
52997403Sobrien					export_pam_environment();
53097403Sobrien
53197403Sobrien				/* set the su'd user's environment & umask */
53297403Sobrien				setusercontext(lc, pwd, pwd->pw_uid,
53397403Sobrien					LOGIN_SETPATH | LOGIN_SETUMASK |
53497403Sobrien					LOGIN_SETENV);
53597403Sobrien				if (p)
53697403Sobrien					setenv("TERM", p, 1);
53797403Sobrien
53897403Sobrien				p = pam_getenv(pamh, "HOME");
53997403Sobrien				if (chdir(p ? p : pwd->pw_dir) < 0)
54097403Sobrien					errx(1, "no directory");
54197403Sobrien			}
54297403Sobrien		}
54397403Sobrien		login_close(lc);
54497403Sobrien
54597403Sobrien		if (iscsh == YES) {
54697403Sobrien			if (fastlogin)
54797403Sobrien				*np.a-- = "-f";
54897403Sobrien			if (asme)
54997403Sobrien				*np.a-- = "-m";
55097403Sobrien		}
55197403Sobrien		/* csh strips the first character... */
55297403Sobrien		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
55397403Sobrien
55497403Sobrien		if (ruid != 0)
55597403Sobrien			syslog(LOG_NOTICE, "%s to %s%s", username, user,
55697403Sobrien			    ontty());
55797403Sobrien
55897403Sobrien		execv(shell, np.b);
55997403Sobrien		err(1, "%s", shell);
56097403Sobrien	}
56197403Sobrien}
56297403Sobrien
56397403Sobrienstatic void
56497403Sobrienexport_pam_environment(void)
56597403Sobrien{
56697403Sobrien	char	**pp;
56797403Sobrien
56897403Sobrien	for (pp = environ_pam; *pp != NULL; pp++) {
56997403Sobrien		if (ok_to_export(*pp))
57097403Sobrien			putenv(*pp);
57197403Sobrien		free(*pp);
57297403Sobrien	}
57397403Sobrien}
57497403Sobrien
57597403Sobrien/*
57697403Sobrien * Sanity checks on PAM environmental variables:
57797403Sobrien * - Make sure there is an '=' in the string.
57897403Sobrien * - Make sure the string doesn't run on too long.
57997403Sobrien * - Do not export certain variables.  This list was taken from the
58097403Sobrien *   Solaris pam_putenv(3) man page.
58197403Sobrien * Note that if the user is chrooted, PAM may have a better idea than we
58297403Sobrien * do of where her home directory is.
58397403Sobrien */
58497403Sobrienstatic int
58597403Sobrienok_to_export(const char *s)
58697403Sobrien{
58797403Sobrien	static const char *noexport[] = {
58897403Sobrien		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
58997403Sobrien		"IFS", "PATH", NULL
59097403Sobrien	};
59197403Sobrien	const char **pp;
59297403Sobrien	size_t n;
59397403Sobrien
59497403Sobrien	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
59597403Sobrien		return 0;
59697403Sobrien	if (strncmp(s, "LD_", 3) == 0)
59797403Sobrien		return 0;
59897403Sobrien	for (pp = noexport; *pp != NULL; pp++) {
59997403Sobrien		n = strlen(*pp);
60097403Sobrien		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
60197403Sobrien			return 0;
60297403Sobrien	}
60397403Sobrien	return 1;
60497403Sobrien}
60597403Sobrien
60697403Sobrienstatic void
60797403Sobrienusage(void)
60897403Sobrien{
60997403Sobrien
61097403Sobrien	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
61197403Sobrien	exit(1);
61297403Sobrien	/* NOTREACHED */
61397403Sobrien}
61497403Sobrien
61597403Sobrienstatic int
61697403Sobrienchshell(const char *sh)
61797403Sobrien{
61897403Sobrien	int r;
61997403Sobrien	char *cp;
62097403Sobrien
62197403Sobrien	r = 0;
62297403Sobrien	setusershell();
62397403Sobrien	while ((cp = getusershell()) != NULL && !r)
62497403Sobrien	    r = (strcmp(cp, sh) == 0);
62597403Sobrien	endusershell();
62697403Sobrien	return r;
62797403Sobrien}
62897403Sobrien
62997403Sobrienstatic char *
63097403Sobrienontty(void)
63197403Sobrien{
63297403Sobrien	char *p;
63397403Sobrien	static char buf[MAXPATHLEN + 4];
63497403Sobrien
63597403Sobrien	buf[0] = 0;
63697403Sobrien	p = ttyname(STDERR_FILENO);
63797403Sobrien	if (p)
63897403Sobrien		snprintf(buf, sizeof(buf), " on %s", p);
63997403Sobrien	return buf;
64097403Sobrien}
64197403Sobrien