su.c revision 124166
1134411Ssimon/*
2134411Ssimon * Copyright (c) 1988, 1993, 1994
3134411Ssimon *	The Regents of the University of California.  All rights reserved.
4134411Ssimon * Copyright (c) 2002 Networks Associates Technologies, Inc.
5134411Ssimon * All rights reserved.
6134411Ssimon *
7134411Ssimon * Portions of this software were developed for the FreeBSD Project by
8134411Ssimon * ThinkSec AS and NAI Labs, the Security Research Division of Network
9134411Ssimon * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10134411Ssimon * ("CBOSS"), as part of the DARPA CHATS research program.
11134411Ssimon *
12134411Ssimon * Redistribution and use in source and binary forms, with or without
13134411Ssimon * modification, are permitted provided that the following conditions
14134411Ssimon * are met:
15134411Ssimon * 1. Redistributions of source code must retain the above copyright
16134411Ssimon *    notice, this list of conditions and the following disclaimer.
17134411Ssimon * 2. Redistributions in binary form must reproduce the above copyright
18134411Ssimon *    notice, this list of conditions and the following disclaimer in the
19134411Ssimon *    documentation and/or other materials provided with the distribution.
20134411Ssimon * 3. All advertising materials mentioning features or use of this software
21134411Ssimon *    must display the following acknowledgement:
22134411Ssimon *	This product includes software developed by the University of
23134411Ssimon *	California, Berkeley and its contributors.
24134411Ssimon * 4. Neither the name of the University nor the names of its contributors
25134411Ssimon *    may be used to endorse or promote products derived from this software
26134411Ssimon *    without specific prior written permission.
27152895Sjoel *
28134411Ssimon * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29134411Ssimon * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30134411Ssimon * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31134411Ssimon * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32134411Ssimon * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33134411Ssimon * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34134938Sru * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35134411Ssimon * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36134411Ssimon * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37134411Ssimon * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38134411Ssimon * SUCH DAMAGE.
39134411Ssimon */
40134411Ssimon
41134411Ssimon#ifndef lint
42134411Ssimonstatic const char copyright[] =
43134411Ssimon"@(#) Copyright (c) 1988, 1993, 1994\n\
44134411Ssimon	The Regents of the University of California.  All rights reserved.\n";
45146489Sbrueffer#endif /* not lint */
46134411Ssimon
47134411Ssimon#ifndef lint
48134411Ssimon#if 0
49134411Ssimonstatic char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
50134411Ssimon#endif
51134411Ssimonstatic const char rcsid[] =
52134411Ssimon  "$FreeBSD: head/usr.bin/su/su.c 124166 2004-01-06 09:47:24Z des $";
53134411Ssimon#endif /* not lint */
54134411Ssimon
55134411Ssimon#include <sys/param.h>
56134411Ssimon#include <sys/time.h>
57134411Ssimon#include <sys/resource.h>
58134411Ssimon#include <sys/wait.h>
59134411Ssimon
60134411Ssimon#include <err.h>
61134411Ssimon#include <errno.h>
62134411Ssimon#include <grp.h>
63152895Sjoel#include <libutil.h>
64152895Sjoel#include <login_cap.h>
65134411Ssimon#include <paths.h>
66134411Ssimon#include <pwd.h>
67#include <signal.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
71#include <syslog.h>
72#include <unistd.h>
73
74#include <security/pam_appl.h>
75#include <security/openpam.h>
76
77#define PAM_END() do {							\
78	int local_ret;							\
79	if (pamh != NULL) {						\
80		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);		\
81		if (local_ret != PAM_SUCCESS)				\
82			syslog(LOG_ERR, "pam_setcred: %s",		\
83				pam_strerror(pamh, local_ret));		\
84		if (asthem) {						\
85			local_ret = pam_close_session(pamh, 0);		\
86			if (local_ret != PAM_SUCCESS)			\
87				syslog(LOG_ERR, "pam_close_session: %s",\
88					pam_strerror(pamh, local_ret));	\
89		}							\
90		local_ret = pam_end(pamh, local_ret);			\
91		if (local_ret != PAM_SUCCESS)				\
92			syslog(LOG_ERR, "pam_end: %s",			\
93				pam_strerror(pamh, local_ret));		\
94	}								\
95} while (0)
96
97
98#define PAM_SET_ITEM(what, item) do {					\
99	int local_ret;							\
100	local_ret = pam_set_item(pamh, what, item);			\
101	if (local_ret != PAM_SUCCESS) {					\
102		syslog(LOG_ERR, "pam_set_item(" #what "): %s",		\
103			pam_strerror(pamh, local_ret));			\
104		errx(1, "pam_set_item(" #what "): %s",			\
105			pam_strerror(pamh, local_ret));			\
106	}								\
107} while (0)
108
109enum tristate { UNSET, YES, NO };
110
111static pam_handle_t *pamh = NULL;
112static char	**environ_pam;
113
114static char	*ontty(void);
115static int	chshell(char *);
116static void	usage(void);
117static int	export_pam_environment(void);
118static int	ok_to_export(const char *);
119
120extern char	**environ;
121
122int
123main(int argc, char *argv[])
124{
125	struct passwd	*pwd;
126	struct pam_conv	conv = { openpam_ttyconv, NULL };
127	enum tristate	iscsh;
128	login_cap_t	*lc;
129	union {
130		const char	**a;
131		char		* const *b;
132	}		np;
133	uid_t		ruid;
134	pid_t		child_pid, child_pgrp, pid;
135	int		asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
136			statusp, setmaclabel;
137	char		*username, *cleanenv, *class, shellbuf[MAXPATHLEN];
138	const char	*p, *user, *shell, *mytty, **nargv;
139	struct sigaction sa, sa_int, sa_quit, sa_pipe;
140	int temp, fds[2];
141
142	shell = class = cleanenv = NULL;
143	asme = asthem = fastlogin = statusp = 0;
144	user = "root";
145	iscsh = UNSET;
146	setmaclabel = 0;
147
148	while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
149		switch ((char)ch) {
150		case 'f':
151			fastlogin = 1;
152			break;
153		case '-':
154		case 'l':
155			asme = 0;
156			asthem = 1;
157			break;
158		case 'm':
159			asme = 1;
160			asthem = 0;
161			break;
162		case 's':
163			setmaclabel = 1;
164			break;
165		case 'c':
166			class = optarg;
167			break;
168		case '?':
169		default:
170			usage();
171		}
172
173	if (optind < argc)
174		user = argv[optind++];
175
176	if (user == NULL)
177		usage();
178
179	if (strlen(user) > MAXLOGNAME - 1)
180		errx(1, "username too long");
181
182	nargv = malloc(sizeof(char *) * (argc + 4));
183	if (nargv == NULL)
184		errx(1, "malloc failure");
185
186	nargv[argc + 3] = NULL;
187	for (i = argc; i >= optind; i--)
188		nargv[i + 3] = argv[i];
189	np.a = &nargv[i + 3];
190
191	argv += optind;
192
193	errno = 0;
194	prio = getpriority(PRIO_PROCESS, 0);
195	if (errno)
196		prio = 0;
197
198	setpriority(PRIO_PROCESS, 0, -2);
199	openlog("su", LOG_CONS, LOG_AUTH);
200
201	/* get current login name, real uid and shell */
202	ruid = getuid();
203	username = getlogin();
204	pwd = getpwnam(username);
205	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
206		pwd = getpwuid(ruid);
207	if (pwd == NULL)
208		errx(1, "who are you?");
209
210	username = strdup(pwd->pw_name);
211	if (username == NULL)
212		err(1, "strdup failure");
213
214	if (asme) {
215		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
216			/* must copy - pwd memory is recycled */
217			shell = strncpy(shellbuf, pwd->pw_shell,
218			    sizeof(shellbuf));
219			shellbuf[sizeof(shellbuf) - 1] = '\0';
220		}
221		else {
222			shell = _PATH_BSHELL;
223			iscsh = NO;
224		}
225	}
226
227	/* Do the whole PAM startup thing */
228	retcode = pam_start("su", user, &conv, &pamh);
229	if (retcode != PAM_SUCCESS) {
230		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
231		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
232	}
233
234	PAM_SET_ITEM(PAM_RUSER, username);
235
236	mytty = ttyname(STDERR_FILENO);
237	if (!mytty)
238		mytty = "tty";
239	PAM_SET_ITEM(PAM_TTY, mytty);
240
241	retcode = pam_authenticate(pamh, 0);
242	if (retcode != PAM_SUCCESS) {
243#if 0
244		syslog(LOG_ERR, "pam_authenticate: %s",
245		    pam_strerror(pamh, retcode));
246#endif
247		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
248		    username, user, mytty);
249		errx(1, "Sorry");
250	}
251	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
252	if (retcode == PAM_SUCCESS)
253		user = p;
254	else
255		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
256		    pam_strerror(pamh, retcode));
257	pwd = getpwnam(user);
258	if (pwd == NULL)
259		errx(1, "unknown login: %s", user);
260
261	retcode = pam_acct_mgmt(pamh, 0);
262	if (retcode == PAM_NEW_AUTHTOK_REQD) {
263		retcode = pam_chauthtok(pamh,
264			PAM_CHANGE_EXPIRED_AUTHTOK);
265		if (retcode != PAM_SUCCESS) {
266			syslog(LOG_ERR, "pam_chauthtok: %s",
267			    pam_strerror(pamh, retcode));
268			errx(1, "Sorry");
269		}
270	}
271	if (retcode != PAM_SUCCESS) {
272		syslog(LOG_ERR, "pam_acct_mgmt: %s",
273			pam_strerror(pamh, retcode));
274		errx(1, "Sorry");
275	}
276
277	/* get target login information */
278	if (class == NULL)
279		lc = login_getpwclass(pwd);
280	else {
281		if (ruid != 0)
282			errx(1, "only root may use -c");
283		lc = login_getclass(class);
284		if (lc == NULL)
285			errx(1, "unknown class: %s", class);
286	}
287
288	/* if asme and non-standard target shell, must be root */
289	if (asme) {
290		if (ruid != 0 && !chshell(pwd->pw_shell))
291			errx(1, "permission denied (shell).");
292	}
293	else if (pwd->pw_shell && *pwd->pw_shell) {
294		shell = pwd->pw_shell;
295		iscsh = UNSET;
296	}
297	else {
298		shell = _PATH_BSHELL;
299		iscsh = NO;
300	}
301
302	/* if we're forking a csh, we want to slightly muck the args */
303	if (iscsh == UNSET) {
304		p = strrchr(shell, '/');
305		if (p)
306			++p;
307		else
308			p = shell;
309		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
310	}
311	setpriority(PRIO_PROCESS, 0, prio);
312
313	/* Switch to home directory */
314	if (asthem) {
315		if (chdir(pwd->pw_dir) < 0)
316			errx(1, "no directory");
317	}
318
319	/*
320	 * PAM modules might add supplementary groups in pam_setcred(), so
321	 * initialize them first.
322	 */
323	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
324		err(1, "setusercontext");
325
326	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
327	if (retcode != PAM_SUCCESS) {
328		syslog(LOG_ERR, "pam_setcred: %s",
329		    pam_strerror(pamh, retcode));
330		errx(1, "failed to establish credentials.");
331	}
332	if (asthem) {
333		retcode = pam_open_session(pamh, 0);
334		if (retcode != PAM_SUCCESS) {
335			syslog(LOG_ERR, "pam_open_session: %s",
336			    pam_strerror(pamh, retcode));
337			errx(1, "failed to open session.");
338		}
339	}
340
341	/*
342	 * We must fork() before setuid() because we need to call
343	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
344	 */
345	sa.sa_flags = SA_RESTART;
346	sa.sa_handler = SIG_IGN;
347	sigemptyset(&sa.sa_mask);
348	sigaction(SIGINT, &sa, &sa_int);
349	sigaction(SIGQUIT, &sa, &sa_quit);
350	sigaction(SIGPIPE, &sa, &sa_pipe);
351	sa.sa_handler = SIG_DFL;
352	sigaction(SIGTSTP, &sa, NULL);
353	statusp = 1;
354	if (pipe(fds) == -1) {
355		err(1, "pipe");
356		PAM_END();
357		exit(1);
358	}
359	child_pid = fork();
360	switch (child_pid) {
361	default:
362		sa.sa_handler = SIG_IGN;
363		sigaction(SIGTTOU, &sa, NULL);
364		close(fds[0]);
365		setpgid(child_pid, child_pid);
366		tcsetpgrp(STDERR_FILENO, child_pid);
367		close(fds[1]);
368		sigaction(SIGPIPE, &sa_pipe, NULL);
369		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
370			if (WIFSTOPPED(statusp)) {
371				kill(getpid(), SIGSTOP);
372				child_pgrp = getpgid(child_pid);
373				tcsetpgrp(1, child_pgrp);
374				kill(child_pid, SIGCONT);
375				statusp = 1;
376				continue;
377			}
378			break;
379		}
380		tcsetpgrp(STDERR_FILENO, getpgrp());
381		if (pid == -1)
382			err(1, "waitpid");
383		PAM_END();
384		exit(statusp);
385	case -1:
386		err(1, "fork");
387		PAM_END();
388		exit(1);
389	case 0:
390		close(fds[1]);
391		read(fds[0], &temp, 1);
392		close(fds[0]);
393		sigaction(SIGPIPE, &sa_pipe, NULL);
394		sigaction(SIGINT, &sa_int, NULL);
395		sigaction(SIGQUIT, &sa_quit, NULL);
396
397		/*
398		 * Set all user context except for: Environmental variables
399		 * Umask Login records (wtmp, etc) Path
400		 */
401		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
402			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
403			   LOGIN_SETMAC);
404		/*
405		 * If -s is present, also set the MAC label.
406		 */
407		if (setmaclabel)
408			setwhat |= LOGIN_SETMAC;
409		/*
410		 * Don't touch resource/priority settings if -m has been used
411		 * or -l and -c hasn't, and we're not su'ing to root.
412		 */
413		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
414			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
415		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
416			err(1, "setusercontext");
417
418		if (!asme) {
419			if (asthem) {
420				p = getenv("TERM");
421				environ = &cleanenv;
422			}
423
424			if (asthem || pwd->pw_uid)
425				setenv("USER", pwd->pw_name, 1);
426			setenv("HOME", pwd->pw_dir, 1);
427			setenv("SHELL", shell, 1);
428
429			if (asthem) {
430				/*
431				 * Add any environmental variables that the
432				 * PAM modules may have set.
433				 */
434				environ_pam = pam_getenvlist(pamh);
435				if (environ_pam)
436					export_pam_environment();
437
438				/* set the su'd user's environment & umask */
439				setusercontext(lc, pwd, pwd->pw_uid,
440					LOGIN_SETPATH | LOGIN_SETUMASK |
441					LOGIN_SETENV);
442				if (p)
443					setenv("TERM", p, 1);
444			}
445		}
446		login_close(lc);
447
448		if (iscsh == YES) {
449			if (fastlogin)
450				*np.a-- = "-f";
451			if (asme)
452				*np.a-- = "-m";
453		}
454		/* csh strips the first character... */
455		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
456
457		if (ruid != 0)
458			syslog(LOG_NOTICE, "%s to %s%s", username, user,
459			    ontty());
460
461		execv(shell, np.b);
462		err(1, "%s", shell);
463	}
464}
465
466static int
467export_pam_environment(void)
468{
469	char	**pp;
470
471	for (pp = environ_pam; *pp != NULL; pp++) {
472		if (ok_to_export(*pp))
473			putenv(*pp);
474		free(*pp);
475	}
476	return PAM_SUCCESS;
477}
478
479/*
480 * Sanity checks on PAM environmental variables:
481 * - Make sure there is an '=' in the string.
482 * - Make sure the string doesn't run on too long.
483 * - Do not export certain variables.  This list was taken from the
484 *   Solaris pam_putenv(3) man page.
485 * Note that if the user is chrooted, PAM may have a better idea than we
486 * do of where her home directory is.
487 */
488static int
489ok_to_export(const char *s)
490{
491	static const char *noexport[] = {
492		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
493		"IFS", "PATH", NULL
494	};
495	const char **pp;
496	size_t n;
497
498	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
499		return 0;
500	if (strncmp(s, "LD_", 3) == 0)
501		return 0;
502	for (pp = noexport; *pp != NULL; pp++) {
503		n = strlen(*pp);
504		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
505			return 0;
506	}
507	return 1;
508}
509
510static void
511usage(void)
512{
513
514	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
515	exit(1);
516}
517
518static int
519chshell(char *sh)
520{
521	int r;
522	char *cp;
523
524	r = 0;
525	setusershell();
526	while ((cp = getusershell()) != NULL && !r)
527	    r = (strcmp(cp, sh) == 0);
528	endusershell();
529	return r;
530}
531
532static char *
533ontty(void)
534{
535	char *p;
536	static char buf[MAXPATHLEN + 4];
537
538	buf[0] = 0;
539	p = ttyname(STDERR_FILENO);
540	if (p)
541		snprintf(buf, sizeof(buf), " on %s", p);
542	return buf;
543}
544