xref: /freebsd/usr.bin/su/su.c (revision 729362425c09cf6b362366aabc6fb547eee8035a)
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 && 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 
103 enum tristate { UNSET, YES, NO };
104 
105 static pam_handle_t *pamh = NULL;
106 static int	creds_set = 0;
107 static char	**environ_pam;
108 
109 static char	*ontty(void);
110 static int	chshell(char *);
111 static void	usage(void);
112 static int	export_pam_environment(void);
113 static int	ok_to_export(const char *);
114 
115 extern char	**environ;
116 
117 int
118 main(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 
443 static int
444 export_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  */
463 static int
464 ok_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 
485 static void
486 usage(void)
487 {
488 
489 	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
490 	exit(1);
491 }
492 
493 static int
494 chshell(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 
509 static char *
510 ontty(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