1 /*
2  * su(1) for Linux.  Run a shell with substitute user and group IDs.
3  *
4  * Copyright (C) 1992-2006 Free Software Foundation, Inc.
5  * Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
6  * Copyright (C) 2016-2017 Karel Zak <kzak@redhat.com>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2, or (at your option) any later
11  * version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.  You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19  * USA.
20  *
21  *
22  * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>.
23  */
24 #include <stdio.h>
25 #include <getopt.h>
26 #include <sys/types.h>
27 #include <pwd.h>
28 #include <grp.h>
29 #include <security/pam_appl.h>
30 #ifdef HAVE_SECURITY_PAM_MISC_H
31 # include <security/pam_misc.h>
32 #elif defined(HAVE_SECURITY_OPENPAM_H)
33 # include <security/openpam.h>
34 #endif
35 #include <signal.h>
36 #include <sys/wait.h>
37 #include <syslog.h>
38 #include <utmpx.h>
39 
40 #ifdef HAVE_PTY
41 # include <pty.h>
42 # include <poll.h>
43 # include <sys/signalfd.h>
44 # include "pty-session.h"
45 # define USE_PTY
46 #endif
47 
48 #include "err.h"
49 
50 #include <stdbool.h>
51 
52 #include "c.h"
53 #include "xalloc.h"
54 #include "nls.h"
55 #include "pathnames.h"
56 #include "env.h"
57 #include "closestream.h"
58 #include "strv.h"
59 #include "strutils.h"
60 #include "ttyutils.h"
61 #include "pwdutils.h"
62 #include "optutils.h"
63 
64 #include "logindefs.h"
65 #include "su-common.h"
66 
67 #include "debug.h"
68 
69 UL_DEBUG_DEFINE_MASK(su);
70 UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES;
71 
72 #define SU_DEBUG_INIT		(1 << 1)
73 #define SU_DEBUG_PAM		(1 << 2)
74 #define SU_DEBUG_PARENT		(1 << 3)
75 #define SU_DEBUG_TTY		(1 << 4)
76 #define SU_DEBUG_LOG		(1 << 5)
77 #define SU_DEBUG_MISC		(1 << 6)
78 #define SU_DEBUG_SIG		(1 << 7)
79 #define SU_DEBUG_PTY		(1 << 8)
80 #define SU_DEBUG_ALL		0xFFFF
81 
82 #define DBG(m, x)       __UL_DBG(su, SU_DEBUG_, m, x)
83 #define ON_DBG(m, x)    __UL_DBG_CALL(su, SU_DEBUG_, m, x)
84 
85 
86 /* name of the pam configuration files. separate configs for su and su -  */
87 #define PAM_SRVNAME_SU "su"
88 #define PAM_SRVNAME_SU_L "su-l"
89 
90 #define PAM_SRVNAME_RUNUSER "runuser"
91 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
92 
93 #ifdef HAVE_LIBECONF
94 #define _PATH_LOGINDEFS_SU	"default/su"
95 #define _PATH_LOGINDEFS_RUNUSER "default/runuser"
96 #else
97 #define _PATH_LOGINDEFS_SU	"/etc/default/su"
98 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
99 #endif
100 
101 #define is_pam_failure(_rc)	((_rc) != PAM_SUCCESS)
102 
103 /* The shell to run if none is given in the user's passwd entry.  */
104 #define DEFAULT_SHELL "/bin/sh"
105 
106 /* The user to become if none is specified.  */
107 #define DEFAULT_USER "root"
108 
109 #ifndef HAVE_ENVIRON_DECL
110 extern char **environ;
111 #endif
112 
113 enum {
114 	SIGTERM_IDX = 0,
115 	SIGINT_IDX,
116 	SIGQUIT_IDX,
117 
118 	SIGNALS_IDX_COUNT
119 };
120 
121 /*
122  * su/runuser control struct
123  */
124 struct su_context {
125 	pam_handle_t	*pamh;			/* PAM handler */
126 	struct pam_conv conv;			/* PAM conversation */
127 
128 	struct passwd	*pwd;			/* new user info */
129 	char		*pwdbuf;		/* pwd strings */
130 
131 	const char	*tty_name;		/* tty_path without /dev prefix */
132 	const char	*tty_number;		/* end of the tty_path */
133 
134 	char		*new_user;		/* wanted user */
135 	char		*old_user;		/* original user */
136 
137 	pid_t		child;			/* fork() baby */
138 	int		childstatus;		/* wait() status */
139 
140 	char		**env_whitelist_names;	/* environment whitelist */
141 	char		**env_whitelist_vals;
142 
143 	struct sigaction oldact[SIGNALS_IDX_COUNT];	/* original sigactions indexed by SIG*_IDX */
144 #ifdef USE_PTY
145 	struct ul_pty	*pty;			/* pseudo terminal handler (for --pty) */
146 #endif
147 	unsigned int runuser :1,		/* flase=su, true=runuser */
148 		     runuser_uopt :1,		/* runuser -u specified */
149 		     isterm :1,			/* is stdin terminal? */
150 		     fast_startup :1,		/* pass the `-f' option to the subshell. */
151 		     simulate_login :1,		/* simulate a login instead of just starting a shell. */
152 		     change_environment :1,	/* change some environment vars to indicate the user su'd to.*/
153 		     same_session :1,		/* don't call setsid() with a command. */
154 		     suppress_pam_info:1,	/* don't print PAM info messages (Last login, etc.). */
155 		     pam_has_session :1,	/* PAM session opened */
156 		     pam_has_cred :1,		/* PAM cred established */
157 		     force_pty :1,		/* create pseudo-terminal */
158 		     restricted :1;		/* false for root user */
159 };
160 
161 
162 static sig_atomic_t volatile caught_signal = false;
163 
164 /* Signal handler for parent process.  */
165 static void
su_catch_sig(int sig)166 su_catch_sig(int sig)
167 {
168 	caught_signal = sig;
169 }
170 
su_init_debug(void)171 static void su_init_debug(void)
172 {
173 	__UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG);
174 }
175 
init_tty(struct su_context * su)176 static void init_tty(struct su_context *su)
177 {
178 	su->isterm = isatty(STDIN_FILENO) ? 1 : 0;
179 	DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false"));
180 	if (su->isterm)
181 		get_terminal_name(NULL, &su->tty_name, &su->tty_number);
182 }
183 
184 /*
185  * Note, this function has to be possible call more than once. If the child is
186  * already dead than it returns saved result from the previous call.
187  */
wait_for_child(struct su_context * su)188 static int wait_for_child(struct su_context *su)
189 {
190 	pid_t pid = (pid_t) -1;
191 	int status = 0;
192 
193 	if (su->child == (pid_t) -1)
194 		return su->childstatus;
195 
196 	if (su->child != (pid_t) -1) {
197 		/*
198 		 * The "su" parent process spends all time here in waitpid(),
199 		 * but "su --pty" uses pty_proxy_master() and waitpid() is only
200 		 * called to pick up child status or to react to SIGSTOP.
201 		 */
202 		DBG(SIG, ul_debug("waiting for child [%d]...", su->child));
203 		for (;;) {
204 			pid = waitpid(su->child, &status, WUNTRACED);
205 
206 			if (pid != (pid_t) - 1 && WIFSTOPPED(status)) {
207 				DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session"));
208 				kill(getpid(), SIGSTOP);
209 				/* once we get here, we must have resumed */
210 				kill(pid, SIGCONT);
211 				DBG(SIG, ul_debug(" session resumed -- continue"));
212 #ifdef USE_PTY
213 				/* Let's go back to pty_proxy_master() */
214 				if (su->force_pty && ul_pty_is_running(su->pty)) {
215 					DBG(SIG, ul_debug(" leaving on child SIGSTOP"));
216 					return 0;
217 				}
218 #endif
219 			} else
220 				break;
221 		}
222 	}
223 	if (pid != (pid_t) -1) {
224 		if (WIFSIGNALED(status)) {
225 			fprintf(stderr, "%s%s\n",
226 				strsignal(WTERMSIG(status)),
227 				WCOREDUMP(status) ? _(" (core dumped)")
228 				: "");
229 			status = WTERMSIG(status) + 128;
230 		} else
231 			status = WEXITSTATUS(status);
232 
233 		DBG(SIG, ul_debug("child %d is dead", su->child));
234 		su->child = (pid_t) -1;	/* Don't use the PID anymore! */
235 		su->childstatus = status;
236 #ifdef USE_PTY
237 		/* inform pty suff that we have no child anymore */
238 		if (su->force_pty)
239 			ul_pty_set_child(su->pty, (pid_t) -1);
240 #endif
241 	} else if (caught_signal)
242 		status = caught_signal + 128;
243 	else
244 		status = 1;
245 
246 	DBG(SIG, ul_debug("child status=%d", status));
247 	return status;
248 }
249 
250 #ifdef USE_PTY
wait_for_child_cb(void * data,pid_t child)251 static void wait_for_child_cb(
252 			void *data,
253 			pid_t child __attribute__((__unused__)))
254 {
255 	wait_for_child((struct su_context *) data);
256 }
257 #endif
258 
259 /* Log the fact that someone has run su to the user given by PW;
260    if SUCCESSFUL is true, they gave the correct password, etc.  */
261 
log_syslog(struct su_context * su,bool successful)262 static void log_syslog(struct su_context *su, bool successful)
263 {
264 	DBG(LOG, ul_debug("syslog logging"));
265 
266 	openlog(program_invocation_short_name, 0, LOG_AUTH);
267 	syslog(LOG_NOTICE, "%s(to %s) %s on %s",
268 	       successful ? "" :
269 	       su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
270 	       su->new_user, su->old_user ? : "",
271 	       su->tty_name ? : "none");
272 	closelog();
273 }
274 
275 /*
276  * Log failed login attempts in _PATH_BTMP if that exists.
277  */
log_btmp(struct su_context * su)278 static void log_btmp(struct su_context *su)
279 {
280 	struct utmpx ut;
281 	struct timeval tv;
282 
283 	DBG(LOG, ul_debug("btmp logging"));
284 
285 	memset(&ut, 0, sizeof(ut));
286 	str2memcpy(ut.ut_user,
287 		su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
288 		sizeof(ut.ut_user));
289 
290 	if (su->tty_number)
291 		str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
292 	if (su->tty_name)
293 		str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
294 
295 	gettimeofday(&tv, NULL);
296 	ut.ut_tv.tv_sec = tv.tv_sec;
297 	ut.ut_tv.tv_usec = tv.tv_usec;
298 	ut.ut_type = LOGIN_PROCESS;	/* XXX doesn't matter */
299 	ut.ut_pid = getpid();
300 
301 	updwtmpx(_PATH_BTMP, &ut);
302 }
303 
supam_conv(int num_msg,const struct pam_message ** msg,struct pam_response ** resp,void * data)304 static int supam_conv(	int num_msg,
305 			const struct pam_message **msg,
306 			struct pam_response **resp,
307 			void *data)
308 {
309 	struct su_context *su = (struct su_context *) data;
310 
311 	if (su->suppress_pam_info
312 	    && num_msg == 1
313 	    && msg && msg[0]->msg_style == PAM_TEXT_INFO)
314 		return PAM_SUCCESS;
315 
316 #ifdef HAVE_SECURITY_PAM_MISC_H
317 	return misc_conv(num_msg, msg, resp, data);
318 #elif defined(HAVE_SECURITY_OPENPAM_H)
319 	return openpam_ttyconv(num_msg, msg, resp, data);
320 #endif
321 }
322 
supam_cleanup(struct su_context * su,int retcode)323 static void supam_cleanup(struct su_context *su, int retcode)
324 {
325 	const int errsv = errno;
326 
327 	DBG(PAM, ul_debug("cleanup"));
328 
329 	if (su->pam_has_session)
330 		pam_close_session(su->pamh, 0);
331 	if (su->pam_has_cred)
332 		pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
333 	pam_end(su->pamh, retcode);
334 	errno = errsv;
335 }
336 
337 
supam_export_environment(struct su_context * su)338 static void supam_export_environment(struct su_context *su)
339 {
340 	char **env;
341 
342 	DBG(PAM, ul_debug("init environ[]"));
343 
344 	/* This is a copy but don't care to free as we exec later anyways.  */
345 	env = pam_getenvlist(su->pamh);
346 
347 	while (env && *env) {
348 		if (putenv(*env) != 0)
349 			err(EXIT_FAILURE, _("failed to modify environment"));
350 		env++;
351 	}
352 }
353 
supam_authenticate(struct su_context * su)354 static void supam_authenticate(struct su_context *su)
355 {
356 	const char *srvname = NULL;
357 	int rc;
358 
359 	srvname = su->runuser ?
360 		   (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) :
361 		   (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU);
362 
363 	DBG(PAM, ul_debug("start [name: %s]", srvname));
364 
365 	rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
366 	if (is_pam_failure(rc))
367 		goto done;
368 
369 	if (su->tty_name) {
370 		rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
371 		if (is_pam_failure(rc))
372 			goto done;
373 	}
374 	if (su->old_user) {
375 		rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
376 		if (is_pam_failure(rc))
377 			goto done;
378 	}
379 	if (su->runuser) {
380 		/*
381 		 * This is the only difference between runuser(1) and su(1). The command
382 		 * runuser(1) does not required authentication, because user is root.
383 		 */
384 		if (su->restricted)
385 			errx(EXIT_FAILURE, _("may not be used by non-root users"));
386 		return;
387 	}
388 
389 	rc = pam_authenticate(su->pamh, 0);
390 	if (is_pam_failure(rc))
391 		goto done;
392 
393 	/* Check password expiration and offer option to change it.  */
394 	rc = pam_acct_mgmt(su->pamh, 0);
395 	if (rc == PAM_NEW_AUTHTOK_REQD)
396 		rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
397  done:
398 	log_syslog(su, !is_pam_failure(rc));
399 
400 	if (is_pam_failure(rc)) {
401 		const char *msg;
402 
403 		DBG(PAM, ul_debug("authentication failed"));
404 		log_btmp(su);
405 
406 		msg = pam_strerror(su->pamh, rc);
407 		pam_end(su->pamh, rc);
408 		sleep(getlogindefs_num("FAIL_DELAY", 1));
409 		errx(EXIT_FAILURE, "%s", msg ? msg : _("authentication failed"));
410 	}
411 }
412 
supam_open_session(struct su_context * su)413 static void supam_open_session(struct su_context *su)
414 {
415 	int rc;
416 
417 	DBG(PAM, ul_debug("opening session"));
418 
419 	rc = pam_open_session(su->pamh, 0);
420 	if (is_pam_failure(rc)) {
421 		supam_cleanup(su, rc);
422 		errx(EXIT_FAILURE, _("cannot open session: %s"),
423 		     pam_strerror(su->pamh, rc));
424 	} else
425 		su->pam_has_session = 1;
426 }
427 
parent_setup_signals(struct su_context * su)428 static void parent_setup_signals(struct su_context *su)
429 {
430 	sigset_t ourset;
431 
432 	/*
433 	 * Signals setup
434 	 *
435 	 * 1) block all signals
436 	 */
437 	DBG(SIG, ul_debug("initialize signals"));
438 
439 	sigfillset(&ourset);
440 	if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
441 		warn(_("cannot block signals"));
442 		caught_signal = true;
443 	}
444 
445 	if (!caught_signal) {
446 		struct sigaction action;
447 		action.sa_handler = su_catch_sig;
448 		sigemptyset(&action.sa_mask);
449 		action.sa_flags = 0;
450 
451 		sigemptyset(&ourset);
452 
453 		/* 2a) add wanted signals to the mask (for session) */
454 		if (!su->same_session
455 		    && (sigaddset(&ourset, SIGINT)
456 		       || sigaddset(&ourset, SIGQUIT))) {
457 
458 			warn(_("cannot initialize signal mask for session"));
459 			caught_signal = true;
460 		}
461 		/* 2b) add wanted generic signals to the mask */
462 		if (!caught_signal
463 		    && (sigaddset(&ourset, SIGTERM)
464 		       || sigaddset(&ourset, SIGALRM))) {
465 
466 			warn(_("cannot initialize signal mask"));
467 			caught_signal = true;
468 		}
469 
470 		/* 3a) set signal handlers (for session) */
471 		if (!caught_signal
472 		    && !su->same_session
473 		    && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX])
474 		       || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) {
475 
476 			warn(_("cannot set signal handler for session"));
477 			caught_signal = true;
478 		}
479 
480 		/* 3b) set signal handlers */
481 		if (!caught_signal
482 		     && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) {
483 
484 			warn(_("cannot set signal handler"));
485 			caught_signal = true;
486 		}
487 
488 		/* 4) unblock wanted signals */
489 		if (!caught_signal
490 		    && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) {
491 
492 			warn(_("cannot set signal mask"));
493 			caught_signal = true;
494 		}
495 	}
496 }
497 
498 
create_watching_parent(struct su_context * su)499 static void create_watching_parent(struct su_context *su)
500 {
501 	int status;
502 
503 	DBG(MISC, ul_debug("forking..."));
504 #ifdef USE_PTY
505 	if (su->force_pty) {
506 		struct ul_pty_callbacks *cb;
507 
508 		/* set callbacks */
509 		ul_pty_set_callback_data(su->pty, (void *) su);
510 
511 		cb = ul_pty_get_callbacks(su->pty);
512 		cb->child_wait    = wait_for_child_cb;
513 		cb->child_sigstop = wait_for_child_cb;
514 
515 		/* create pty */
516 		if (ul_pty_setup(su->pty))
517 			err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
518 	}
519 #endif
520 	fflush(stdout);			/* ??? */
521 
522 	switch ((int) (su->child = fork())) {
523 	case -1: /* error */
524 		supam_cleanup(su, PAM_ABORT);
525 #ifdef USE_PTY
526 		if (su->force_pty)
527 			ul_pty_cleanup(su->pty);
528 #endif
529 		err(EXIT_FAILURE, _("cannot create child process"));
530 		break;
531 
532 	case 0: /* child */
533 		return;
534 
535 	default: /* parent */
536 		DBG(MISC, ul_debug("child [pid=%d]", (int) su->child));
537 		break;
538 	}
539 
540 	/* free unnecessary stuff */
541 	free_getlogindefs_data();
542 
543 	/* In the parent watch the child.  */
544 
545 	/* su without pam support does not have a helper that keeps
546 	   sitting on any directory so let's go to /.  */
547 	if (chdir("/") != 0)
548 		warn(_("cannot change directory to %s"), "/");
549 #ifdef USE_PTY
550 	if (su->force_pty) {
551 		ul_pty_set_child(su->pty, su->child);
552 
553 		if (ul_pty_proxy_master(su->pty) != 0)
554 			caught_signal = true;
555 
556 		/* ul_pty_proxy_master() keeps classic signal handler are out of game */
557 		caught_signal = ul_pty_get_delivered_signal(su->pty);
558 
559 		ul_pty_cleanup(su->pty);
560 	} else
561 #endif
562 		parent_setup_signals(su);
563 
564 	/*
565 	 * Wait for child
566 	 */
567 	if (!caught_signal)
568 		status = wait_for_child(su);
569 	else
570 		status = 1;
571 
572 	DBG(SIG, ul_debug("final child status=%d", status));
573 
574 	if (caught_signal && su->child != (pid_t)-1) {
575 		fprintf(stderr, _("\nSession terminated, killing shell..."));
576 		kill(su->child, SIGTERM);
577 	}
578 
579 	supam_cleanup(su, PAM_SUCCESS);
580 
581 	if (caught_signal) {
582 		if (su->child != (pid_t)-1) {
583 			DBG(SIG, ul_debug("killing child"));
584 			sleep(2);
585 			kill(su->child, SIGKILL);
586 			fprintf(stderr, _(" ...killed.\n"));
587 		}
588 
589 		/* Let's terminate itself with the received signal.
590 		 *
591 		 * It seems that shells use WIFSIGNALED() rather than our exit status
592 		 * value to detect situations when is necessary to cleanup (reset)
593 		 * terminal settings (kzak -- Jun 2013).
594 		 */
595 		DBG(SIG, ul_debug("restore signals setting"));
596 		switch (caught_signal) {
597 		case SIGTERM:
598 			sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL);
599 			break;
600 		case SIGINT:
601 			sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL);
602 			break;
603 		case SIGQUIT:
604 			sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL);
605 			break;
606 		default:
607 			/* just in case that signal stuff initialization failed and
608 			 * caught_signal = true */
609 			caught_signal = SIGKILL;
610 			break;
611 		}
612 		DBG(SIG, ul_debug("self-send %d signal", caught_signal));
613 		kill(getpid(), caught_signal);
614 	}
615 
616 	DBG(MISC, ul_debug("exiting [rc=%d]", status));
617 	exit(status);
618 }
619 
620 /* Adds @name from the current environment to the whitelist. If @name is not
621  * set then nothing is added to the whitelist and returns 1.
622  */
env_whitelist_add(struct su_context * su,const char * name)623 static int env_whitelist_add(struct su_context *su, const char *name)
624 {
625 	const char *env = getenv(name);
626 
627 	if (!env)
628 		return 1;
629 	if (strv_extend(&su->env_whitelist_names, name))
630                 err_oom();
631 	if (strv_extend(&su->env_whitelist_vals, env))
632                 err_oom();
633 	return 0;
634 }
635 
env_whitelist_setenv(struct su_context * su,int overwrite)636 static int env_whitelist_setenv(struct su_context *su, int overwrite)
637 {
638 	char **one;
639 	size_t i = 0;
640 	int rc;
641 
642 	STRV_FOREACH(one, su->env_whitelist_names) {
643 		rc = setenv(*one, su->env_whitelist_vals[i], overwrite);
644 		if (rc)
645 			return rc;
646 		i++;
647 	}
648 
649 	return 0;
650 }
651 
652 /* Creates (add to) whitelist from comma delimited string */
env_whitelist_from_string(struct su_context * su,const char * str)653 static int env_whitelist_from_string(struct su_context *su, const char *str)
654 {
655 	char **all = strv_split(str, ",");
656 	char **one;
657 
658 	if (!all) {
659 		if (errno == ENOMEM)
660 			err_oom();
661 		return -EINVAL;
662 	}
663 
664 	STRV_FOREACH(one, all)
665 		env_whitelist_add(su, *one);
666 	strv_free(all);
667 	return 0;
668 }
669 
setenv_path(const struct passwd * pw)670 static void setenv_path(const struct passwd *pw)
671 {
672 	int rc;
673 
674 	DBG(MISC, ul_debug("setting PATH"));
675 
676 	if (pw->pw_uid)
677 		rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
678 
679 	else if ((rc = logindefs_setenv("PATH", "ENV_SUPATH", NULL)) != 0)
680 		rc = logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT);
681 
682 	if (rc)
683 		err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
684 }
685 
modify_environment(struct su_context * su,const char * shell)686 static void modify_environment(struct su_context *su, const char *shell)
687 {
688 	const struct passwd *pw = su->pwd;
689 
690 
691 	DBG(MISC, ul_debug("modify environ[]"));
692 
693 	/* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
694 	 *
695 	 * Unset all other environment variables, but follow
696 	 * --whitelist-environment if specified.
697 	 */
698 	if (su->simulate_login) {
699 		/* leave TERM unchanged */
700 		env_whitelist_add(su, "TERM");
701 
702 		/* Note that original su(1) has allocated environ[] by malloc
703 		 * to the number of expected variables. This seems unnecessary
704 		 * optimization as libc later re-alloc(current_size+2) and for
705 		 * empty environ[] the curren_size is zero. It seems better to
706 		 * keep all logic around environment in glibc's hands.
707 		 *                                           --kzak [Aug 2018]
708 		 */
709 #ifdef HAVE_CLEARENV
710 		clearenv();
711 #else
712 		environ = NULL;
713 #endif
714 		/* always reset */
715 		if (shell)
716 			xsetenv("SHELL", shell, 1);
717 
718 		setenv_path(pw);
719 
720 		xsetenv("HOME", pw->pw_dir, 1);
721 		xsetenv("USER", pw->pw_name, 1);
722 		xsetenv("LOGNAME", pw->pw_name, 1);
723 
724 		/* apply all from whitelist, but no overwrite */
725 		env_whitelist_setenv(su, 0);
726 
727 	/* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
728 	 */
729 	} else if (su->change_environment) {
730 		xsetenv("HOME", pw->pw_dir, 1);
731 		if (shell)
732 			xsetenv("SHELL", shell, 1);
733 
734 		if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
735 			setenv_path(pw);
736 
737 		if (pw->pw_uid) {
738 			xsetenv("USER", pw->pw_name, 1);
739 			xsetenv("LOGNAME", pw->pw_name, 1);
740 		}
741 	}
742 
743 	supam_export_environment(su);
744 }
745 
init_groups(struct su_context * su,gid_t * groups,size_t ngroups)746 static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
747 {
748 	int rc;
749 
750 	DBG(MISC, ul_debug("initialize groups"));
751 
752 	errno = 0;
753 	if (ngroups)
754 		rc = setgroups(ngroups, groups);
755 	else
756 		rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
757 
758 	if (rc == -1) {
759 		supam_cleanup(su, PAM_ABORT);
760 		err(EXIT_FAILURE, _("cannot set groups"));
761 	}
762 	endgrent();
763 
764 	rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
765 	if (is_pam_failure(rc))
766 		errx(EXIT_FAILURE, _("failed to establish user credentials: %s"),
767 					pam_strerror(su->pamh, rc));
768 	su->pam_has_cred = 1;
769 }
770 
change_identity(const struct passwd * pw)771 static void change_identity(const struct passwd *pw)
772 {
773 	DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid));
774 
775 	if (setgid(pw->pw_gid))
776 		err(EXIT_FAILURE, _("cannot set group id"));
777 	if (setuid(pw->pw_uid))
778 		err(EXIT_FAILURE, _("cannot set user id"));
779 }
780 
781 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
782  * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
783  * N_ADDITIONAL_ARGS extra arguments.
784  */
run_shell(struct su_context * su,char const * shell,char const * command,char ** additional_args,size_t n_additional_args)785 static void run_shell(
786 		struct su_context *su,
787 		char const *shell, char const *command, char **additional_args,
788 		size_t n_additional_args)
789 {
790 	size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
791 	const char **args = xcalloc(n_args, sizeof *args);
792 	size_t argno = 1;
793 
794 	DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
795 				shell, command,
796 				su->simulate_login ? " login" : "",
797 				su->fast_startup ? " fast-start" : ""));
798 
799 	if (su->simulate_login) {
800 		char *arg0;
801 		char *shell_basename;
802 
803 		shell_basename = basename(shell);
804 		arg0 = xmalloc(strlen(shell_basename) + 2);
805 		arg0[0] = '-';
806 		strcpy(arg0 + 1, shell_basename);
807 		args[0] = arg0;
808 	} else
809 		args[0] = basename(shell);
810 
811 	if (su->fast_startup)
812 		args[argno++] = "-f";
813 	if (command) {
814 		args[argno++] = "-c";
815 		args[argno++] = command;
816 	}
817 
818 	memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
819 	args[argno + n_additional_args] = NULL;
820 	execv(shell, (char **)args);
821 	errexec(shell);
822 }
823 
824 /* Return true if SHELL is a restricted shell (one not returned by
825  * getusershell), else false, meaning it is a standard shell.
826  */
is_restricted_shell(const char * shell)827 static bool is_restricted_shell(const char *shell)
828 {
829 	char *line;
830 
831 	setusershell();
832 	while ((line = getusershell()) != NULL) {
833 		if (*line != '#' && !strcmp(line, shell)) {
834 			endusershell();
835 			return false;
836 		}
837 	}
838 	endusershell();
839 
840 	DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell));
841 	return true;
842 }
843 
usage_common(void)844 static void usage_common(void)
845 {
846 	fputs(_(" -m, -p, --preserve-environment      do not reset environment variables\n"), stdout);
847 	fputs(_(" -w, --whitelist-environment <list>  don't reset specified variables\n"), stdout);
848 	fputs(USAGE_SEPARATOR, stdout);
849 
850 	fputs(_(" -g, --group <group>             specify the primary group\n"), stdout);
851 	fputs(_(" -G, --supp-group <group>        specify a supplemental group\n"), stdout);
852 	fputs(USAGE_SEPARATOR, stdout);
853 
854 	fputs(_(" -, -l, --login                  make the shell a login shell\n"), stdout);
855 	fputs(_(" -c, --command <command>         pass a single command to the shell with -c\n"), stdout);
856 	fputs(_(" --session-command <command>     pass a single command to the shell with -c\n"
857 	        "                                   and do not create a new session\n"), stdout);
858 	fputs(_(" -f, --fast                      pass -f to the shell (for csh or tcsh)\n"), stdout);
859 	fputs(_(" -s, --shell <shell>             run <shell> if /etc/shells allows it\n"), stdout);
860 	fputs(_(" -P, --pty                       create a new pseudo-terminal\n"), stdout);
861 
862 	fputs(USAGE_SEPARATOR, stdout);
863 	printf(USAGE_HELP_OPTIONS(33));
864 }
865 
usage_runuser(void)866 static void usage_runuser(void)
867 {
868 	fputs(USAGE_HEADER, stdout);
869 	fprintf(stdout,
870 		_(" %1$s [options] -u <user> [[--] <command>]\n"
871 	          " %1$s [options] [-] [<user> [<argument>...]]\n"),
872 		program_invocation_short_name);
873 
874 	fputs(USAGE_SEPARATOR, stdout);
875 	fputs(_("Run <command> with the effective user ID and group ID of <user>.  If -u is\n"
876 	       "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
877 	       "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
878 
879 	fputs(USAGE_OPTIONS, stdout);
880 	fputs(_(" -u, --user <user>               username\n"), stdout);
881 	usage_common();
882 	fputs(USAGE_SEPARATOR, stdout);
883 
884 	fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
885 }
886 
usage_su(void)887 static void usage_su(void)
888 {
889 	fputs(USAGE_HEADER, stdout);
890 	fprintf(stdout,
891 		_(" %s [options] [-] [<user> [<argument>...]]\n"),
892 		program_invocation_short_name);
893 
894 	fputs(USAGE_SEPARATOR, stdout);
895 	fputs(_("Change the effective user ID and group ID to that of <user>.\n"
896 		"A mere - implies -l.  If <user> is not given, root is assumed.\n"), stdout);
897 
898 	fputs(USAGE_OPTIONS, stdout);
899 	usage_common();
900 
901 	fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
902 }
903 
usage(int mode)904 static void __attribute__((__noreturn__)) usage(int mode)
905 {
906 	if (mode == SU_MODE)
907 		usage_su();
908 	else
909 		usage_runuser();
910 
911 	exit(EXIT_SUCCESS);
912 }
913 
load_config(void * data)914 static void load_config(void *data)
915 {
916 	struct su_context *su = (struct su_context *) data;
917 
918 	DBG(MISC, ul_debug("loading logindefs"));
919 #ifndef HAVE_LIBECONF
920 	logindefs_load_file(_PATH_LOGINDEFS);
921 #endif
922 	logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
923 }
924 
925 /*
926  * Returns 1 if the current user is not root
927  */
is_not_root(void)928 static int is_not_root(void)
929 {
930 	const uid_t ruid = getuid();
931 	const uid_t euid = geteuid();
932 
933 	/* if we're really root and aren't running setuid */
934 	return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
935 }
936 
add_supp_group(const char * name,gid_t ** groups,size_t * ngroups)937 static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
938 {
939 	struct group *gr;
940 
941 	if (*ngroups >= NGROUPS_MAX)
942 		errx(EXIT_FAILURE,
943 		     P_("specifying more than %d supplemental group is not possible",
944 		        "specifying more than %d supplemental groups is not possible",
945 			NGROUPS_MAX - 1), NGROUPS_MAX - 1);
946 
947 	gr = getgrnam(name);
948 	if (!gr)
949 		errx(EXIT_FAILURE, _("group %s does not exist"), name);
950 
951 	DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid));
952 
953 	*groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
954 	(*groups)[*ngroups] = gr->gr_gid;
955 	(*ngroups)++;
956 
957 	return gr->gr_gid;
958 }
959 
su_main(int argc,char ** argv,int mode)960 int su_main(int argc, char **argv, int mode)
961 {
962 	struct su_context _su = {
963 		.conv			= { supam_conv, NULL },
964 		.runuser		= (mode == RUNUSER_MODE ? 1 : 0),
965 		.change_environment	= 1,
966 		.new_user		= DEFAULT_USER
967 	}, *su = &_su;
968 
969 	int optc;
970 	char *command = NULL;
971 	int request_same_session = 0;
972 	char *shell = NULL;
973 
974 	gid_t *groups = NULL;
975 	size_t ngroups = 0;
976 	bool use_supp = false;
977 	bool use_gid = false;
978 	gid_t gid = 0;
979 
980 	static const struct option longopts[] = {
981 		{"command", required_argument, NULL, 'c'},
982 		{"session-command", required_argument, NULL, 'C'},
983 		{"fast", no_argument, NULL, 'f'},
984 		{"login", no_argument, NULL, 'l'},
985 		{"preserve-environment", no_argument, NULL, 'p'},
986 		{"pty", no_argument, NULL, 'P'},
987 		{"shell", required_argument, NULL, 's'},
988 		{"group", required_argument, NULL, 'g'},
989 		{"supp-group", required_argument, NULL, 'G'},
990 		{"user", required_argument, NULL, 'u'},	/* runuser only */
991 		{"whitelist-environment", required_argument, NULL, 'w'},
992 		{"help", no_argument, 0, 'h'},
993 		{"version", no_argument, 0, 'V'},
994 		{NULL, 0, NULL, 0}
995 	};
996 	static const ul_excl_t excl[] = {	/* rows and cols in ASCII order */
997 		{ 'm', 'w' },			/* preserve-environment, whitelist-environment */
998 		{ 'p', 'w' },			/* preserve-environment, whitelist-environment */
999 		{ 0 }
1000 	};
1001 	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1002 
1003 	setlocale(LC_ALL, "");
1004 	bindtextdomain(PACKAGE, LOCALEDIR);
1005 	textdomain(PACKAGE);
1006 	close_stdout_atexit();
1007 
1008 	su_init_debug();
1009 	su->conv.appdata_ptr = (void *) su;
1010 
1011 	while ((optc =
1012 		getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
1013 			    NULL)) != -1) {
1014 
1015 		err_exclusive_options(optc, longopts, excl, excl_st);
1016 
1017 		switch (optc) {
1018 		case 'c':
1019 			command = optarg;
1020 			break;
1021 
1022 		case 'C':
1023 			command = optarg;
1024 			request_same_session = 1;
1025 			break;
1026 
1027 		case 'f':
1028 			su->fast_startup = true;
1029 			break;
1030 
1031 		case 'g':
1032 			use_gid = true;
1033 			gid = add_supp_group(optarg, &groups, &ngroups);
1034 			break;
1035 
1036 		case 'G':
1037 			use_supp = true;
1038 			add_supp_group(optarg, &groups, &ngroups);
1039 			break;
1040 
1041 		case 'l':
1042 			su->simulate_login = true;
1043 			break;
1044 
1045 		case 'm':
1046 		case 'p':
1047 			su->change_environment = false;
1048 			break;
1049 
1050 		case 'w':
1051 			env_whitelist_from_string(su, optarg);
1052 			break;
1053 
1054 		case 'P':
1055 #ifdef USE_PTY
1056 			su->force_pty = 1;
1057 #else
1058 			errx(EXIT_FAILURE, _("--pty is not supported for your system"));
1059 #endif
1060 			break;
1061 
1062 		case 's':
1063 			shell = optarg;
1064 			break;
1065 
1066 		case 'u':
1067 			if (!su->runuser)
1068 				errtryhelp(EXIT_FAILURE);
1069 			su->runuser_uopt = 1;
1070 			su->new_user = optarg;
1071 			break;
1072 
1073 		case 'h':
1074 			usage(mode);
1075 
1076 		case 'V':
1077 			print_version(EXIT_SUCCESS);
1078 		default:
1079 			errtryhelp(EXIT_FAILURE);
1080 		}
1081 	}
1082 
1083 	su->restricted = is_not_root();
1084 
1085 	if (optind < argc && !strcmp(argv[optind], "-")) {
1086 		su->simulate_login = true;
1087 		++optind;
1088 	}
1089 
1090 	if (su->simulate_login && !su->change_environment) {
1091 		warnx(_
1092 		      ("ignoring --preserve-environment, it's mutually exclusive with --login"));
1093 		su->change_environment = true;
1094 	}
1095 
1096 	switch (mode) {
1097 	case RUNUSER_MODE:
1098 		/* runuser -u <user> <command>
1099 		 *
1100 		 * If -u <user> is not specified, then follow traditional su(1) behavior and
1101 		 * fallthrough
1102 		 */
1103 		if (su->runuser_uopt) {
1104 			if (shell || su->fast_startup || command || su->simulate_login)
1105 				errx(EXIT_FAILURE,
1106 				     _("options --{shell,fast,command,session-command,login} and "
1107 				      "--user are mutually exclusive"));
1108 			if (optind == argc)
1109 				errx(EXIT_FAILURE, _("no command was specified"));
1110 			break;
1111 		}
1112 		/* fallthrough */
1113 	case SU_MODE:
1114 		if (optind < argc)
1115 			su->new_user = argv[optind++];
1116 		break;
1117 	}
1118 
1119 	if ((use_supp || use_gid) && su->restricted)
1120 		errx(EXIT_FAILURE,
1121 		     _("only root can specify alternative groups"));
1122 
1123 	logindefs_set_loader(load_config, (void *) su);
1124 	init_tty(su);
1125 
1126 	su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
1127 	if (!su->pwd
1128 	    || !su->pwd->pw_passwd
1129 	    || !su->pwd->pw_name || !*su->pwd->pw_name
1130 	    || !su->pwd->pw_dir  || !*su->pwd->pw_dir)
1131 		errx(EXIT_FAILURE,
1132 		     _("user %s does not exist or the user entry does not "
1133 		       "contain all the required fields"), su->new_user);
1134 
1135 	su->new_user = su->pwd->pw_name;
1136 	su->old_user = xgetlogin();
1137 
1138 	if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
1139 		su->pwd->pw_shell = DEFAULT_SHELL;
1140 
1141 	if (use_supp && !use_gid)
1142 		su->pwd->pw_gid = groups[0];
1143 	else if (use_gid)
1144 		su->pwd->pw_gid = gid;
1145 
1146 	supam_authenticate(su);
1147 
1148 	if (request_same_session || !command || !su->pwd->pw_uid)
1149 		su->same_session = 1;
1150 
1151 	/* initialize shell variable only if "-u <user>" not specified */
1152 	if (su->runuser_uopt) {
1153 		shell = NULL;
1154 	} else {
1155 		if (!shell && !su->change_environment)
1156 			shell = getenv("SHELL");
1157 
1158 		if (shell
1159 		    && strcmp(shell, su->pwd->pw_shell) != 0
1160 		    && getuid() != 0
1161 		    && is_restricted_shell(su->pwd->pw_shell)) {
1162 			/* The user being su'd to has a nonstandard shell, and
1163 			 * so is probably a uucp account or has restricted
1164 			 * access.  Don't compromise the account by allowing
1165 			 * access with a standard shell.
1166 			 */
1167 			warnx(_("using restricted shell %s"), su->pwd->pw_shell);
1168 			shell = NULL;
1169 		}
1170 		shell = xstrdup(shell ? shell : su->pwd->pw_shell);
1171 	}
1172 
1173 	init_groups(su, groups, ngroups);
1174 
1175 	if (!su->simulate_login || command)
1176 		su->suppress_pam_info = 1;	/* don't print PAM info messages */
1177 
1178 	supam_open_session(su);
1179 
1180 #ifdef USE_PTY
1181 	if (su->force_pty) {
1182 		ON_DBG(PTY, ul_pty_init_debug(0xffff));
1183 
1184 		su->pty = ul_new_pty(su->isterm);
1185 		if (!su->pty)
1186 			err(EXIT_FAILURE, _("failed to allocate pty handler"));
1187 	}
1188 #endif
1189 	create_watching_parent(su);
1190 	/* Now we're in the child.  */
1191 
1192 	change_identity(su->pwd);
1193 	if (!su->same_session) {
1194 		/* note that on --pty we call setsid() in ul_pty_init_slave() */
1195 		DBG(MISC, ul_debug("call setsid()"));
1196 		setsid();
1197 	}
1198 #ifdef USE_PTY
1199 	if (su->force_pty)
1200 		ul_pty_init_slave(su->pty);
1201 #endif
1202 	/* Set environment after pam_open_session, which may put KRB5CCNAME
1203 	   into the pam_env, etc.  */
1204 
1205 	modify_environment(su, shell);
1206 
1207 	if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
1208 		warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
1209 
1210 	if (shell)
1211 		run_shell(su, shell, command, argv + optind, max(0, argc - optind));
1212 
1213 	execvp(argv[optind], &argv[optind]);
1214 	err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
1215 }
1216