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