su.c revision 83373
115885Sjulian/*
215885Sjulian * Copyright (c) 1988, 1993, 1994
315885Sjulian *	The Regents of the University of California.  All rights reserved.
415885Sjulian *
515885Sjulian * Redistribution and use in source and binary forms, with or without
615885Sjulian * modification, are permitted provided that the following conditions
715885Sjulian * are met:
815885Sjulian * 1. Redistributions of source code must retain the above copyright
915885Sjulian *    notice, this list of conditions and the following disclaimer.
1015885Sjulian * 2. Redistributions in binary form must reproduce the above copyright
1115885Sjulian *    notice, this list of conditions and the following disclaimer in the
1215885Sjulian *    documentation and/or other materials provided with the distribution.
1315885Sjulian * 3. All advertising materials mentioning features or use of this software
1415885Sjulian *    must display the following acknowledgement:
1515885Sjulian *	This product includes software developed by the University of
1615885Sjulian *	California, Berkeley and its contributors.
1715885Sjulian * 4. Neither the name of the University nor the names of its contributors
1815885Sjulian *    may be used to endorse or promote products derived from this software
1915885Sjulian *    without specific prior written permission.
2015885Sjulian *
2115885Sjulian * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2218207Sbde * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2318207Sbde * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2418207Sbde * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2518207Sbde * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2615885Sjulian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2715885Sjulian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2817967Sjulian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2917967Sjulian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3017254Sjulian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3117254Sjulian * SUCH DAMAGE.
3217254Sjulian */
3317254Sjulian
3417254Sjulian#ifndef lint
3517254Sjulianstatic const char copyright[] =
3615885Sjulian"@(#) Copyright (c) 1988, 1993, 1994\n\
3715885Sjulian	The Regents of the University of California.  All rights reserved.\n";
3815885Sjulian#endif /* not lint */
3915885Sjulian
4015885Sjulian#ifndef lint
4115885Sjulian#if 0
4215885Sjulianstatic char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
4315885Sjulian#endif
4415885Sjulianstatic const char rcsid[] =
4515885Sjulian  "$FreeBSD: head/usr.bin/su/su.c 83373 2001-09-12 19:15:02Z markm $";
4615885Sjulian#endif /* not lint */
4715885Sjulian
4815885Sjulian#include <sys/param.h>
4915885Sjulian#include <sys/time.h>
5015885Sjulian#include <sys/resource.h>
5115885Sjulian#include <sys/wait.h>
5215885Sjulian
5315885Sjulian#include <err.h>
5418240Sjulian#include <errno.h>
5515885Sjulian#include <grp.h>
5617921Sjulian#include <libutil.h>
5717921Sjulian#include <login_cap.h>
5817921Sjulian#include <paths.h>
5915885Sjulian#include <pwd.h>
6015885Sjulian#include <signal.h>
6115885Sjulian#include <stdio.h>
6215885Sjulian#include <stdlib.h>
6315885Sjulian#include <string.h>
6415885Sjulian#include <syslog.h>
6517921Sjulian#include <unistd.h>
6617921Sjulian
6717921Sjulian#include <security/pam_appl.h>
6817921Sjulian#include <security/pam_misc.h>
6917921Sjulian
7017921Sjulian#define PAM_END() do {						\
7115885Sjulian	int local_ret;						\
7215885Sjulian	if (pamh != NULL && creds_set) {			\
7315885Sjulian		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);	\
7417921Sjulian		if (local_ret != PAM_SUCCESS)			\
7517921Sjulian			syslog(LOG_ERR, "pam_setcred: %s",	\
7617921Sjulian				pam_strerror(pamh, local_ret));	\
7717921Sjulian		local_ret = pam_end(pamh, local_ret);		\
7817921Sjulian		if (local_ret != PAM_SUCCESS)			\
7917921Sjulian			syslog(LOG_ERR, "pam_end: %s",		\
8017921Sjulian				pam_strerror(pamh, local_ret));	\
8115885Sjulian	}							\
8215885Sjulian} while (0)
8315885Sjulian
8415885Sjulian
8515885Sjulian#define PAM_SET_ITEM(what, item) do {				\
8615885Sjulian	int local_ret;						\
8715885Sjulian	local_ret = pam_set_item(pamh, what, item);		\
8815885Sjulian	if (local_ret != PAM_SUCCESS) {				\
8917921Sjulian		syslog(LOG_ERR, "pam_set_item(" #what "): %s",	\
9017921Sjulian			pam_strerror(pamh, local_ret));		\
9117921Sjulian		errx(1, "pam_set_item(" #what "): %s",		\
9217921Sjulian			pam_strerror(pamh, local_ret));		\
9315885Sjulian	}							\
9415885Sjulian} while (0)
9515885Sjulian
9615885Sjulianenum tristate { UNSET, YES, NO };
9715885Sjulian
9815885Sjulianstatic pam_handle_t *pamh = NULL;
9917921Sjulianstatic int	creds_set = 0;
10017921Sjulianstatic char	**environ_pam;
10117921Sjulian
10215885Sjulianstatic char	*ontty(void);
10315885Sjulianstatic int	chshell(char *);
10415885Sjulianstatic void	usage(void);
10515885Sjulianstatic int	export_pam_environment(void);
10615885Sjulianstatic int	ok_to_export(const char *);
10715885Sjulian
10815885Sjulianextern char	**environ;
10917921Sjulian
11017921Sjulianint
11117921Sjulianmain(int argc, char *argv[])
11217921Sjulian{
11317921Sjulian	struct passwd	*pwd;
11415885Sjulian	struct pam_conv	conv = {misc_conv, NULL};
11515885Sjulian	enum tristate	iscsh;
11615885Sjulian	login_cap_t	*lc;
11715885Sjulian	union {
11815885Sjulian		const char	**a;
11915885Sjulian		char		* const *b;
12015885Sjulian	} 		np;
12117921Sjulian	uid_t		ruid;
12217921Sjulian	gid_t		gid;
12317921Sjulian	int		asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
12417921Sjulian			statusp, child_pid, child_pgrp, ret_pid;
12517921Sjulian	char		*username, *cleanenv, *class, shellbuf[MAXPATHLEN],
12615885Sjulian			myhost[MAXHOSTNAMELEN + 1];
12715885Sjulian	const char	*p, *user, *shell, *mytty, **nargv;
12815885Sjulian
12915885Sjulian	shell = class = cleanenv = NULL;
13015885Sjulian	asme = asthem = fastlogin = statusp = 0;
13115885Sjulian	user = "root";
13215885Sjulian	iscsh = UNSET;
13315885Sjulian
13415885Sjulian	while ((ch = getopt(argc, argv, "-flmc:")) != -1)
13515885Sjulian		switch ((char)ch) {
13617921Sjulian		case 'f':
13717921Sjulian			fastlogin = 1;
13817921Sjulian			break;
13917921Sjulian		case '-':
14017921Sjulian		case 'l':
14115885Sjulian			asme = 0;
14218240Sjulian			asthem = 1;
14318244Sjulian			break;
14415885Sjulian		case 'm':
14515885Sjulian			asme = 1;
14615885Sjulian			asthem = 0;
14715885Sjulian			break;
14815885Sjulian		case 'c':
14917921Sjulian			class = optarg;
15017921Sjulian			break;
15115885Sjulian		case '?':
15215885Sjulian		default:
15318240Sjulian			usage();
15415885Sjulian		}
15515885Sjulian
15615885Sjulian	if (optind < argc)
15715885Sjulian		user = argv[optind++];
15815885Sjulian
15918240Sjulian	if (user == NULL)
16015885Sjulian		usage();
16115885Sjulian
16218240Sjulian	if (strlen(user) > MAXLOGNAME - 1)
16315885Sjulian		errx(1, "username too long");
16418240Sjulian
16518240Sjulian	nargv = malloc(sizeof(char *) * (argc + 4));
16618240Sjulian	if (nargv == NULL)
16718240Sjulian		errx(1, "malloc failure");
16818240Sjulian
16918240Sjulian	nargv[argc + 3] = NULL;
17018240Sjulian	for (i = argc; i >= optind; i--)
17115885Sjulian		nargv[i + 3] = argv[i];
17217921Sjulian	np.a = &nargv[i + 3];
17317921Sjulian
17417921Sjulian	argv += optind;
17517921Sjulian
17620407Swollman	errno = 0;
17720407Swollman	prio = getpriority(PRIO_PROCESS, 0);
17820407Swollman	if (errno)
17918240Sjulian		prio = 0;
18018240Sjulian
18118240Sjulian	setpriority(PRIO_PROCESS, 0, -2);
18220407Swollman	openlog("su", LOG_CONS, LOG_AUTH);
18315885Sjulian
18417921Sjulian	/* get current login name, real uid and shell */
18517921Sjulian	ruid = getuid();
18617921Sjulian	username = getlogin();
18717921Sjulian	pwd = getpwnam(username);
18820407Swollman	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
18920407Swollman		pwd = getpwuid(ruid);
19020407Swollman	if (pwd == NULL)
19115885Sjulian		errx(1, "who are you?");
19215885Sjulian	gid = pwd->pw_gid;
19315885Sjulian
19415885Sjulian	username = strdup(pwd->pw_name);
19515885Sjulian	if (username == NULL)
19615885Sjulian		err(1, "strdup failure");
19715885Sjulian
19815885Sjulian	if (asme) {
19915885Sjulian		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
20017921Sjulian			/* must copy - pwd memory is recycled */
20117921Sjulian			shell = strncpy(shellbuf, pwd->pw_shell,
20217921Sjulian			    sizeof(shellbuf));
20317921Sjulian			shellbuf[sizeof(shellbuf) - 1] = '\0';
20415885Sjulian		}
20515885Sjulian		else {
20617921Sjulian			shell = _PATH_BSHELL;
20717921Sjulian			iscsh = NO;
20817921Sjulian		}
20915885Sjulian	}
21015885Sjulian
21115885Sjulian	/* Do the whole PAM startup thing */
21215885Sjulian	retcode = pam_start("su", user, &conv, &pamh);
21315885Sjulian	if (retcode != PAM_SUCCESS) {
21415885Sjulian		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
21515885Sjulian		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
21615885Sjulian	}
21717921Sjulian
21817921Sjulian	PAM_SET_ITEM(PAM_RUSER, getlogin());
21917921Sjulian
22017921Sjulian	gethostname(myhost, sizeof(myhost));
22115885Sjulian	PAM_SET_ITEM(PAM_RHOST, myhost);
22215885Sjulian
22315885Sjulian	mytty = ttyname(STDERR_FILENO);
22415885Sjulian	if (!mytty)
22515885Sjulian		mytty = "tty";
22615885Sjulian	PAM_SET_ITEM(PAM_TTY, mytty);
22717921Sjulian
22817921Sjulian	retcode = pam_authenticate(pamh, 0);
22917921Sjulian	if (retcode != PAM_SUCCESS) {
23017921Sjulian		syslog(LOG_ERR, "pam_authenticate: %s",
23115885Sjulian		    pam_strerror(pamh, retcode));
23215885Sjulian		errx(1, "Sorry");
23315885Sjulian	}
23415885Sjulian	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
23515885Sjulian	if (retcode == PAM_SUCCESS)
23615885Sjulian		user = p;
23715885Sjulian	else
23815885Sjulian		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
23915885Sjulian		    pam_strerror(pamh, retcode));
24015885Sjulian
24115885Sjulian	retcode = pam_acct_mgmt(pamh, 0);
24215885Sjulian	if (retcode == PAM_NEW_AUTHTOK_REQD) {
24317921Sjulian		retcode = pam_chauthtok(pamh,
24417921Sjulian			PAM_CHANGE_EXPIRED_AUTHTOK);
24517921Sjulian		if (retcode != PAM_SUCCESS) {
24617921Sjulian			syslog(LOG_ERR, "pam_chauthtok: %s",
24715885Sjulian			    pam_strerror(pamh, retcode));
24815885Sjulian			errx(1, "Sorry");
24917921Sjulian		}
25017921Sjulian	}
25117921Sjulian	if (retcode != PAM_SUCCESS) {
25217921Sjulian		syslog(LOG_ERR, "pam_acct_mgmt: %s",
25317254Sjulian			pam_strerror(pamh, retcode));
25417254Sjulian		errx(1, "Sorry");
25517921Sjulian	}
25617921Sjulian
25717921Sjulian	/* get target login information, default to root */
25817921Sjulian	pwd = getpwnam(user);
25917254Sjulian	if (pwd == NULL)
26017254Sjulian		errx(1, "unknown login: %s", user);
26117254Sjulian	if (class == NULL)
26217254Sjulian		lc = login_getpwclass(pwd);
26315885Sjulian	else {
26415885Sjulian		if (ruid != 0)
26515885Sjulian			errx(1, "only root may use -c");
26615885Sjulian		lc = login_getclass(class);
26715885Sjulian		if (lc == NULL)
26815885Sjulian			errx(1, "unknown class: %s", class);
26915885Sjulian	}
27015885Sjulian
27115885Sjulian	/* if asme and non-standard target shell, must be root */
27215885Sjulian	if (asme) {
27315885Sjulian		if (ruid != 0 && !chshell(pwd->pw_shell))
27415885Sjulian			errx(1, "permission denied (shell).");
27517921Sjulian	}
27617921Sjulian	else if (pwd->pw_shell && *pwd->pw_shell) {
27717921Sjulian		shell = pwd->pw_shell;
27815885Sjulian		iscsh = UNSET;
27917921Sjulian	}
28017921Sjulian	else {
28117921Sjulian		shell = _PATH_BSHELL;
28217921Sjulian		iscsh = NO;
28318240Sjulian	}
28420407Swollman
28517921Sjulian	/* if we're forking a csh, we want to slightly muck the args */
28618240Sjulian	if (iscsh == UNSET) {
28718240Sjulian		p = strrchr(shell, '/');
28818240Sjulian		if (p)
28918240Sjulian			++p;
29018240Sjulian		else
29115885Sjulian			p = shell;
29217921Sjulian		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
29317921Sjulian	}
29417921Sjulian	setpriority(PRIO_PROCESS, 0, prio);
29517921Sjulian
29615885Sjulian	/*
29715885Sjulian	 * PAM modules might add supplementary groups in pam_setcred(), so
29815885Sjulian	 * initialize them first.
29915885Sjulian	 */
30015885Sjulian	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
30115885Sjulian		err(1, "setusercontext");
30215885Sjulian
30317921Sjulian	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
30417921Sjulian	if (retcode != PAM_SUCCESS)
30517921Sjulian		syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
30617921Sjulian		    pam_strerror(pamh, retcode));
30715885Sjulian	else
30815885Sjulian		creds_set = 1;
30915885Sjulian
31015885Sjulian	/*
31115885Sjulian	 * We must fork() before setuid() because we need to call
31215885Sjulian	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
31317921Sjulian	 */
31417921Sjulian
31518240Sjulian	statusp = 1;
31618240Sjulian	child_pid = fork();
31718240Sjulian	switch (child_pid) {
31818240Sjulian	default:
31918240Sjulian		while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
32017921Sjulian			if (WIFSTOPPED(statusp)) {
32118240Sjulian				child_pgrp = tcgetpgrp(1);
32215885Sjulian				kill(getpid(), SIGSTOP);
32315885Sjulian				tcsetpgrp(1, child_pgrp);
32415885Sjulian				kill(child_pid, SIGCONT);
32515885Sjulian				statusp = 1;
32615885Sjulian				continue;
32715885Sjulian			}
32815885Sjulian			break;
32915885Sjulian		}
33015885Sjulian		if (ret_pid == -1)
33117254Sjulian			err(1, "waitpid");
33217921Sjulian		PAM_END();
33317921Sjulian		exit(statusp);
33417921Sjulian	case -1:
33517921Sjulian		err(1, "fork");
33617921Sjulian		PAM_END();
33717921Sjulian		exit(1);
33815885Sjulian	case 0:
33915885Sjulian		/*
34015885Sjulian		 * Set all user context except for: Environmental variables
34115885Sjulian		 * Umask Login records (wtmp, etc) Path
34215885Sjulian		 */
34315885Sjulian		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
34415885Sjulian			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
34515885Sjulian		/*
34617967Sjulian		 * Don't touch resource/priority settings if -m has been used
34718005Sjulian		 * or -l and -c hasn't, and we're not su'ing to root.
34818005Sjulian		 */
34918005Sjulian		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
35018005Sjulian			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
35118005Sjulian		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
35217967Sjulian			err(1, "setusercontext");
35317967Sjulian
35417967Sjulian		if (!asme) {
35517967Sjulian			if (asthem) {
35617967Sjulian				p = getenv("TERM");
35717967Sjulian				environ = &cleanenv;
35817967Sjulian
35917967Sjulian				/*
36015885Sjulian				 * Add any environmental variables that the
36115885Sjulian				 * PAM modules may have set.
36215885Sjulian				 */
36315885Sjulian				environ_pam = pam_getenvlist(pamh);
36415885Sjulian				if (environ_pam)
36515885Sjulian					export_pam_environment();
36615885Sjulian
36717921Sjulian				/* set the su'd user's environment & umask */
36817921Sjulian				setusercontext(lc, pwd, pwd->pw_uid,
36917921Sjulian					LOGIN_SETPATH | LOGIN_SETUMASK |
37017921Sjulian					LOGIN_SETENV);
37115885Sjulian				if (p)
37215885Sjulian					setenv("TERM", p, 1);
37315885Sjulian				if (chdir(pwd->pw_dir) < 0)
37415885Sjulian					errx(1, "no directory");
37515885Sjulian			}
37615885Sjulian			if (asthem || pwd->pw_uid)
37715885Sjulian				setenv("USER", pwd->pw_name, 1);
37815885Sjulian			setenv("HOME", pwd->pw_dir, 1);
37917254Sjulian			setenv("SHELL", shell, 1);
38017967Sjulian		}
38115885Sjulian		login_close(lc);
38215885Sjulian
38317921Sjulian		if (iscsh == YES) {
38417921Sjulian			if (fastlogin)
38517921Sjulian				*np.a-- = "-f";
38615885Sjulian			if (asme)
38717921Sjulian				*np.a-- = "-m";
38817921Sjulian		}
38917921Sjulian		/* csh strips the first character... */
39017921Sjulian		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
39117921Sjulian
39217921Sjulian		if (ruid != 0)
39317921Sjulian			syslog(LOG_NOTICE, "%s to %s%s", username, user,
39417921Sjulian			    ontty());
39515885Sjulian
39615885Sjulian		execv(shell, np.b);
39717254Sjulian		err(1, "%s", shell);
39815885Sjulian	}
39915885Sjulian}
40015885Sjulian
40115885Sjulianstatic int
40217254Sjulianexport_pam_environment(void)
40317964Sjulian{
40417254Sjulian	char	**pp;
40517254Sjulian
40617254Sjulian	for (pp = environ_pam; *pp != NULL; pp++) {
40717254Sjulian		if (ok_to_export(*pp))
40817254Sjulian			putenv(*pp);
40917964Sjulian		free(*pp);
41017254Sjulian	}
41115885Sjulian	return PAM_SUCCESS;
41215885Sjulian}
41315885Sjulian
41415885Sjulian/*
41515885Sjulian * Sanity checks on PAM environmental variables:
41615885Sjulian * - Make sure there is an '=' in the string.
41715885Sjulian * - Make sure the string doesn't run on too long.
41815885Sjulian * - Do not export certain variables.  This list was taken from the
41915885Sjulian *   Solaris pam_putenv(3) man page.
42015885Sjulian */
42115885Sjulianstatic int
42217254Sjulianok_to_export(const char *s)
42317254Sjulian{
42417254Sjulian	static const char *noexport[] = {
42517921Sjulian		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
42617921Sjulian		"IFS", "PATH", NULL
42717921Sjulian	};
42817921Sjulian	const char **pp;
42917254Sjulian	size_t n;
43015885Sjulian
43117921Sjulian	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
43217921Sjulian		return 0;
43317921Sjulian	if (strncmp(s, "LD_", 3) == 0)
43417921Sjulian		return 0;
43517921Sjulian	for (pp = noexport; *pp != NULL; pp++) {
43617921Sjulian		n = strlen(*pp);
43717921Sjulian		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
43817921Sjulian			return 0;
43917921Sjulian	}
44017921Sjulian	return 1;
44115885Sjulian}
44215885Sjulian
44315885Sjulianstatic void
44415885Sjulianusage(void)
44517921Sjulian{
44617921Sjulian
44717921Sjulian	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
44817921Sjulian	exit(1);
44917921Sjulian}
45015885Sjulian
45115885Sjulianstatic int
45215885Sjulianchshell(char *sh)
45315885Sjulian{
45415885Sjulian	int r;
45515885Sjulian	char *cp;
45617921Sjulian
45717921Sjulian	r = 0;
45817921Sjulian	setusershell();
45917921Sjulian	do {
46017921Sjulian		cp = getusershell();
46115885Sjulian		r = strcmp(cp, sh);
46215885Sjulian	} while (!r && cp != NULL);
46315885Sjulian	endusershell();
46415885Sjulian	return r;
46515885Sjulian}
46618005Sjulian
46715885Sjulianstatic char *
46815885Sjulianontty(void)
46917921Sjulian{
47017921Sjulian	char *p;
47117921Sjulian	static char buf[MAXPATHLEN + 4];
47215885Sjulian
47315885Sjulian	buf[0] = 0;
47415885Sjulian	p = ttyname(STDERR_FILENO);
47517921Sjulian	if (p)
47617921Sjulian		snprintf(buf, sizeof(buf), " on %s", p);
47717921Sjulian	return buf;
47817921Sjulian}
47915885Sjulian