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