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