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