xref: /freebsd/usr.bin/su/su.c (revision 4f29da19bd44f0e99f021510460a81bf754c21d2)
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
65 static 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
72 static 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$");
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 
133 enum tristate { UNSET, YES, NO };
134 
135 static pam_handle_t *pamh = NULL;
136 static char	**environ_pam;
137 
138 static char	*ontty(void);
139 static int	chshell(const char *);
140 static void	usage(void) __dead2;
141 static void	export_pam_environment(void);
142 static int	ok_to_export(const char *);
143 
144 extern char	**environ;
145 
146 int
147 main(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, child_pgrp, 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 		setpgid(child_pid, child_pid);
396 		if (tcgetpgrp(STDERR_FILENO) == getpgrp())
397 			tcsetpgrp(STDERR_FILENO, child_pid);
398 		close(fds[1]);
399 		sigaction(SIGPIPE, &sa_pipe, NULL);
400 		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
401 			if (WIFSTOPPED(statusp)) {
402 				child_pgrp = getpgid(child_pid);
403 				if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
404 					tcsetpgrp(STDERR_FILENO, getpgrp());
405 				kill(getpid(), SIGSTOP);
406 				if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
407 					child_pgrp = getpgid(child_pid);
408 					tcsetpgrp(STDERR_FILENO, child_pgrp);
409 				}
410 				kill(child_pid, SIGCONT);
411 				statusp = 1;
412 				continue;
413 			}
414 			break;
415 		}
416 		child_pgrp = getpgid(child_pid);
417 		if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
418 			tcsetpgrp(STDERR_FILENO, getpgrp());
419 		if (pid == -1)
420 			err(1, "waitpid");
421 		PAM_END();
422 		exit(WEXITSTATUS(statusp));
423 	case -1:
424 		PAM_END();
425 		err(1, "fork");
426 	case 0:
427 		close(fds[1]);
428 		read(fds[0], &temp, 1);
429 		close(fds[0]);
430 		sigaction(SIGPIPE, &sa_pipe, NULL);
431 		sigaction(SIGINT, &sa_int, NULL);
432 		sigaction(SIGQUIT, &sa_quit, NULL);
433 
434 		/*
435 		 * Set all user context except for: Environmental variables
436 		 * Umask Login records (wtmp, etc) Path
437 		 */
438 		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
439 			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
440 			   LOGIN_SETMAC);
441 		/*
442 		 * If -s is present, also set the MAC label.
443 		 */
444 		if (setmaclabel)
445 			setwhat |= LOGIN_SETMAC;
446 		/*
447 		 * Don't touch resource/priority settings if -m has been used
448 		 * or -l and -c hasn't, and we're not su'ing to root.
449 		 */
450 		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
451 			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
452 		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
453 			err(1, "setusercontext");
454 
455 		if (!asme) {
456 			if (asthem) {
457 				p = getenv("TERM");
458 				environ = &cleanenv;
459 			}
460 
461 			if (asthem || pwd->pw_uid)
462 				setenv("USER", pwd->pw_name, 1);
463 			setenv("HOME", pwd->pw_dir, 1);
464 			setenv("SHELL", shell, 1);
465 
466 			if (asthem) {
467 				/*
468 				 * Add any environmental variables that the
469 				 * PAM modules may have set.
470 				 */
471 				environ_pam = pam_getenvlist(pamh);
472 				if (environ_pam)
473 					export_pam_environment();
474 
475 				/* set the su'd user's environment & umask */
476 				setusercontext(lc, pwd, pwd->pw_uid,
477 					LOGIN_SETPATH | LOGIN_SETUMASK |
478 					LOGIN_SETENV);
479 				if (p)
480 					setenv("TERM", p, 1);
481 			}
482 		}
483 		login_close(lc);
484 
485 		if (iscsh == YES) {
486 			if (fastlogin)
487 				*np.a-- = "-f";
488 			if (asme)
489 				*np.a-- = "-m";
490 		}
491 		/* csh strips the first character... */
492 		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
493 
494 		if (ruid != 0)
495 			syslog(LOG_NOTICE, "%s to %s%s", username, user,
496 			    ontty());
497 
498 		execv(shell, np.b);
499 		err(1, "%s", shell);
500 	}
501 }
502 
503 static void
504 export_pam_environment(void)
505 {
506 	char	**pp;
507 
508 	for (pp = environ_pam; *pp != NULL; pp++) {
509 		if (ok_to_export(*pp))
510 			putenv(*pp);
511 		free(*pp);
512 	}
513 }
514 
515 /*
516  * Sanity checks on PAM environmental variables:
517  * - Make sure there is an '=' in the string.
518  * - Make sure the string doesn't run on too long.
519  * - Do not export certain variables.  This list was taken from the
520  *   Solaris pam_putenv(3) man page.
521  * Note that if the user is chrooted, PAM may have a better idea than we
522  * do of where her home directory is.
523  */
524 static int
525 ok_to_export(const char *s)
526 {
527 	static const char *noexport[] = {
528 		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
529 		"IFS", "PATH", NULL
530 	};
531 	const char **pp;
532 	size_t n;
533 
534 	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
535 		return 0;
536 	if (strncmp(s, "LD_", 3) == 0)
537 		return 0;
538 	for (pp = noexport; *pp != NULL; pp++) {
539 		n = strlen(*pp);
540 		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
541 			return 0;
542 	}
543 	return 1;
544 }
545 
546 static void
547 usage(void)
548 {
549 
550 	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
551 	exit(1);
552 	/* NOTREACHED */
553 }
554 
555 static int
556 chshell(const char *sh)
557 {
558 	int r;
559 	char *cp;
560 
561 	r = 0;
562 	setusershell();
563 	while ((cp = getusershell()) != NULL && !r)
564 	    r = (strcmp(cp, sh) == 0);
565 	endusershell();
566 	return r;
567 }
568 
569 static char *
570 ontty(void)
571 {
572 	char *p;
573 	static char buf[MAXPATHLEN + 4];
574 
575 	buf[0] = 0;
576 	p = ttyname(STDERR_FILENO);
577 	if (p)
578 		snprintf(buf, sizeof(buf), " on %s", p);
579 	return buf;
580 }
581