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