su.c revision 330897
1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2002, 2005 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 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33/*-
34 * Copyright (c) 1988, 1993, 1994
35 *	The Regents of the University of California.  All rights reserved.
36 *
37 * Redistribution and use in source and binary forms, with or without
38 * modification, are permitted provided that the following conditions
39 * are met:
40 * 1. Redistributions of source code must retain the above copyright
41 *    notice, this list of conditions and the following disclaimer.
42 * 2. Redistributions in binary form must reproduce the above copyright
43 *    notice, this list of conditions and the following disclaimer in the
44 *    documentation and/or other materials provided with the distribution.
45 * 4. Neither the name of the University nor the names of its contributors
46 *    may be used to endorse or promote products derived from this software
47 *    without specific prior written permission.
48 *
49 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59 * SUCH DAMAGE.
60 */
61
62#ifndef lint
63static const char copyright[] =
64"@(#) Copyright (c) 1988, 1993, 1994\n\
65	The Regents of the University of California.  All rights reserved.\n";
66#endif /* not lint */
67
68#if 0
69#ifndef lint
70static char sccsid[] = "@(#)su.c	8.3 (Berkeley) 4/2/94";
71#endif /* not lint */
72#endif
73
74#include <sys/cdefs.h>
75__FBSDID("$FreeBSD: stable/11/usr.bin/su/su.c 330897 2018-03-14 03:19:51Z eadler $");
76
77#include <sys/param.h>
78#include <sys/time.h>
79#include <sys/resource.h>
80#include <sys/wait.h>
81
82#ifdef USE_BSM_AUDIT
83#include <bsm/libbsm.h>
84#include <bsm/audit_uevents.h>
85#endif
86
87#include <err.h>
88#include <errno.h>
89#include <grp.h>
90#include <login_cap.h>
91#include <paths.h>
92#include <pwd.h>
93#include <signal.h>
94#include <stdio.h>
95#include <stdlib.h>
96#include <string.h>
97#include <syslog.h>
98#include <unistd.h>
99#include <stdarg.h>
100
101#include <security/pam_appl.h>
102#include <security/openpam.h>
103
104#define PAM_END() do {							\
105	int local_ret;							\
106	if (pamh != NULL) {						\
107		local_ret = pam_setcred(pamh, PAM_DELETE_CRED);		\
108		if (local_ret != PAM_SUCCESS)				\
109			syslog(LOG_ERR, "pam_setcred: %s",		\
110				pam_strerror(pamh, local_ret));		\
111		if (asthem) {						\
112			local_ret = pam_close_session(pamh, 0);		\
113			if (local_ret != PAM_SUCCESS)			\
114				syslog(LOG_ERR, "pam_close_session: %s",\
115					pam_strerror(pamh, local_ret));	\
116		}							\
117		local_ret = pam_end(pamh, local_ret);			\
118		if (local_ret != PAM_SUCCESS)				\
119			syslog(LOG_ERR, "pam_end: %s",			\
120				pam_strerror(pamh, local_ret));		\
121	}								\
122} while (0)
123
124
125#define PAM_SET_ITEM(what, item) do {					\
126	int local_ret;							\
127	local_ret = pam_set_item(pamh, what, item);			\
128	if (local_ret != PAM_SUCCESS) {					\
129		syslog(LOG_ERR, "pam_set_item(" #what "): %s",		\
130			pam_strerror(pamh, local_ret));			\
131		errx(1, "pam_set_item(" #what "): %s",			\
132			pam_strerror(pamh, local_ret));			\
133		/* NOTREACHED */					\
134	}								\
135} while (0)
136
137enum tristate { UNSET, YES, NO };
138
139static pam_handle_t *pamh = NULL;
140static char	**environ_pam;
141
142static char	*ontty(void);
143static int	chshell(const char *);
144static void	usage(void) __dead2;
145static void	export_pam_environment(void);
146static int	ok_to_export(const char *);
147
148extern char	**environ;
149
150int
151main(int argc, char *argv[])
152{
153	static char	*cleanenv;
154	struct passwd	*pwd = NULL;
155	struct pam_conv	conv = { openpam_ttyconv, NULL };
156	enum tristate	iscsh;
157	login_cap_t	*lc;
158	union {
159		const char	**a;
160		char		* const *b;
161	}		np;
162	uid_t		ruid;
163	pid_t		child_pid, child_pgrp, pid;
164	int		asme, ch, asthem, fastlogin, prio, i, retcode,
165			statusp, setmaclabel;
166	u_int		setwhat;
167	char		*username, *class, shellbuf[MAXPATHLEN];
168	const char	*p, *user, *shell, *mytty, **nargv;
169	const void	*v;
170	struct sigaction sa, sa_int, sa_quit, sa_pipe;
171	int temp, fds[2];
172#ifdef USE_BSM_AUDIT
173	const char	*aerr;
174	au_id_t		 auid;
175#endif
176
177	p = shell = class = cleanenv = NULL;
178	asme = asthem = fastlogin = statusp = 0;
179	user = "root";
180	iscsh = UNSET;
181	setmaclabel = 0;
182
183	while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
184		switch ((char)ch) {
185		case 'f':
186			fastlogin = 1;
187			break;
188		case '-':
189		case 'l':
190			asme = 0;
191			asthem = 1;
192			break;
193		case 'm':
194			asme = 1;
195			asthem = 0;
196			break;
197		case 's':
198			setmaclabel = 1;
199			break;
200		case 'c':
201			class = optarg;
202			break;
203		case '?':
204		default:
205			usage();
206		/* NOTREACHED */
207		}
208
209	if (optind < argc)
210		user = argv[optind++];
211
212	if (user == NULL)
213		usage();
214	/* NOTREACHED */
215
216	/*
217	 * Try to provide more helpful debugging output if su(1) is running
218	 * non-setuid, or was run from a file system not mounted setuid.
219	 */
220	if (geteuid() != 0)
221		errx(1, "not running setuid");
222
223#ifdef USE_BSM_AUDIT
224	if (getauid(&auid) < 0 && errno != ENOSYS) {
225		syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
226		errx(1, "Permission denied");
227	}
228#endif
229	if (strlen(user) > MAXLOGNAME - 1) {
230#ifdef USE_BSM_AUDIT
231		if (audit_submit(AUE_su, auid,
232		    EPERM, 1, "username too long: '%s'", user))
233			errx(1, "Permission denied");
234#endif
235		errx(1, "username too long");
236	}
237
238	nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
239	if (nargv == NULL)
240		errx(1, "malloc failure");
241
242	nargv[argc + 3] = NULL;
243	for (i = argc; i >= optind; i--)
244		nargv[i + 3] = argv[i];
245	np.a = &nargv[i + 3];
246
247	argv += optind;
248
249	errno = 0;
250	prio = getpriority(PRIO_PROCESS, 0);
251	if (errno)
252		prio = 0;
253
254	setpriority(PRIO_PROCESS, 0, -2);
255	openlog("su", LOG_CONS, LOG_AUTH);
256
257	/* get current login name, real uid and shell */
258	ruid = getuid();
259	username = getlogin();
260	if (username != NULL)
261		pwd = getpwnam(username);
262	if (pwd == NULL || pwd->pw_uid != ruid)
263		pwd = getpwuid(ruid);
264	if (pwd == NULL) {
265#ifdef USE_BSM_AUDIT
266		if (audit_submit(AUE_su, auid, EPERM, 1,
267		    "unable to determine invoking subject: '%s'", username))
268			errx(1, "Permission denied");
269#endif
270		errx(1, "who are you?");
271	}
272
273	username = strdup(pwd->pw_name);
274	if (username == NULL)
275		err(1, "strdup failure");
276
277	if (asme) {
278		if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
279			/* must copy - pwd memory is recycled */
280			strlcpy(shellbuf, pwd->pw_shell,
281			    sizeof(shellbuf));
282			shell = shellbuf;
283		}
284		else {
285			shell = _PATH_BSHELL;
286			iscsh = NO;
287		}
288	}
289
290	/* Do the whole PAM startup thing */
291	retcode = pam_start("su", user, &conv, &pamh);
292	if (retcode != PAM_SUCCESS) {
293		syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
294		errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
295	}
296
297	PAM_SET_ITEM(PAM_RUSER, username);
298
299	mytty = ttyname(STDERR_FILENO);
300	if (!mytty)
301		mytty = "tty";
302	PAM_SET_ITEM(PAM_TTY, mytty);
303
304	retcode = pam_authenticate(pamh, 0);
305	if (retcode != PAM_SUCCESS) {
306#ifdef USE_BSM_AUDIT
307		if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
308		    username, user, mytty))
309			errx(1, "Permission denied");
310#endif
311		syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
312		    username, user, mytty);
313		errx(1, "Sorry");
314	}
315#ifdef USE_BSM_AUDIT
316	if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
317		errx(1, "Permission denied");
318#endif
319	retcode = pam_get_item(pamh, PAM_USER, &v);
320	if (retcode == PAM_SUCCESS)
321		user = v;
322	else
323		syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
324		    pam_strerror(pamh, retcode));
325	pwd = getpwnam(user);
326	if (pwd == NULL) {
327#ifdef USE_BSM_AUDIT
328		if (audit_submit(AUE_su, auid, EPERM, 1,
329		    "unknown subject: %s", user))
330			errx(1, "Permission denied");
331#endif
332		errx(1, "unknown login: %s", user);
333	}
334
335	retcode = pam_acct_mgmt(pamh, 0);
336	if (retcode == PAM_NEW_AUTHTOK_REQD) {
337		retcode = pam_chauthtok(pamh,
338			PAM_CHANGE_EXPIRED_AUTHTOK);
339		if (retcode != PAM_SUCCESS) {
340#ifdef USE_BSM_AUDIT
341			aerr = pam_strerror(pamh, retcode);
342			if (aerr == NULL)
343				aerr = "Unknown PAM error";
344			if (audit_submit(AUE_su, auid, EPERM, 1,
345			    "pam_chauthtok: %s", aerr))
346				errx(1, "Permission denied");
347#endif
348			syslog(LOG_ERR, "pam_chauthtok: %s",
349			    pam_strerror(pamh, retcode));
350			errx(1, "Sorry");
351		}
352	}
353	if (retcode != PAM_SUCCESS) {
354#ifdef USE_BSM_AUDIT
355		if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
356		    pam_strerror(pamh, retcode)))
357			errx(1, "Permission denied");
358#endif
359		syslog(LOG_ERR, "pam_acct_mgmt: %s",
360			pam_strerror(pamh, retcode));
361		errx(1, "Sorry");
362	}
363
364	/* get target login information */
365	if (class == NULL)
366		lc = login_getpwclass(pwd);
367	else {
368		if (ruid != 0) {
369#ifdef USE_BSM_AUDIT
370			if (audit_submit(AUE_su, auid, EPERM, 1,
371			    "only root may use -c"))
372				errx(1, "Permission denied");
373#endif
374			errx(1, "only root may use -c");
375		}
376		lc = login_getclass(class);
377		if (lc == NULL)
378			err(1, "login_getclass");
379		if (lc->lc_class == NULL || strcmp(class, lc->lc_class) != 0)
380			errx(1, "unknown class: %s", class);
381	}
382
383	/* if asme and non-standard target shell, must be root */
384	if (asme) {
385		if (ruid != 0 && !chshell(pwd->pw_shell))
386			errx(1, "permission denied (shell)");
387	}
388	else if (pwd->pw_shell && *pwd->pw_shell) {
389		shell = pwd->pw_shell;
390		iscsh = UNSET;
391	}
392	else {
393		shell = _PATH_BSHELL;
394		iscsh = NO;
395	}
396
397	/* if we're forking a csh, we want to slightly muck the args */
398	if (iscsh == UNSET) {
399		p = strrchr(shell, '/');
400		if (p)
401			++p;
402		else
403			p = shell;
404		iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
405	}
406	setpriority(PRIO_PROCESS, 0, prio);
407
408	/*
409	 * PAM modules might add supplementary groups in pam_setcred(), so
410	 * initialize them first.
411	 */
412	if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
413		err(1, "setusercontext");
414
415	retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
416	if (retcode != PAM_SUCCESS) {
417		syslog(LOG_ERR, "pam_setcred: %s",
418		    pam_strerror(pamh, retcode));
419		errx(1, "failed to establish credentials.");
420	}
421	if (asthem) {
422		retcode = pam_open_session(pamh, 0);
423		if (retcode != PAM_SUCCESS) {
424			syslog(LOG_ERR, "pam_open_session: %s",
425			    pam_strerror(pamh, retcode));
426			errx(1, "failed to open session.");
427		}
428	}
429
430	/*
431	 * We must fork() before setuid() because we need to call
432	 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
433	 */
434	sa.sa_flags = SA_RESTART;
435	sa.sa_handler = SIG_IGN;
436	sigemptyset(&sa.sa_mask);
437	sigaction(SIGINT, &sa, &sa_int);
438	sigaction(SIGQUIT, &sa, &sa_quit);
439	sigaction(SIGPIPE, &sa, &sa_pipe);
440	sa.sa_handler = SIG_DFL;
441	sigaction(SIGTSTP, &sa, NULL);
442	statusp = 1;
443	if (pipe(fds) == -1) {
444		PAM_END();
445		err(1, "pipe");
446	}
447	child_pid = fork();
448	switch (child_pid) {
449	default:
450		sa.sa_handler = SIG_IGN;
451		sigaction(SIGTTOU, &sa, NULL);
452		close(fds[0]);
453		setpgid(child_pid, child_pid);
454		if (tcgetpgrp(STDERR_FILENO) == getpgrp())
455			tcsetpgrp(STDERR_FILENO, child_pid);
456		close(fds[1]);
457		sigaction(SIGPIPE, &sa_pipe, NULL);
458		while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
459			if (WIFSTOPPED(statusp)) {
460				child_pgrp = getpgid(child_pid);
461				if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
462					tcsetpgrp(STDERR_FILENO, getpgrp());
463				kill(getpid(), SIGSTOP);
464				if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
465					child_pgrp = getpgid(child_pid);
466					tcsetpgrp(STDERR_FILENO, child_pgrp);
467				}
468				kill(child_pid, SIGCONT);
469				statusp = 1;
470				continue;
471			}
472			break;
473		}
474		tcsetpgrp(STDERR_FILENO, getpgrp());
475		if (pid == -1)
476			err(1, "waitpid");
477		PAM_END();
478		exit(WEXITSTATUS(statusp));
479	case -1:
480		PAM_END();
481		err(1, "fork");
482	case 0:
483		close(fds[1]);
484		read(fds[0], &temp, 1);
485		close(fds[0]);
486		sigaction(SIGPIPE, &sa_pipe, NULL);
487		sigaction(SIGINT, &sa_int, NULL);
488		sigaction(SIGQUIT, &sa_quit, NULL);
489
490		/*
491		 * Set all user context except for: Environmental variables
492		 * Umask Login records (wtmp, etc) Path
493		 */
494		setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
495			   LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
496			   LOGIN_SETMAC);
497		/*
498		 * If -s is present, also set the MAC label.
499		 */
500		if (setmaclabel)
501			setwhat |= LOGIN_SETMAC;
502		/*
503		 * Don't touch resource/priority settings if -m has been used
504		 * or -l and -c hasn't, and we're not su'ing to root.
505		 */
506		if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
507			setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
508		if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
509			err(1, "setusercontext");
510
511		if (!asme) {
512			if (asthem) {
513				p = getenv("TERM");
514				environ = &cleanenv;
515			}
516
517			if (asthem || pwd->pw_uid)
518				setenv("USER", pwd->pw_name, 1);
519			setenv("HOME", pwd->pw_dir, 1);
520			setenv("SHELL", shell, 1);
521
522			if (asthem) {
523				/*
524				 * Add any environmental variables that the
525				 * PAM modules may have set.
526				 */
527				environ_pam = pam_getenvlist(pamh);
528				if (environ_pam)
529					export_pam_environment();
530
531				/* set the su'd user's environment & umask */
532				setusercontext(lc, pwd, pwd->pw_uid,
533					LOGIN_SETPATH | LOGIN_SETUMASK |
534					LOGIN_SETENV);
535				if (p)
536					setenv("TERM", p, 1);
537
538				p = pam_getenv(pamh, "HOME");
539				if (chdir(p ? p : pwd->pw_dir) < 0)
540					errx(1, "no directory");
541			}
542		}
543		login_close(lc);
544
545		if (iscsh == YES) {
546			if (fastlogin)
547				*np.a-- = "-f";
548			if (asme)
549				*np.a-- = "-m";
550		}
551		/* csh strips the first character... */
552		*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
553
554		if (ruid != 0)
555			syslog(LOG_NOTICE, "%s to %s%s", username, user,
556			    ontty());
557
558		execv(shell, np.b);
559		err(1, "%s", shell);
560	}
561}
562
563static void
564export_pam_environment(void)
565{
566	char	**pp;
567	char	*p;
568
569	for (pp = environ_pam; *pp != NULL; pp++) {
570		if (ok_to_export(*pp)) {
571			p = strchr(*pp, '=');
572			*p = '\0';
573			setenv(*pp, p + 1, 1);
574		}
575		free(*pp);
576	}
577}
578
579/*
580 * Sanity checks on PAM environmental variables:
581 * - Make sure there is an '=' in the string.
582 * - Make sure the string doesn't run on too long.
583 * - Do not export certain variables.  This list was taken from the
584 *   Solaris pam_putenv(3) man page.
585 * Note that if the user is chrooted, PAM may have a better idea than we
586 * do of where her home directory is.
587 */
588static int
589ok_to_export(const char *s)
590{
591	static const char *noexport[] = {
592		"SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
593		"IFS", "PATH", NULL
594	};
595	const char **pp;
596	size_t n;
597
598	if (strlen(s) > 1024 || strchr(s, '=') == NULL)
599		return 0;
600	if (strncmp(s, "LD_", 3) == 0)
601		return 0;
602	for (pp = noexport; *pp != NULL; pp++) {
603		n = strlen(*pp);
604		if (s[n] == '=' && strncmp(s, *pp, n) == 0)
605			return 0;
606	}
607	return 1;
608}
609
610static void
611usage(void)
612{
613
614	fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
615	exit(1);
616	/* NOTREACHED */
617}
618
619static int
620chshell(const char *sh)
621{
622	int r;
623	char *cp;
624
625	r = 0;
626	setusershell();
627	while ((cp = getusershell()) != NULL && !r)
628	    r = (strcmp(cp, sh) == 0);
629	endusershell();
630	return r;
631}
632
633static char *
634ontty(void)
635{
636	char *p;
637	static char buf[MAXPATHLEN + 4];
638
639	buf[0] = 0;
640	p = ttyname(STDERR_FILENO);
641	if (p)
642		snprintf(buf, sizeof(buf), " on %s", p);
643	return buf;
644}
645