su.c revision 91745
1167465Smp/*
259243Sobrien * Copyright (c) 1988, 1993, 1994
359243Sobrien *	The Regents of the University of California.  All rights reserved.
459243Sobrien *
559243Sobrien * Redistribution and use in source and binary forms, with or without
659243Sobrien * modification, are permitted provided that the following conditions
759243Sobrien * are met:
859243Sobrien * 1. Redistributions of source code must retain the above copyright
959243Sobrien *    notice, this list of conditions and the following disclaimer.
1059243Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1159243Sobrien *    notice, this list of conditions and the following disclaimer in the
1259243Sobrien *    documentation and/or other materials provided with the distribution.
1359243Sobrien * 3. All advertising materials mentioning features or use of this software
1459243Sobrien *    must display the following acknowledgement:
1559243Sobrien *	This product includes software developed by the University of
1659243Sobrien *	California, Berkeley and its contributors.
17100616Smp * 4. Neither the name of the University nor the names of its contributors
1859243Sobrien *    may be used to endorse or promote products derived from this software
1959243Sobrien *    without specific prior written permission.
2059243Sobrien *
2159243Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2259243Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2359243Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2459243Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2559243Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2659243Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2759243Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2859243Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2959243Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3059243Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3159243Sobrien * SUCH DAMAGE.
3259243Sobrien */
3359243Sobrien
3459243Sobrien#ifndef lint
3559243Sobrienstatic const char copyright[] =
3659243Sobrien"@(#) Copyright (c) 1988, 1993, 1994\n\
3759243Sobrien	The Regents of the University of California.  All rights reserved.\n";
3859243Sobrien#endif /* not lint */
3959243Sobrien
4059243Sobrien#ifndef lint
4159243Sobrien#if 0
42167465Smpstatic char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
4359243Sobrien#endif
4459243Sobrienstatic const char rcsid[] =
4559243Sobrien  "$FreeBSD: head/usr.bin/su/su.c 91745 2002-03-06 12:46:56Z des $";
4659243Sobrien#endif /* not lint */
4759243Sobrien
48145479Smp#include <sys/param.h>
49145479Smp#include <sys/time.h>
5059243Sobrien#include <sys/resource.h>
5159243Sobrien#include <sys/wait.h>
5259243Sobrien
5359243Sobrien#include <err.h>
5459243Sobrien#include <errno.h>
5559243Sobrien#include <grp.h>
5659243Sobrien#include <libutil.h>
5759243Sobrien#include <login_cap.h>
5859243Sobrien#include <paths.h>
5959243Sobrien#include <pwd.h>
6059243Sobrien#include <signal.h>
6159243Sobrien#include <stdio.h>
6259243Sobrien#include <stdlib.h>
6359243Sobrien#include <string.h>
6459243Sobrien#include <syslog.h>
6559243Sobrien#include <unistd.h>
6659243Sobrien
6759243Sobrien#include <security/pam_appl.h>
6859243Sobrien#include <security/openpam.h>
6959243Sobrien
7059243Sobrien#define PAM_END() do {						\
7159243Sobrien	int local_ret;						\
7259243Sobrien	if (pamh != NULL && creds_set) {			\
7359243Sobrien		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);	\
7459243Sobrien		if (local_ret != PAM_SUCCESS)			\
7559243Sobrien			syslog(LOG_ERR, "pam_setcred: %s",	\
7659243Sobrien				pam_strerror(pamh, local_ret));	\
7759243Sobrien		local_ret = pam_end(pamh, local_ret);		\
7859243Sobrien		if (local_ret != PAM_SUCCESS)			\
7959243Sobrien			syslog(LOG_ERR, "pam_end: %s",		\
8059243Sobrien				pam_strerror(pamh, local_ret));	\
8159243Sobrien	}							\
8259243Sobrien} while (0)
8359243Sobrien
8459243Sobrien
85167465Smp#define PAM_SET_ITEM(what, item) do {				\
86167465Smp	int local_ret;						\
8759243Sobrien	local_ret = pam_set_item(pamh, what, item);		\
8859243Sobrien	if (local_ret != PAM_SUCCESS) {				\
8959243Sobrien		syslog(LOG_ERR, "pam_set_item(" #what "): %s",	\
9059243Sobrien			pam_strerror(pamh, local_ret));		\
9159243Sobrien		errx(1, "pam_set_item(" #what "): %s",		\
9259243Sobrien			pam_strerror(pamh, local_ret));		\
93145479Smp	}							\
9459243Sobrien} while (0)
9559243Sobrien
9659243Sobrienenum tristate { UNSET, YES, NO };
9759243Sobrien
9859243Sobrienstatic pam_handle_t *pamh = NULL;
9959243Sobrienstatic int	creds_set = 0;
10059243Sobrienstatic char	**environ_pam;
10159243Sobrien
10259243Sobrienstatic char	*ontty(void);
103167465Smpstatic int	chshell(char *);
104145479Smpstatic void	usage(void);
105145479Smpstatic int	export_pam_environment(void);
106145479Smpstatic int	ok_to_export(const char *);
107145479Smp
10859243Sobrienextern char	**environ;
10959243Sobrien
110145479Smpint
11159243Sobrienmain(int argc, char *argv[])
11269408Sache{
11359243Sobrien	struct passwd	*pwd;
11459243Sobrien	struct pam_conv	conv = { openpam_ttyconv, NULL };
11559243Sobrien	enum tristate	iscsh;
11659243Sobrien	login_cap_t	*lc;
11759243Sobrien	union {
11859243Sobrien		const char	**a;
11959243Sobrien		char		* const *b;
12059243Sobrien	} 		np;
12159243Sobrien	uid_t		ruid;
12259243Sobrien	gid_t		gid;
123100616Smp	int		asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
12459243Sobrien			statusp, child_pid, child_pgrp, ret_pid;
125100616Smp	char		*username, *cleanenv, *class, shellbuf[MAXPATHLEN];
126100616Smp	const char	*p, *user, *shell, *mytty, **nargv;
12759243Sobrien
12859243Sobrien	shell = class = cleanenv = NULL;
12959243Sobrien	asme = asthem = fastlogin = statusp = 0;
13059243Sobrien	user = "root";
13159243Sobrien	iscsh = UNSET;
13259243Sobrien
13359243Sobrien	while ((ch = getopt(argc, argv, "-flmc:")) != -1)
13459243Sobrien		switch ((char)ch) {
13559243Sobrien		case 'f':
136145479Smp			fastlogin = 1;
13759243Sobrien			break;
138167465Smp		case '-':
13959243Sobrien		case 'l':
140145479Smp			asme = 0;
14159243Sobrien			asthem = 1;
14259243Sobrien			break;
14359243Sobrien		case 'm':
144167465Smp			asme = 1;
14583098Smp			asthem = 0;
146167465Smp			break;
14783098Smp		case 'c':
148167465Smp			class = optarg;
14983098Smp			break;
150167465Smp		case '?':
151167465Smp		default:
15259243Sobrien			usage();
153167465Smp		}
15459243Sobrien
155167465Smp	if (optind < argc)
156167465Smp		user = argv[optind++];
157167465Smp
158167465Smp	if (user == NULL)
15959243Sobrien		usage();
160167465Smp
16159243Sobrien	if (strlen(user) > MAXLOGNAME - 1)
16259243Sobrien		errx(1, "username too long");
163167465Smp
16459243Sobrien	nargv = malloc(sizeof(char *) * (argc + 4));
165167465Smp	if (nargv == NULL)
166167465Smp		errx(1, "malloc failure");
167167465Smp
168167465Smp	nargv[argc + 3] = NULL;
169167465Smp	for (i = argc; i >= optind; i--)
170167465Smp		nargv[i + 3] = argv[i];
171145479Smp	np.a = &nargv[i + 3];
17259243Sobrien
173145479Smp	argv += optind;
17459243Sobrien
175145479Smp	errno = 0;
176167465Smp	prio = getpriority(PRIO_PROCESS, 0);
177145479Smp	if (errno)
178131962Smp		prio = 0;
179167465Smp
18059243Sobrien	setpriority(PRIO_PROCESS, 0, -2);
18169408Sache	openlog("su", LOG_CONS, LOG_AUTH);
18259243Sobrien
18369408Sache	/* get current login name, real uid and shell */
18459243Sobrien	ruid = getuid();
18559243Sobrien	username = getlogin();
18659243Sobrien	pwd = getpwnam(username);
18759243Sobrien	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
18859243Sobrien		pwd = getpwuid(ruid);
18959243Sobrien	if (pwd == NULL)
19059243Sobrien		errx(1, "who are you?");
19159243Sobrien	gid = pwd->pw_gid;
19259243Sobrien
19359243Sobrien	username = strdup(pwd->pw_name);
19459243Sobrien	if (username == NULL)
19559243Sobrien		err(1, "strdup failure");
19659243Sobrien
197167465Smp	if (asme) {
198145479Smp		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
199145479Smp			/* must copy - pwd memory is recycled */
20059243Sobrien			shell = strncpy(shellbuf, pwd->pw_shell,
20159243Sobrien			    sizeof(shellbuf));
20259243Sobrien			shellbuf[sizeof(shellbuf) - 1] = '\0';
20359243Sobrien		}
20459243Sobrien		else {
20559243Sobrien			shell = _PATH_BSHELL;
20659243Sobrien			iscsh = NO;
20759243Sobrien		}
20859243Sobrien	}
20959243Sobrien
21059243Sobrien	/* Do the whole PAM startup thing */
21159243Sobrien	retcode = pam_start("su", user, &conv, &pamh);
21259243Sobrien	if (retcode != PAM_SUCCESS) {
21359243Sobrien		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
21459243Sobrien		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
21559243Sobrien	}
21659243Sobrien
21759243Sobrien	PAM_SET_ITEM(PAM_RUSER, getlogin());
21859243Sobrien
21959243Sobrien	mytty = ttyname(STDERR_FILENO);
22059243Sobrien	if (!mytty)
22159243Sobrien		mytty = "tty";
22259243Sobrien	PAM_SET_ITEM(PAM_TTY, mytty);
22359243Sobrien
22459243Sobrien	retcode = pam_authenticate(pamh, 0);
225167465Smp	if (retcode != PAM_SUCCESS) {
226167465Smp		syslog(LOG_ERR, "pam_authenticate: %s",
22759243Sobrien		    pam_strerror(pamh, retcode));
22859243Sobrien		errx(1, "Sorry");
229167465Smp	}
23059243Sobrien	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
23159243Sobrien	if (retcode == PAM_SUCCESS)
232131962Smp		user = p;
233131962Smp	else
234131962Smp		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
235131962Smp		    pam_strerror(pamh, retcode));
23659243Sobrien
23759243Sobrien	retcode = pam_acct_mgmt(pamh, 0);
23859243Sobrien	if (retcode == PAM_NEW_AUTHTOK_REQD) {
23959243Sobrien		retcode = pam_chauthtok(pamh,
24059243Sobrien			PAM_CHANGE_EXPIRED_AUTHTOK);
24159243Sobrien		if (retcode != PAM_SUCCESS) {
24259243Sobrien			syslog(LOG_ERR, "pam_chauthtok: %s",
24369408Sache			    pam_strerror(pamh, retcode));
24459243Sobrien			errx(1, "Sorry");
24559243Sobrien		}
24659243Sobrien	}
24759243Sobrien	if (retcode != PAM_SUCCESS) {
24859243Sobrien		syslog(LOG_ERR, "pam_acct_mgmt: %s",
24969408Sache			pam_strerror(pamh, retcode));
25059243Sobrien		errx(1, "Sorry");
25159243Sobrien	}
25259243Sobrien
25359243Sobrien	/* get target login information, default to root */
25459243Sobrien	pwd = getpwnam(user);
25559243Sobrien	if (pwd == NULL)
25659243Sobrien		errx(1, "unknown login: %s", user);
25759243Sobrien	if (class == NULL)
25859243Sobrien		lc = login_getpwclass(pwd);
25959243Sobrien	else {
26059243Sobrien		if (ruid != 0)
26159243Sobrien			errx(1, "only root may use -c");
26259243Sobrien		lc = login_getclass(class);
26359243Sobrien		if (lc == NULL)
26459243Sobrien			errx(1, "unknown class: %s", class);
26559243Sobrien	}
26659243Sobrien
26759243Sobrien	/* if asme and non-standard target shell, must be root */
26859243Sobrien	if (asme) {
26959243Sobrien		if (ruid != 0 && !chshell(pwd->pw_shell))
27059243Sobrien			errx(1, "permission denied (shell).");
27159243Sobrien	}
27259243Sobrien	else if (pwd->pw_shell && *pwd->pw_shell) {
27359243Sobrien		shell = pwd->pw_shell;
27459243Sobrien		iscsh = UNSET;
27559243Sobrien	}
27659243Sobrien	else {
27759243Sobrien		shell = _PATH_BSHELL;
27859243Sobrien		iscsh = NO;
27959243Sobrien	}
28059243Sobrien
281167465Smp	/* if we're forking a csh, we want to slightly muck the args */
28259243Sobrien	if (iscsh == UNSET) {
28359243Sobrien		p = strrchr(shell, '/');
28459243Sobrien		if (p)
28559243Sobrien			++p;
28659243Sobrien		else
28759243Sobrien			p = shell;
28859243Sobrien		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
28959243Sobrien	}
29059243Sobrien	setpriority(PRIO_PROCESS, 0, prio);
29159243Sobrien
29259243Sobrien	/*
29359243Sobrien	 * PAM modules might add supplementary groups in pam_setcred(), so
29459243Sobrien	 * initialize them first.
29559243Sobrien	 */
29659243Sobrien	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
29759243Sobrien		err(1, "setusercontext");
29859243Sobrien
29959243Sobrien	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
30059243Sobrien	if (retcode != PAM_SUCCESS)
30159243Sobrien		syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
30259243Sobrien		    pam_strerror(pamh, retcode));
30359243Sobrien	else
30459243Sobrien		creds_set = 1;
30559243Sobrien
30659243Sobrien	/*
30759243Sobrien	 * We must fork() before setuid() because we need to call
308167465Smp	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
309167465Smp	 */
31059243Sobrien
31159243Sobrien	statusp = 1;
31259243Sobrien	child_pid = fork();
31359243Sobrien	switch (child_pid) {
31459243Sobrien	default:
31559243Sobrien		while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
316167465Smp			if (WIFSTOPPED(statusp)) {
317167465Smp				child_pgrp = tcgetpgrp(1);
31859243Sobrien				kill(getpid(), SIGSTOP);
31959243Sobrien				tcsetpgrp(1, child_pgrp);
32059243Sobrien				kill(child_pid, SIGCONT);
32159243Sobrien				statusp = 1;
322167465Smp				continue;
32359243Sobrien			}
32459243Sobrien			break;
32559243Sobrien		}
32659243Sobrien		if (ret_pid == -1)
32759243Sobrien			err(1, "waitpid");
32859243Sobrien		PAM_END();
32959243Sobrien		exit(statusp);
33059243Sobrien	case -1:
33159243Sobrien		err(1, "fork");
33259243Sobrien		PAM_END();
33359243Sobrien		exit(1);
33459243Sobrien	case 0:
33559243Sobrien		/*
33659243Sobrien		 * Set all user context except for: Environmental variables
33759243Sobrien		 * Umask Login records (wtmp, etc) Path
33859243Sobrien		 */
33959243Sobrien		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
34059243Sobrien			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP);
34159243Sobrien		/*
34259243Sobrien		 * Don't touch resource/priority settings if -m has been used
34359243Sobrien		 * or -l and -c hasn't, and we're not su'ing to root.
34459243Sobrien		 */
345167465Smp		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
34659243Sobrien			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
347145479Smp		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
34859243Sobrien			err(1, "setusercontext");
34959243Sobrien
35059243Sobrien		if (!asme) {
35159243Sobrien			if (asthem) {
35259243Sobrien				p = getenv("TERM");
35359243Sobrien				environ = &cleanenv;
35459243Sobrien
35559243Sobrien				/*
35659243Sobrien				 * Add any environmental variables that the
35759243Sobrien				 * PAM modules may have set.
35859243Sobrien				 */
35959243Sobrien				environ_pam = pam_getenvlist(pamh);
36059243Sobrien				if (environ_pam)
36159243Sobrien					export_pam_environment();
36259243Sobrien
36359243Sobrien				/* set the su'd user's environment & umask */
36459243Sobrien				setusercontext(lc, pwd, pwd->pw_uid,
36559243Sobrien					LOGIN_SETPATH | LOGIN_SETUMASK |
36659243Sobrien					LOGIN_SETENV);
36759243Sobrien				if (p)
36859243Sobrien					setenv("TERM", p, 1);
36959243Sobrien				if (chdir(pwd->pw_dir) < 0)
37059243Sobrien					errx(1, "no directory");
37159243Sobrien			}
37259243Sobrien			if (asthem || pwd->pw_uid)
37359243Sobrien				setenv("USER", pwd->pw_name, 1);
37459243Sobrien			setenv("HOME", pwd->pw_dir, 1);
37559243Sobrien			setenv("SHELL", shell, 1);
37659243Sobrien		}
37759243Sobrien		login_close(lc);
37859243Sobrien
37959243Sobrien		if (iscsh == YES) {
38059243Sobrien			if (fastlogin)
38159243Sobrien				*np.a-- = "-f";
38259243Sobrien			if (asme)
383167465Smp				*np.a-- = "-m";
384167465Smp		}
385167465Smp		/* csh strips the first character... */
38659243Sobrien		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
38759243Sobrien
38859243Sobrien		if (ruid != 0)
38959243Sobrien			syslog(LOG_NOTICE, "%s to %s%s", username, user,
39059243Sobrien			    ontty());
39159243Sobrien
39259243Sobrien		execv(shell, np.b);
39359243Sobrien		err(1, "%s", shell);
39459243Sobrien	}
39559243Sobrien}
39659243Sobrien
39759243Sobrienstatic int
39859243Sobrienexport_pam_environment(void)
39959243Sobrien{
40059243Sobrien	char	**pp;
40159243Sobrien
40259243Sobrien	for (pp = environ_pam; *pp != NULL; pp++) {
40359243Sobrien		if (ok_to_export(*pp))
40459243Sobrien			putenv(*pp);
40559243Sobrien		free(*pp);
40659243Sobrien	}
40759243Sobrien	return PAM_SUCCESS;
40859243Sobrien}
409167465Smp
41059243Sobrien/*
411167465Smp * Sanity checks on PAM environmental variables:
41259243Sobrien * - Make sure there is an '=' in the string.
41359243Sobrien * - Make sure the string doesn't run on too long.
414167465Smp * - Do not export certain variables.  This list was taken from the
41559243Sobrien *   Solaris pam_putenv(3) man page.
41659243Sobrien */
41759243Sobrienstatic int
41859243Sobrienok_to_export(const char *s)
41959243Sobrien{
42059243Sobrien	static const char *noexport[] = {
42159243Sobrien		"SHELL", "HOME", "LOGNAME", "MAIL", "CDPATH",
42259243Sobrien		"IFS", "PATH", NULL
42359243Sobrien	};
42459243Sobrien	const char **pp;
42559243Sobrien	size_t n;
42659243Sobrien
42759243Sobrien	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
42859243Sobrien		return 0;
42959243Sobrien	if (strncmp(s, "LD_", 3) == 0)
43059243Sobrien		return 0;
43159243Sobrien	for (pp = noexport; *pp != NULL; pp++) {
43259243Sobrien		n = strlen(*pp);
43359243Sobrien		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
43459243Sobrien			return 0;
43559243Sobrien	}
43659243Sobrien	return 1;
43759243Sobrien}
43859243Sobrien
43959243Sobrienstatic void
44059243Sobrienusage(void)
44159243Sobrien{
44259243Sobrien
44359243Sobrien	fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n");
44459243Sobrien	exit(1);
44559243Sobrien}
44659243Sobrien
44759243Sobrienstatic int
448167465Smpchshell(char *sh)
44959243Sobrien{
450167465Smp	int r;
45159243Sobrien	char *cp;
45259243Sobrien
45359243Sobrien	r = 0;
454167465Smp	setusershell();
45559243Sobrien	do {
45659243Sobrien		cp = getusershell();
45759243Sobrien		r = strcmp(cp, sh);
45859243Sobrien	} while (!r && cp != NULL);
45959243Sobrien	endusershell();
460172665Smp	return r;
46159243Sobrien}
46259243Sobrien
46359243Sobrienstatic char *
46459243Sobrienontty(void)
465184072Sru{
46659243Sobrien	char *p;
46759243Sobrien	static char buf[MAXPATHLEN + 4];
468167465Smp
46959243Sobrien	buf[0] = 0;
47059243Sobrien	p = ttyname(STDERR_FILENO);
47159243Sobrien	if (p)
47259243Sobrien		snprintf(buf, sizeof(buf), " on %s", p);
47359243Sobrien	return buf;
47459243Sobrien}
47559243Sobrien