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