su.c revision 153966
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
64#ifndef lint
65static const char copyright[] =
66"@(#) Copyright (c) 1988, 1993, 1994\n\
67	The Regents of the University of California.  All rights reserved.\n";
68#endif /* not lint */
69
70#if 0
71#ifndef lint
72static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
73#endif /* not lint */
74#endif
75
76#include <sys/cdefs.h>
77__FBSDID("$FreeBSD: head/usr.bin/su/su.c 153966 2006-01-02 09:46:38Z brian $");
78
79#include <sys/param.h>
80#include <sys/time.h>
81#include <sys/resource.h>
82#include <sys/wait.h>
83
84#include <err.h>
85#include <errno.h>
86#include <grp.h>
87#include <login_cap.h>
88#include <paths.h>
89#include <pwd.h>
90#include <signal.h>
91#include <stdio.h>
92#include <stdlib.h>
93#include <string.h>
94#include <syslog.h>
95#include <unistd.h>
96
97#include <security/pam_appl.h>
98#include <security/openpam.h>
99
100#define PAM_END() do {							\
101	int local_ret;							\
102	if (pamh != NULL) {						\
103		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);		\
104		if (local_ret != PAM_SUCCESS)				\
105			syslog(LOG_ERR, "pam_setcred: %s",		\
106				pam_strerror(pamh, local_ret));		\
107		if (asthem) {						\
108			local_ret = pam_close_session(pamh, 0);		\
109			if (local_ret != PAM_SUCCESS)			\
110				syslog(LOG_ERR, "pam_close_session: %s",\
111					pam_strerror(pamh, local_ret));	\
112		}							\
113		local_ret = pam_end(pamh, local_ret);			\
114		if (local_ret != PAM_SUCCESS)				\
115			syslog(LOG_ERR, "pam_end: %s",			\
116				pam_strerror(pamh, local_ret));		\
117	}								\
118} while (0)
119
120
121#define PAM_SET_ITEM(what, item) do {					\
122	int local_ret;							\
123	local_ret = pam_set_item(pamh, what, item);			\
124	if (local_ret != PAM_SUCCESS) {					\
125		syslog(LOG_ERR, "pam_set_item(" #what "): %s",		\
126			pam_strerror(pamh, local_ret));			\
127		errx(1, "pam_set_item(" #what "): %s",			\
128			pam_strerror(pamh, local_ret));			\
129		/* NOTREACHED */					\
130	}								\
131} while (0)
132
133enum tristate { UNSET, YES, NO };
134
135static pam_handle_t *pamh = NULL;
136static char	**environ_pam;
137
138static char	*ontty(void);
139static int	chshell(const char *);
140static void	usage(void) __dead2;
141static void	export_pam_environment(void);
142static int	ok_to_export(const char *);
143
144extern char	**environ;
145
146int
147main(int argc, char *argv[])
148{
149	static char	*cleanenv;
150	struct passwd	*pwd;
151	struct pam_conv	conv = { openpam_ttyconv, NULL };
152	enum tristate	iscsh;
153	login_cap_t	*lc;
154	union {
155		const char	**a;
156		char		* const *b;
157	}		np;
158	uid_t		ruid;
159	pid_t		child_pid, pid;
160	int		asme, ch, asthem, fastlogin, prio, i, retcode,
161			statusp, setmaclabel;
162	u_int		setwhat;
163	char		*username, *class, shellbuf[MAXPATHLEN];
164	const char	*p, *user, *shell, *mytty, **nargv;
165	struct sigaction sa, sa_int, sa_quit, sa_pipe;
166	int temp, fds[2];
167
168	shell = class = cleanenv = NULL;
169	asme = asthem = fastlogin = statusp = 0;
170	user = "root";
171	iscsh = UNSET;
172	setmaclabel = 0;
173
174	while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
175		switch ((char)ch) {
176		case 'f':
177			fastlogin = 1;
178			break;
179		case '-':
180		case 'l':
181			asme = 0;
182			asthem = 1;
183			break;
184		case 'm':
185			asme = 1;
186			asthem = 0;
187			break;
188		case 's':
189			setmaclabel = 1;
190			break;
191		case 'c':
192			class = optarg;
193			break;
194		case '?':
195		default:
196			usage();
197		/* NOTREACHED */
198		}
199
200	if (optind < argc)
201		user = argv[optind++];
202
203	if (user == NULL)
204		usage();
205	/* NOTREACHED */
206
207	if (strlen(user) > MAXLOGNAME - 1)
208		errx(1, "username too long");
209
210	/*
211	 * Try to provide more helpful debugging output if su(1) is running
212	 * non-setuid, or was run from a file system not mounted setuid.
213	 */
214	if (geteuid() != 0)
215		errx(1, "not running setuid");
216
217	nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
218	if (nargv == NULL)
219		errx(1, "malloc failure");
220
221	nargv[argc + 3] = NULL;
222	for (i = argc; i >= optind; i--)
223		nargv[i + 3] = argv[i];
224	np.a = &nargv[i + 3];
225
226	argv += optind;
227
228	errno = 0;
229	prio = getpriority(PRIO_PROCESS, 0);
230	if (errno)
231		prio = 0;
232
233	setpriority(PRIO_PROCESS, 0, -2);
234	openlog("su", LOG_CONS, LOG_AUTH);
235
236	/* get current login name, real uid and shell */
237	ruid = getuid();
238	username = getlogin();
239	pwd = getpwnam(username);
240	if (username == NULL || pwd == NULL || pwd->pw_uid != ruid)
241		pwd = getpwuid(ruid);
242	if (pwd == NULL)
243		errx(1, "who are you?");
244
245	username = strdup(pwd->pw_name);
246	if (username == NULL)
247		err(1, "strdup failure");
248
249	if (asme) {
250		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
251			/* must copy - pwd memory is recycled */
252			shell = strncpy(shellbuf, pwd->pw_shell,
253			    sizeof(shellbuf));
254			shellbuf[sizeof(shellbuf) - 1] = '\0';
255		}
256		else {
257			shell = _PATH_BSHELL;
258			iscsh = NO;
259		}
260	}
261
262	/* Do the whole PAM startup thing */
263	retcode = pam_start("su", user, &conv, &pamh);
264	if (retcode != PAM_SUCCESS) {
265		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
266		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
267	}
268
269	PAM_SET_ITEM(PAM_RUSER, username);
270
271	mytty = ttyname(STDERR_FILENO);
272	if (!mytty)
273		mytty = "tty";
274	PAM_SET_ITEM(PAM_TTY, mytty);
275
276	retcode = pam_authenticate(pamh, 0);
277	if (retcode != PAM_SUCCESS) {
278		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
279		    username, user, mytty);
280		errx(1, "Sorry");
281	}
282	retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
283	if (retcode == PAM_SUCCESS)
284		user = p;
285	else
286		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
287		    pam_strerror(pamh, retcode));
288	pwd = getpwnam(user);
289	if (pwd == NULL)
290		errx(1, "unknown login: %s", user);
291
292	retcode = pam_acct_mgmt(pamh, 0);
293	if (retcode == PAM_NEW_AUTHTOK_REQD) {
294		retcode = pam_chauthtok(pamh,
295			PAM_CHANGE_EXPIRED_AUTHTOK);
296		if (retcode != PAM_SUCCESS) {
297			syslog(LOG_ERR, "pam_chauthtok: %s",
298			    pam_strerror(pamh, retcode));
299			errx(1, "Sorry");
300		}
301	}
302	if (retcode != PAM_SUCCESS) {
303		syslog(LOG_ERR, "pam_acct_mgmt: %s",
304			pam_strerror(pamh, retcode));
305		errx(1, "Sorry");
306	}
307
308	/* get target login information */
309	if (class == NULL)
310		lc = login_getpwclass(pwd);
311	else {
312		if (ruid != 0)
313			errx(1, "only root may use -c");
314		lc = login_getclass(class);
315		if (lc == NULL)
316			errx(1, "unknown class: %s", class);
317	}
318
319	/* if asme and non-standard target shell, must be root */
320	if (asme) {
321		if (ruid != 0 && !chshell(pwd->pw_shell))
322			errx(1, "permission denied (shell)");
323	}
324	else if (pwd->pw_shell && *pwd->pw_shell) {
325		shell = pwd->pw_shell;
326		iscsh = UNSET;
327	}
328	else {
329		shell = _PATH_BSHELL;
330		iscsh = NO;
331	}
332
333	/* if we're forking a csh, we want to slightly muck the args */
334	if (iscsh == UNSET) {
335		p = strrchr(shell, '/');
336		if (p)
337			++p;
338		else
339			p = shell;
340		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
341	}
342	setpriority(PRIO_PROCESS, 0, prio);
343
344	/* Switch to home directory */
345	if (asthem) {
346		if (chdir(pwd->pw_dir) < 0)
347			errx(1, "no directory");
348	}
349
350	/*
351	 * PAM modules might add supplementary groups in pam_setcred(), so
352	 * initialize them first.
353	 */
354	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
355		err(1, "setusercontext");
356
357	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
358	if (retcode != PAM_SUCCESS) {
359		syslog(LOG_ERR, "pam_setcred: %s",
360		    pam_strerror(pamh, retcode));
361		errx(1, "failed to establish credentials.");
362	}
363	if (asthem) {
364		retcode = pam_open_session(pamh, 0);
365		if (retcode != PAM_SUCCESS) {
366			syslog(LOG_ERR, "pam_open_session: %s",
367			    pam_strerror(pamh, retcode));
368			errx(1, "failed to open session.");
369		}
370	}
371
372	/*
373	 * We must fork() before setuid() because we need to call
374	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
375	 */
376	sa.sa_flags = SA_RESTART;
377	sa.sa_handler = SIG_IGN;
378	sigemptyset(&sa.sa_mask);
379	sigaction(SIGINT, &sa, &sa_int);
380	sigaction(SIGQUIT, &sa, &sa_quit);
381	sigaction(SIGPIPE, &sa, &sa_pipe);
382	sa.sa_handler = SIG_DFL;
383	sigaction(SIGTSTP, &sa, NULL);
384	statusp = 1;
385	if (pipe(fds) == -1) {
386		PAM_END();
387		err(1, "pipe");
388	}
389	child_pid = fork();
390	switch (child_pid) {
391	default:
392		sa.sa_handler = SIG_IGN;
393		sigaction(SIGTTOU, &sa, NULL);
394		close(fds[0]);
395		close(fds[1]);
396		sigaction(SIGPIPE, &sa_pipe, NULL);
397		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
398			if (WIFSTOPPED(statusp)) {
399				kill(getpid(), SIGSTOP);
400				kill(child_pid, SIGCONT);
401				statusp = 1;
402				continue;
403			}
404			break;
405		}
406		if (pid == -1)
407			err(1, "waitpid");
408		PAM_END();
409		exit(WEXITSTATUS(statusp));
410	case -1:
411		PAM_END();
412		err(1, "fork");
413	case 0:
414		close(fds[1]);
415		read(fds[0], &temp, 1);
416		close(fds[0]);
417		sigaction(SIGPIPE, &sa_pipe, NULL);
418		sigaction(SIGINT, &sa_int, NULL);
419		sigaction(SIGQUIT, &sa_quit, NULL);
420
421		/*
422		 * Set all user context except for: Environmental variables
423		 * Umask Login records (wtmp, etc) Path
424		 */
425		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
426			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
427			   LOGIN_SETMAC);
428		/*
429		 * If -s is present, also set the MAC label.
430		 */
431		if (setmaclabel)
432			setwhat |= LOGIN_SETMAC;
433		/*
434		 * Don't touch resource/priority settings if -m has been used
435		 * or -l and -c hasn't, and we're not su'ing to root.
436		 */
437		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
438			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
439		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
440			err(1, "setusercontext");
441
442		if (!asme) {
443			if (asthem) {
444				p = getenv("TERM");
445				environ = &cleanenv;
446			}
447
448			if (asthem || pwd->pw_uid)
449				setenv("USER", pwd->pw_name, 1);
450			setenv("HOME", pwd->pw_dir, 1);
451			setenv("SHELL", shell, 1);
452
453			if (asthem) {
454				/*
455				 * Add any environmental variables that the
456				 * PAM modules may have set.
457				 */
458				environ_pam = pam_getenvlist(pamh);
459				if (environ_pam)
460					export_pam_environment();
461
462				/* set the su'd user's environment & umask */
463				setusercontext(lc, pwd, pwd->pw_uid,
464					LOGIN_SETPATH | LOGIN_SETUMASK |
465					LOGIN_SETENV);
466				if (p)
467					setenv("TERM", p, 1);
468			}
469		}
470		login_close(lc);
471
472		if (iscsh == YES) {
473			if (fastlogin)
474				*np.a-- = "-f";
475			if (asme)
476				*np.a-- = "-m";
477		}
478		/* csh strips the first character... */
479		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
480
481		if (ruid != 0)
482			syslog(LOG_NOTICE, "%s to %s%s", username, user,
483			    ontty());
484
485		execv(shell, np.b);
486		err(1, "%s", shell);
487	}
488}
489
490static void
491export_pam_environment(void)
492{
493	char	**pp;
494
495	for (pp = environ_pam; *pp != NULL; pp++) {
496		if (ok_to_export(*pp))
497			putenv(*pp);
498		free(*pp);
499	}
500}
501
502/*
503 * Sanity checks on PAM environmental variables:
504 * - Make sure there is an '=' in the string.
505 * - Make sure the string doesn't run on too long.
506 * - Do not export certain variables.  This list was taken from the
507 *   Solaris pam_putenv(3) man page.
508 * Note that if the user is chrooted, PAM may have a better idea than we
509 * do of where her home directory is.
510 */
511static int
512ok_to_export(const char *s)
513{
514	static const char *noexport[] = {
515		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
516		"IFS", "PATH", NULL
517	};
518	const char **pp;
519	size_t n;
520
521	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
522		return 0;
523	if (strncmp(s, "LD_", 3) == 0)
524		return 0;
525	for (pp = noexport; *pp != NULL; pp++) {
526		n = strlen(*pp);
527		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
528			return 0;
529	}
530	return 1;
531}
532
533static void
534usage(void)
535{
536
537	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
538	exit(1);
539	/* NOTREACHED */
540}
541
542static int
543chshell(const char *sh)
544{
545	int r;
546	char *cp;
547
548	r = 0;
549	setusershell();
550	while ((cp = getusershell()) != NULL && !r)
551	    r = (strcmp(cp, sh) == 0);
552	endusershell();
553	return r;
554}
555
556static char *
557ontty(void)
558{
559	char *p;
560	static char buf[MAXPATHLEN + 4];
561
562	buf[0] = 0;
563	p = ttyname(STDERR_FILENO);
564	if (p)
565		snprintf(buf, sizeof(buf), " on %s", p);
566	return buf;
567}
568