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