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