]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/su-common.c
su: be sensitive to another SIGCHLD ssi_codes
[thirdparty/util-linux.git] / login-utils / su-common.c
CommitLineData
b1195aa0
KZ
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
eb7d0ad0 6 * Copyright (C) 2016-2017 Karel Zak <kzak@redhat.com>
b1195aa0
KZ
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 */
cf1a99da
KZ
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>
fe2c9909 30#ifdef HAVE_SECURITY_PAM_MISC_H
b1195aa0 31# include <security/pam_misc.h>
fe2c9909 32#elif defined(HAVE_SECURITY_OPENPAM_H)
b1195aa0 33# include <security/openpam.h>
fe2c9909 34#endif
cf1a99da
KZ
35#include <signal.h>
36#include <sys/wait.h>
37#include <syslog.h>
b4b919fe 38#include <utmpx.h>
cf1a99da 39
eb7d0ad0
KZ
40#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) && defined(HAVE_SYS_SIGNALFD_H)
41# include <pty.h>
42# include <poll.h>
43# include <sys/signalfd.h>
44# include "all-io.h"
45# define USE_PTY
46#endif
47
cf1a99da
KZ
48#include "err.h"
49
50#include <stdbool.h>
b1195aa0 51
cf1a99da
KZ
52#include "c.h"
53#include "xalloc.h"
54#include "nls.h"
55#include "pathnames.h"
56#include "env.h"
4e183497 57#include "closestream.h"
75efef98 58#include "strv.h"
c74a7af1 59#include "strutils.h"
bbc5a5ea 60#include "ttyutils.h"
032d759a 61#include "pwdutils.h"
75efef98 62#include "optutils.h"
cf1a99da 63
b1195aa0
KZ
64#include "logindefs.h"
65#include "su-common.h"
66
2260e493
KZ
67#include "debug.h"
68
69UL_DEBUG_DEFINE_MASK(su);
70UL_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)
eb7d0ad0 79#define SU_DEBUG_PTY (1 << 8)
2260e493
KZ
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
cf1a99da 86/* name of the pam configuration files. separate configs for su and su - */
7ec6adb1
KZ
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
37410713
KZ
93#define _PATH_LOGINDEFS_SU "/etc/default/su"
94#define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
cf1a99da
KZ
95
96#define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
97
cf1a99da
KZ
98/* The shell to run if none is given in the user's passwd entry. */
99#define DEFAULT_SHELL "/bin/sh"
100
101/* The user to become if none is specified. */
102#define DEFAULT_USER "root"
103
104#ifndef HAVE_ENVIRON_DECL
105extern char **environ;
106#endif
107
665f36be
KZ
108enum {
109 SIGTERM_IDX = 0,
110 SIGINT_IDX,
111 SIGQUIT_IDX,
112
113 SIGNALS_IDX_COUNT
114};
115
832f5cd5
KZ
116/*
117 * su/runuser control struct
118 */
119struct su_context {
120 pam_handle_t *pamh; /* PAM handler */
121 struct pam_conv conv; /* PAM conversation */
122
032d759a
KZ
123 struct passwd *pwd; /* new user info */
124 char *pwdbuf; /* pwd strings */
125
302b7b65
KZ
126 const char *tty_name; /* tty_path without /dev prefix */
127 const char *tty_number; /* end of the tty_path */
128
94c6730b 129 char *new_user; /* wanted user */
fcf841f8 130 char *old_user; /* original user */
94c6730b 131
44f36ad1 132 pid_t child; /* fork() baby */
13ee2f4d 133 int childstatus; /* wait() status */
44f36ad1 134
75efef98
KZ
135 char **env_whitelist_names; /* environment whitelist */
136 char **env_whitelist_vals;
137
665f36be 138 struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */
eb7d0ad0
KZ
139
140#ifdef USE_PTY
141 struct termios stdin_attrs; /* stdin and slave terminal runtime attributes */
142 int pty_master;
143 int pty_slave;
144 int pty_sigfd; /* signalfd() */
145 int poll_timeout;
146 struct winsize win; /* terminal window size */
0214f438 147 sigset_t oldsig; /* original signal mask */
eb7d0ad0 148#endif
832f5cd5 149 unsigned int runuser :1, /* flase=su, true=runuser */
94c6730b 150 runuser_uopt :1, /* runuser -u specified */
302b7b65 151 isterm :1, /* is stdin terminal? */
832f5cd5
KZ
152 fast_startup :1, /* pass the `-f' option to the subshell. */
153 simulate_login :1, /* simulate a login instead of just starting a shell. */
154 change_environment :1, /* change some environment vars to indicate the user su'd to.*/
155 same_session :1, /* don't call setsid() with a command. */
156 suppress_pam_info:1, /* don't print PAM info messages (Last login, etc.). */
157 pam_has_session :1, /* PAM session opened */
158 pam_has_cred :1, /* PAM cred established */
04845ec7 159 pty :1, /* create pseudo-terminal */
832f5cd5
KZ
160 restricted :1; /* false for root user */
161};
cf1a99da 162
cf1a99da 163
cf1a99da 164static sig_atomic_t volatile caught_signal = false;
cf1a99da 165
b9a92282
KZ
166/* Signal handler for parent process. */
167static void
168su_catch_sig(int sig)
169{
170 caught_signal = sig;
171}
172
2260e493
KZ
173static void su_init_debug(void)
174{
a15dca2f 175 __UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG);
2260e493
KZ
176}
177
302b7b65
KZ
178static void init_tty(struct su_context *su)
179{
180 su->isterm = isatty(STDIN_FILENO) ? 1 : 0;
73afd3f8 181 DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false"));
302b7b65
KZ
182 if (su->isterm)
183 get_terminal_name(NULL, &su->tty_name, &su->tty_number);
184}
185
13ee2f4d
KZ
186/*
187 * Note, this function has to be possible call more than once. If the child is
188 * already dead than it returns saved result from the previous call.
189 */
eb7d0ad0
KZ
190static int wait_for_child(struct su_context *su)
191{
192 pid_t pid = (pid_t) -1;;
193 int status = 0;
194
13ee2f4d
KZ
195 if (su->child == (pid_t) -1)
196 return su->childstatus;
197
eb7d0ad0 198 if (su->child != (pid_t) -1) {
e9fde3e9
KZ
199 /*
200 * The "su" parent process spends all time here in waitpid(),
201 * but "su --pty" uses pty_proxy_master() and waitpid() is only
202 * called to pick up child status or to react to SIGSTOP.
203 */
eb7d0ad0
KZ
204 DBG(SIG, ul_debug("waiting for child [%d]...", su->child));
205 for (;;) {
206 pid = waitpid(su->child, &status, WUNTRACED);
207
208 if (pid != (pid_t) - 1 && WIFSTOPPED(status)) {
e9fde3e9 209 DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session"));
eb7d0ad0
KZ
210 kill(getpid(), SIGSTOP);
211 /* once we get here, we must have resumed */
212 kill(pid, SIGCONT);
e9fde3e9
KZ
213 DBG(SIG, ul_debug(" session resumed -- continue"));
214#ifdef USE_PTY
215 /* Let's go back to pty_proxy_master() */
216 if (su->pty_sigfd != -1) {
217 DBG(SIG, ul_debug(" leaving on child SIGSTOP"));
218 return 0;
219 }
220#endif
eb7d0ad0
KZ
221 } else
222 break;
223 }
224 }
225 if (pid != (pid_t) -1) {
226 if (WIFSIGNALED(status)) {
227 fprintf(stderr, "%s%s\n",
228 strsignal(WTERMSIG(status)),
229 WCOREDUMP(status) ? _(" (core dumped)")
230 : "");
231 status = WTERMSIG(status) + 128;
232 } else
233 status = WEXITSTATUS(status);
234
235 DBG(SIG, ul_debug("child %d is dead", su->child));
236 su->child = (pid_t) -1; /* Don't use the PID anymore! */
13ee2f4d 237 su->childstatus = status;
eb7d0ad0
KZ
238 } else if (caught_signal)
239 status = caught_signal + 128;
240 else
241 status = 1;
242
13ee2f4d 243 DBG(SIG, ul_debug("child status=%d", status));
eb7d0ad0
KZ
244 return status;
245}
246
247
248#ifdef USE_PTY
249static void pty_init_slave(struct su_context *su)
250{
251 DBG(PTY, ul_debug("initialize slave"));
252
927ded6b 253 ioctl(su->pty_slave, TIOCSCTTY, 1);
eb7d0ad0
KZ
254 close(su->pty_master);
255
256 dup2(su->pty_slave, STDIN_FILENO);
257 dup2(su->pty_slave, STDOUT_FILENO);
258 dup2(su->pty_slave, STDERR_FILENO);
259
260 close(su->pty_slave);
261 close(su->pty_sigfd);
262
263 su->pty_slave = -1;
264 su->pty_master = -1;
265 su->pty_sigfd = -1;
266
267 sigprocmask(SIG_SETMASK, &su->oldsig, NULL);
268
269 DBG(PTY, ul_debug("... initialize slave done"));
270}
271
272static void pty_create(struct su_context *su)
273{
274 struct termios slave_attrs;
275 int rc;
276
277 if (su->isterm) {
278 DBG(PTY, ul_debug("create for terminal"));
279 struct winsize win;
280
281 /* original setting of the current terminal */
282 if (tcgetattr(STDIN_FILENO, &su->stdin_attrs) != 0)
283 err(EXIT_FAILURE, _("failed to get terminal attributes"));
284
285 /* reuse the current terminal setting for slave */
286 slave_attrs = su->stdin_attrs;
287 cfmakeraw(&slave_attrs);
288
289 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
290
291 /* create master+slave */
292 rc = openpty(&su->pty_master, &su->pty_slave, NULL, &slave_attrs, &win);
293
294 } else {
295 DBG(PTY, ul_debug("create for non-terminal"));
296 rc = openpty(&su->pty_master, &su->pty_slave, NULL, NULL, NULL);
297
298 /* set slave attributes */
61b3106b 299 if (!rc) {
eb7d0ad0
KZ
300 tcgetattr(su->pty_slave, &slave_attrs);
301 cfmakeraw(&slave_attrs);
302 tcsetattr(su->pty_slave, TCSANOW, &slave_attrs);
303 }
304 }
305
306 if (rc < 0)
307 err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
308
309 DBG(PTY, ul_debug("pty setup done [master=%d, slave=%d]", su->pty_master, su->pty_slave));
310}
311
312static void pty_cleanup(struct su_context *su)
313{
314 if (su->pty_master == -1)
315 return;
316
317 DBG(PTY, ul_debug("cleanup"));
318 if (su->isterm)
319 tcsetattr(STDIN_FILENO, TCSADRAIN, &su->stdin_attrs);
320}
321
322static int write_output(char *obuf, ssize_t bytes)
323{
324 DBG(PTY, ul_debug(" writing output"));
325
326 if (write_all(STDOUT_FILENO, obuf, bytes)) {
327 DBG(PTY, ul_debug(" writing output *failed*"));
328 warn(_("write failed"));
329 return -errno;
330 }
331
332 return 0;
333}
334
335static int write_to_child(struct su_context *su,
336 char *buf, size_t bufsz)
337{
338 return write_all(su->pty_master, buf, bufsz);
339}
340
341/*
342 * The su(1) is usually faster than shell, so it's a good idea to wait until
343 * the previous message has been already read by shell from slave before we
344 * write to master. This is necessary especially for EOF situation when we can
345 * send EOF to master before shell is fully initialized, to workaround this
346 * problem we wait until slave is empty. For example:
347 *
348 * echo "date" | su
349 *
350 * Unfortunately, the child (usually shell) can ignore stdin at all, so we
351 * don't wait forever to avoid dead locks...
352 *
353 * Note that su --pty is primarily designed for interactive sessions as it
354 * maintains master+slave tty stuff within the session. Use pipe to write to
355 * su(1) and assume non-interactive (tee-like) behavior is NOT well
356 * supported.
357 */
358static void write_eof_to_child(struct su_context *su)
359{
360 unsigned int tries = 0;
361 struct pollfd fds[] = {
362 { .fd = su->pty_slave, .events = POLLIN }
363 };
364 char c = DEF_EOF;
365
366 DBG(PTY, ul_debug(" waiting for empty slave"));
367 while (poll(fds, 1, 10) == 1 && tries < 8) {
368 DBG(PTY, ul_debug(" slave is not empty"));
369 xusleep(250000);
370 tries++;
371 }
372 if (tries < 8)
373 DBG(PTY, ul_debug(" slave is empty now"));
374
375 DBG(PTY, ul_debug(" sending EOF to master"));
376 write_to_child(su, &c, sizeof(char));
377}
378
379static int pty_handle_io(struct su_context *su, int fd, int *eof)
380{
381 char buf[BUFSIZ];
382 ssize_t bytes;
383
384 DBG(PTY, ul_debug("%d FD active", fd));
385 *eof = 0;
386
387 /* read from active FD */
388 bytes = read(fd, buf, sizeof(buf));
389 if (bytes < 0) {
390 if (errno == EAGAIN || errno == EINTR)
391 return 0;
392 return -errno;
393 }
394
395 if (bytes == 0) {
396 *eof = 1;
397 return 0;
398 }
399
400 /* from stdin (user) to command */
401 if (fd == STDIN_FILENO) {
402 DBG(PTY, ul_debug(" stdin --> master %zd bytes", bytes));
403
404 if (write_to_child(su, buf, bytes)) {
405 warn(_("write failed"));
406 return -errno;
407 }
408 /* without sync write_output() will write both input &
409 * shell output that looks like double echoing */
410 fdatasync(su->pty_master);
411
412 /* from command (master) to stdout */
413 } else if (fd == su->pty_master) {
414 DBG(PTY, ul_debug(" master --> stdout %zd bytes", bytes));
415 write_output(buf, bytes);
416 }
417
418 return 0;
419}
420
421static int pty_handle_signal(struct su_context *su, int fd)
422{
423 struct signalfd_siginfo info;
424 ssize_t bytes;
425
426 DBG(SIG, ul_debug("signal FD %d active", fd));
427
428 bytes = read(fd, &info, sizeof(info));
429 if (bytes != sizeof(info)) {
430 if (bytes < 0 && (errno == EAGAIN || errno == EINTR))
431 return 0;
432 return -errno;
433 }
434
435 switch (info.ssi_signo) {
436 case SIGCHLD:
437 DBG(SIG, ul_debug(" get signal SIGCHLD"));
e9fde3e9
KZ
438
439 /* The child terminated or stopped. Note that we ignore SIGCONT
440 * here, because stop/cont semantic is handled by wait_for_child() */
3209ed50
KZ
441 if (info.ssi_code == CLD_EXITED
442 || info.ssi_code == CLD_KILLED
443 || info.ssi_code == CLD_DUMPED
444 || info.ssi_status == SIGSTOP)
e9fde3e9
KZ
445 wait_for_child(su);
446 /* The child is dead, force poll() timeout. */
447 if (su->child == (pid_t) -1)
448 su->poll_timeout = 10;
eb7d0ad0
KZ
449 return 0;
450 case SIGWINCH:
451 DBG(SIG, ul_debug(" get signal SIGWINCH"));
452 if (su->isterm) {
453 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
454 ioctl(su->pty_slave, TIOCSWINSZ, (char *)&su->win);
455 }
456 break;
457 case SIGTERM:
458 /* fallthrough */
459 case SIGINT:
460 /* fallthrough */
461 case SIGQUIT:
462 DBG(SIG, ul_debug(" get signal SIG{TERM,INT,QUIT}"));
463 caught_signal = info.ssi_signo;
464 /* Child termination is going to generate SIGCHILD (see above) */
465 kill(su->child, SIGTERM);
466 break;
467 default:
468 abort();
469 }
470
471 return 0;
472}
473
474static void pty_proxy_master(struct su_context *su)
475{
476 sigset_t ourset;
5328d8e7 477 int rc = 0, ret, eof = 0;
eb7d0ad0
KZ
478 enum {
479 POLLFD_SIGNAL = 0,
480 POLLFD_MASTER,
5328d8e7 481 POLLFD_STDIN
eb7d0ad0
KZ
482
483 };
484 struct pollfd pfd[] = {
485 [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP },
486 [POLLFD_MASTER] = { .fd = su->pty_master, .events = POLLIN | POLLERR | POLLHUP },
487 [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP }
488 };
489
490 /* for PTY mode we use signalfd
491 *
492 * TODO: script(1) initializes this FD before fork, good or bad idea?
493 */
494 sigfillset(&ourset);
0214f438 495 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
eb7d0ad0
KZ
496 warn(_("cannot block signals"));
497 caught_signal = true;
498 return;
499 }
500
501 sigemptyset(&ourset);
502 sigaddset(&ourset, SIGCHLD);
503 sigaddset(&ourset, SIGWINCH);
504 sigaddset(&ourset, SIGALRM);
505 sigaddset(&ourset, SIGTERM);
506 sigaddset(&ourset, SIGINT);
507 sigaddset(&ourset, SIGQUIT);
508
509 if ((su->pty_sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) {
510 warn(("cannot create signal file descriptor"));
511 caught_signal = true;
512 return;
513 }
514
515 pfd[POLLFD_SIGNAL].fd = su->pty_sigfd;
516 su->poll_timeout = -1;
517
518 while (!caught_signal) {
519 size_t i;
520 int errsv;
521
522 DBG(PTY, ul_debug("calling poll()"));
523
524 /* wait for input or signal */
5328d8e7 525 ret = poll(pfd, ARRAY_SIZE(pfd), su->poll_timeout);
eb7d0ad0
KZ
526 errsv = errno;
527 DBG(PTY, ul_debug("poll() rc=%d", ret));
528
529 if (ret < 0) {
530 if (errsv == EAGAIN)
531 continue;
532 warn(_("poll failed"));
533 break;
534 }
535 if (ret == 0) {
536 DBG(PTY, ul_debug("leaving poll() loop [timeout=%d]", su->poll_timeout));
537 break;
538 }
539
5328d8e7 540 for (i = 0; i < ARRAY_SIZE(pfd); i++) {
eb7d0ad0
KZ
541 rc = 0;
542
543 if (pfd[i].revents == 0)
544 continue;
545
546 DBG(PTY, ul_debug(" active pfd[%s].fd=%d %s %s %s",
547 i == POLLFD_STDIN ? "stdin" :
548 i == POLLFD_MASTER ? "master" :
549 i == POLLFD_SIGNAL ? "signal" : "???",
550 pfd[i].fd,
551 pfd[i].revents & POLLIN ? "POLLIN" : "",
552 pfd[i].revents & POLLHUP ? "POLLHUP" : "",
553 pfd[i].revents & POLLERR ? "POLLERR" : ""));
554 switch (i) {
555 case POLLFD_STDIN:
556 case POLLFD_MASTER:
557 /* data */
558 if (pfd[i].revents & POLLIN)
559 rc = pty_handle_io(su, pfd[i].fd, &eof);
560 /* EOF maybe detected by two ways:
561 * A) poll() return POLLHUP event after close()
562 * B) read() returns 0 (no data) */
563 if ((pfd[i].revents & POLLHUP) || eof) {
564 DBG(PTY, ul_debug(" ignore FD"));
565 pfd[i].fd = -1;
eb7d0ad0 566 if (i == POLLFD_STDIN) {
eb7d0ad0
KZ
567 write_eof_to_child(su);
568 DBG(PTY, ul_debug(" ignore STDIN"));
569 }
570 }
571 continue;
572 case POLLFD_SIGNAL:
573 rc = pty_handle_signal(su, pfd[i].fd);
574 break;
575 }
576 if (rc)
577 break;
578 }
579 }
580
e9fde3e9
KZ
581 close(su->pty_sigfd);
582 su->pty_sigfd = -1;
eb7d0ad0
KZ
583 DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
584}
eb7d0ad0
KZ
585#endif /* USE_PTY */
586
587
cf1a99da
KZ
588/* Log the fact that someone has run su to the user given by PW;
589 if SUCCESSFUL is true, they gave the correct password, etc. */
590
032d759a 591static void log_syslog(struct su_context *su, bool successful)
cf1a99da 592{
2260e493
KZ
593 DBG(LOG, ul_debug("syslog logging"));
594
983652ab
KZ
595 openlog(program_invocation_short_name, 0, LOG_AUTH);
596 syslog(LOG_NOTICE, "%s(to %s) %s on %s",
597 successful ? "" :
832f5cd5 598 su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
94c6730b 599 su->new_user, su->old_user ? : "",
302b7b65 600 su->tty_name ? : "none");
983652ab
KZ
601 closelog();
602}
c74a7af1
KZ
603
604/*
605 * Log failed login attempts in _PATH_BTMP if that exists.
606 */
032d759a 607static void log_btmp(struct su_context *su)
c74a7af1 608{
b4b919fe 609 struct utmpx ut;
c74a7af1 610 struct timeval tv;
c74a7af1 611
2260e493
KZ
612 DBG(LOG, ul_debug("btmp logging"));
613
c74a7af1 614 memset(&ut, 0, sizeof(ut));
7f76bc8a 615 str2memcpy(ut.ut_user,
032d759a 616 su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
c74a7af1
KZ
617 sizeof(ut.ut_user));
618
302b7b65 619 if (su->tty_number)
7f76bc8a 620 str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
302b7b65 621 if (su->tty_name)
7f76bc8a 622 str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
c74a7af1 623
c74a7af1
KZ
624 gettimeofday(&tv, NULL);
625 ut.ut_tv.tv_sec = tv.tv_sec;
626 ut.ut_tv.tv_usec = tv.tv_usec;
c74a7af1
KZ
627 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
628 ut.ut_pid = getpid();
629
b4b919fe 630 updwtmpx(_PATH_BTMP, &ut);
c74a7af1
KZ
631}
632
b9a92282
KZ
633static int supam_conv( int num_msg,
634 const struct pam_message **msg,
635 struct pam_response **resp,
636 void *data)
fb4edda7 637{
b9a92282 638 struct su_context *su = (struct su_context *) data;
832f5cd5
KZ
639
640 if (su->suppress_pam_info
b9a92282
KZ
641 && num_msg == 1
642 && msg && msg[0]->msg_style == PAM_TEXT_INFO)
fb4edda7 643 return PAM_SUCCESS;
832f5cd5 644
fe2c9909 645#ifdef HAVE_SECURITY_PAM_MISC_H
b9a92282 646 return misc_conv(num_msg, msg, resp, data);
fe2c9909 647#elif defined(HAVE_SECURITY_OPENPAM_H)
b9a92282 648 return openpam_ttyconv(num_msg, msg, resp, data);
fe2c9909 649#endif
fb4edda7
KZ
650}
651
b9a92282 652static void supam_cleanup(struct su_context *su, int retcode)
cf1a99da 653{
e402d137 654 const int errsv = errno;
cf1a99da 655
2260e493
KZ
656 DBG(PAM, ul_debug("cleanup"));
657
832f5cd5
KZ
658 if (su->pam_has_session)
659 pam_close_session(su->pamh, 0);
832f5cd5
KZ
660 if (su->pam_has_cred)
661 pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
832f5cd5 662 pam_end(su->pamh, retcode);
e402d137 663 errno = errsv;
cf1a99da
KZ
664}
665
cf1a99da 666
b9a92282 667static void supam_export_environment(struct su_context *su)
cf1a99da 668{
2260e493
KZ
669 char **env;
670
671 DBG(PAM, ul_debug("init environ[]"));
672
983652ab 673 /* This is a copy but don't care to free as we exec later anyways. */
2260e493 674 env = pam_getenvlist(su->pamh);
b9a92282 675
983652ab
KZ
676 while (env && *env) {
677 if (putenv(*env) != 0)
b9a92282 678 err(EXIT_FAILURE, _("failed to modify environment"));
983652ab
KZ
679 env++;
680 }
cf1a99da
KZ
681}
682
032d759a 683static void supam_authenticate(struct su_context *su)
dc5bfb71 684{
302b7b65 685 const char *srvname = NULL;
e402d137 686 int rc;
dc5bfb71
KZ
687
688 srvname = su->runuser ?
689 (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) :
690 (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU);
691
2260e493
KZ
692 DBG(PAM, ul_debug("start [name: %s]", srvname));
693
e402d137
KZ
694 rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
695 if (is_pam_failure(rc))
dc5bfb71
KZ
696 goto done;
697
302b7b65 698 if (su->tty_name) {
e402d137
KZ
699 rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
700 if (is_pam_failure(rc))
dc5bfb71
KZ
701 goto done;
702 }
94c6730b 703 if (su->old_user) {
e402d137
KZ
704 rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
705 if (is_pam_failure(rc))
dc5bfb71
KZ
706 goto done;
707 }
dc5bfb71
KZ
708 if (su->runuser) {
709 /*
710 * This is the only difference between runuser(1) and su(1). The command
711 * runuser(1) does not required authentication, because user is root.
712 */
713 if (su->restricted)
714 errx(EXIT_FAILURE, _("may not be used by non-root users"));
715 return;
716 }
717
e402d137
KZ
718 rc = pam_authenticate(su->pamh, 0);
719 if (is_pam_failure(rc))
dc5bfb71
KZ
720 goto done;
721
e402d137
KZ
722 /* Check password expiration and offer option to change it. */
723 rc = pam_acct_mgmt(su->pamh, 0);
724 if (rc == PAM_NEW_AUTHTOK_REQD)
725 rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
dc5bfb71 726 done:
e402d137 727 log_syslog(su, !is_pam_failure(rc));
dc5bfb71 728
e402d137 729 if (is_pam_failure(rc)) {
dc5bfb71
KZ
730 const char *msg;
731
2260e493 732 DBG(PAM, ul_debug("authentication failed"));
032d759a 733 log_btmp(su);
dc5bfb71 734
e402d137
KZ
735 msg = pam_strerror(su->pamh, rc);
736 pam_end(su->pamh, rc);
dc5bfb71
KZ
737 sleep(getlogindefs_num("FAIL_DELAY", 1));
738 errx(EXIT_FAILURE, "%s", msg ? msg : _("incorrect password"));
739 }
740}
741
a4440cd1 742static void supam_open_session(struct su_context *su)
cf1a99da 743{
2260e493
KZ
744 int rc;
745
746 DBG(PAM, ul_debug("opening session"));
983652ab 747
2260e493 748 rc = pam_open_session(su->pamh, 0);
e402d137
KZ
749 if (is_pam_failure(rc)) {
750 supam_cleanup(su, rc);
983652ab 751 errx(EXIT_FAILURE, _("cannot open session: %s"),
e402d137 752 pam_strerror(su->pamh, rc));
983652ab 753 } else
832f5cd5 754 su->pam_has_session = 1;
a4440cd1
KZ
755}
756
305ef556 757static void parent_setup_signals(struct su_context *su)
a4440cd1 758{
a4440cd1 759 sigset_t ourset;
983652ab 760
5fc211d2 761 /*
8ce9c386
KZ
762 * Signals setup
763 *
5fc211d2 764 * 1) block all signals
5fc211d2 765 */
b09e7ea8 766 DBG(SIG, ul_debug("initialize signals"));
b09e7ea8 767
983652ab 768 sigfillset(&ourset);
0214f438 769 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
983652ab
KZ
770 warn(_("cannot block signals"));
771 caught_signal = true;
cf1a99da 772 }
5fc211d2 773
983652ab
KZ
774 if (!caught_signal) {
775 struct sigaction action;
776 action.sa_handler = su_catch_sig;
777 sigemptyset(&action.sa_mask);
778 action.sa_flags = 0;
8ce9c386 779
983652ab 780 sigemptyset(&ourset);
5fc211d2 781
8ce9c386 782 /* 2a) add wanted signals to the mask (for session) */
5fc211d2
KZ
783 if (!su->same_session
784 && (sigaddset(&ourset, SIGINT)
785 || sigaddset(&ourset, SIGQUIT))) {
786
8ce9c386 787 warn(_("cannot initialize signal mask for session"));
5fc211d2 788 caught_signal = true;
983652ab 789 }
8ce9c386 790 /* 2b) add wanted generic signals to the mask */
5fc211d2
KZ
791 if (!caught_signal
792 && (sigaddset(&ourset, SIGTERM)
8ce9c386 793 || sigaddset(&ourset, SIGALRM))) {
5fc211d2 794
8ce9c386 795 warn(_("cannot initialize signal mask"));
983652ab
KZ
796 caught_signal = true;
797 }
8ce9c386
KZ
798
799 /* 3a) set signal handlers (for session) */
5fc211d2
KZ
800 if (!caught_signal
801 && !su->same_session
665f36be
KZ
802 && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX])
803 || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) {
5fc211d2 804
8ce9c386
KZ
805 warn(_("cannot set signal handler for session"));
806 caught_signal = true;
807 }
808
809 /* 3b) set signal handlers */
810 if (!caught_signal
665f36be 811 && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) {
8ce9c386 812
983652ab
KZ
813 warn(_("cannot set signal handler"));
814 caught_signal = true;
815 }
8ce9c386
KZ
816
817 /* 4) unblock wanted signals */
818 if (!caught_signal
819 && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) {
820
821 warn(_("cannot set signal mask"));
822 caught_signal = true;
823 }
983652ab 824 }
305ef556
KZ
825}
826
827
828static void create_watching_parent(struct su_context *su)
829{
830 int status;
831
832 DBG(MISC, ul_debug("forking..."));
ae6e2537 833#ifdef USE_PTY
0214f438
KZ
834 /* no-op, just save original signal mask to oldsig */
835 sigprocmask(SIG_BLOCK, NULL, &su->oldsig);
836
eb7d0ad0 837 if (su->pty)
eb7d0ad0 838 pty_create(su);
ae6e2537 839#endif
eb7d0ad0
KZ
840 fflush(stdout); /* ??? */
841
305ef556
KZ
842 switch ((int) (su->child = fork())) {
843 case -1: /* error */
844 supam_cleanup(su, PAM_ABORT);
ae6e2537 845#ifdef USE_PTY
eb7d0ad0
KZ
846 if (su->pty)
847 pty_cleanup(su);
ae6e2537 848#endif
305ef556
KZ
849 err(EXIT_FAILURE, _("cannot create child process"));
850 break;
851
852 case 0: /* child */
853 return;
854
855 default: /* parent */
856 DBG(MISC, ul_debug("child [pid=%d]", (int) su->child));
857 break;
858 }
859
f4b03edb
KZ
860 /* free unnecessary stuff */
861 free_getlogindefs_data();
305ef556
KZ
862
863 /* In the parent watch the child. */
864
865 /* su without pam support does not have a helper that keeps
866 sitting on any directory so let's go to /. */
867 if (chdir("/") != 0)
868 warn(_("cannot change directory to %s"), "/");
ae6e2537 869#ifdef USE_PTY
eb7d0ad0
KZ
870 if (su->pty)
871 pty_proxy_master(su);
872 else
ae6e2537 873#endif
eb7d0ad0 874 parent_setup_signals(su);
5fc211d2
KZ
875
876 /*
877 * Wait for child
878 */
b09e7ea8 879 if (!caught_signal)
44f36ad1 880 status = wait_for_child(su);
b09e7ea8 881 else
983652ab
KZ
882 status = 1;
883
13ee2f4d
KZ
884 DBG(SIG, ul_debug("final child status=%d", status));
885
44f36ad1 886 if (caught_signal && su->child != (pid_t)-1) {
983652ab 887 fprintf(stderr, _("\nSession terminated, killing shell..."));
44f36ad1 888 kill(su->child, SIGTERM);
dffab154 889 }
8960f3ae 890
b9a92282 891 supam_cleanup(su, PAM_SUCCESS);
983652ab
KZ
892
893 if (caught_signal) {
44f36ad1 894 if (su->child != (pid_t)-1) {
6b283282
KZ
895 DBG(SIG, ul_debug("killing child"));
896 sleep(2);
44f36ad1 897 kill(su->child, SIGKILL);
6b283282
KZ
898 fprintf(stderr, _(" ...killed.\n"));
899 }
983652ab
KZ
900
901 /* Let's terminate itself with the received signal.
902 *
903 * It seems that shells use WIFSIGNALED() rather than our exit status
904 * value to detect situations when is necessary to cleanup (reset)
905 * terminal settings (kzak -- Jun 2013).
906 */
2260e493 907 DBG(SIG, ul_debug("restore signals setting"));
983652ab
KZ
908 switch (caught_signal) {
909 case SIGTERM:
665f36be 910 sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL);
983652ab
KZ
911 break;
912 case SIGINT:
665f36be 913 sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL);
983652ab
KZ
914 break;
915 case SIGQUIT:
665f36be 916 sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL);
983652ab
KZ
917 break;
918 default:
919 /* just in case that signal stuff initialization failed and
920 * caught_signal = true */
921 caught_signal = SIGKILL;
922 break;
923 }
2260e493 924 DBG(SIG, ul_debug("self-send %d signal", caught_signal));
983652ab
KZ
925 kill(getpid(), caught_signal);
926 }
2260e493 927
ae6e2537 928#ifdef USE_PTY
eb7d0ad0
KZ
929 if (su->pty)
930 pty_cleanup(su);
ae6e2537 931#endif
2260e493 932 DBG(MISC, ul_debug("exiting [rc=%d]", status));
983652ab 933 exit(status);
cf1a99da
KZ
934}
935
75efef98
KZ
936/* Adds @name from the current environment to the whitelist. If @name is not
937 * set then nothing is added to the whitelist and returns 1.
938 */
939static int env_whitelist_add(struct su_context *su, const char *name)
940{
941 const char *env = getenv(name);
942
943 if (!env)
944 return 1;
945 if (strv_extend(&su->env_whitelist_names, name))
946 err_oom();
947 if (strv_extend(&su->env_whitelist_vals, env))
948 err_oom();
949 return 0;
950}
951
952static int env_whitelist_setenv(struct su_context *su, int overwrite)
953{
954 char **one;
955 size_t i = 0;
956 int rc;
957
958 STRV_FOREACH(one, su->env_whitelist_names) {
959 rc = setenv(*one, su->env_whitelist_vals[i], overwrite);
960 if (rc)
961 return rc;
962 i++;
963 }
964
965 return 0;
966}
967
968/* Creates (add to) whitelist from comma delimited string */
969static int env_whitelist_from_string(struct su_context *su, const char *str)
970{
971 char **all = strv_split(str, ",");
972 char **one;
973
974 if (!all) {
975 if (errno == ENOMEM)
976 err_oom();
977 return -EINVAL;
978 }
979
980 STRV_FOREACH(one, all)
981 env_whitelist_add(su, *one);
982 strv_free(all);
983 return 0;
984}
985
13396b10 986static void setenv_path(const struct passwd *pw)
cf1a99da 987{
e402d137 988 int rc;
13396b10 989
2260e493
KZ
990 DBG(MISC, ul_debug("setting PATH"));
991
983652ab 992 if (pw->pw_uid)
e402d137 993 rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
cf1a99da 994
e402d137
KZ
995 else if ((rc = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
996 rc = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
cf1a99da 997
e402d137
KZ
998 if (rc)
999 err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
cf1a99da
KZ
1000}
1001
13396b10 1002static void modify_environment(struct su_context *su, const char *shell)
cf1a99da 1003{
032d759a
KZ
1004 const struct passwd *pw = su->pwd;
1005
2260e493
KZ
1006
1007 DBG(MISC, ul_debug("modify environ[]"));
1008
13396b10 1009 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
75efef98
KZ
1010 *
1011 * Unset all other environment variables, but follow
1012 * --whitelist-environment if specified.
13396b10 1013 */
832f5cd5 1014 if (su->simulate_login) {
75efef98
KZ
1015 /* leave TERM unchanged */
1016 env_whitelist_add(su, "TERM");
1017
1018 /* Note that original su(1) has allocated environ[] by malloc
1019 * to the number of expected variables. This seems unnecessary
1020 * optimization as libc later realloc(current_size+2) and for
1021 * empty environ[] the curren_size is zero. It seems better to
1022 * keep all logic around environment in glibc's hands.
1023 * --kzak [Aug 2018]
1024 */
1025#ifdef HAVE_CLEARENV
1026 clearenv();
1027#else
1028 environ = NULL;
1029#endif
1030 /* always reset */
983652ab
KZ
1031 if (shell)
1032 xsetenv("SHELL", shell, 1);
75efef98
KZ
1033
1034 setenv_path(pw);
1035
1036 xsetenv("HOME", pw->pw_dir, 1);
983652ab
KZ
1037 xsetenv("USER", pw->pw_name, 1);
1038 xsetenv("LOGNAME", pw->pw_name, 1);
75efef98
KZ
1039
1040 /* apply all from whitelist, but no overwrite */
1041 env_whitelist_setenv(su, 0);
13396b10
KZ
1042
1043 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
1044 */
1045 } else if (su->change_environment) {
1046 xsetenv("HOME", pw->pw_dir, 1);
1047 if (shell)
1048 xsetenv("SHELL", shell, 1);
1049
1050 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
1051 setenv_path(pw);
1052
1053 if (pw->pw_uid) {
1054 xsetenv("USER", pw->pw_name, 1);
1055 xsetenv("LOGNAME", pw->pw_name, 1);
983652ab
KZ
1056 }
1057 }
1058
b9a92282 1059 supam_export_environment(su);
cf1a99da
KZ
1060}
1061
93031585 1062static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
cf1a99da 1063{
93031585 1064 int rc;
983652ab 1065
2260e493
KZ
1066 DBG(MISC, ul_debug("initialize groups"));
1067
983652ab 1068 errno = 0;
93031585
KZ
1069 if (ngroups)
1070 rc = setgroups(ngroups, groups);
983652ab 1071 else
93031585 1072 rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
983652ab 1073
93031585 1074 if (rc == -1) {
b9a92282 1075 supam_cleanup(su, PAM_ABORT);
983652ab
KZ
1076 err(EXIT_FAILURE, _("cannot set groups"));
1077 }
1078 endgrent();
1079
93031585
KZ
1080 rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
1081 if (is_pam_failure(rc))
1082 errx(EXIT_FAILURE, _("failed to user credentials: %s"),
1083 pam_strerror(su->pamh, rc));
1084 su->pam_has_cred = 1;
cf1a99da
KZ
1085}
1086
93031585 1087static void change_identity(const struct passwd *pw)
cf1a99da 1088{
2260e493
KZ
1089 DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid));
1090
983652ab
KZ
1091 if (setgid(pw->pw_gid))
1092 err(EXIT_FAILURE, _("cannot set group id"));
1093 if (setuid(pw->pw_uid))
1094 err(EXIT_FAILURE, _("cannot set user id"));
cf1a99da
KZ
1095}
1096
581ddd37
KZ
1097/* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
1098 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
1099 * N_ADDITIONAL_ARGS extra arguments.
1100 */
1101static void run_shell(
1102 struct su_context *su,
1103 char const *shell, char const *command, char **additional_args,
1104 size_t n_additional_args)
cf1a99da 1105{
581ddd37 1106 size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
feab5687 1107 const char **args = xcalloc(n_args, sizeof *args);
983652ab
KZ
1108 size_t argno = 1;
1109
242708de
KZ
1110 DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
1111 shell, command,
1112 su->simulate_login ? " login" : "",
1113 su->fast_startup ? " fast-start" : ""));
2260e493 1114
832f5cd5 1115 if (su->simulate_login) {
983652ab
KZ
1116 char *arg0;
1117 char *shell_basename;
1118
1119 shell_basename = basename(shell);
1120 arg0 = xmalloc(strlen(shell_basename) + 2);
1121 arg0[0] = '-';
1122 strcpy(arg0 + 1, shell_basename);
1123 args[0] = arg0;
1124 } else
1125 args[0] = basename(shell);
581ddd37 1126
832f5cd5 1127 if (su->fast_startup)
983652ab
KZ
1128 args[argno++] = "-f";
1129 if (command) {
1130 args[argno++] = "-c";
1131 args[argno++] = command;
1132 }
581ddd37 1133
983652ab
KZ
1134 memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
1135 args[argno + n_additional_args] = NULL;
1136 execv(shell, (char **)args);
3c29b695 1137 errexec(shell);
cf1a99da
KZ
1138}
1139
1140/* Return true if SHELL is a restricted shell (one not returned by
581ddd37
KZ
1141 * getusershell), else false, meaning it is a standard shell.
1142 */
1143static bool is_restricted_shell(const char *shell)
cf1a99da 1144{
983652ab
KZ
1145 char *line;
1146
1147 setusershell();
1148 while ((line = getusershell()) != NULL) {
1149 if (*line != '#' && !strcmp(line, shell)) {
1150 endusershell();
1151 return false;
1152 }
cf1a99da 1153 }
983652ab 1154 endusershell();
242708de 1155
e19db044 1156 DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell));
983652ab 1157 return true;
cf1a99da
KZ
1158}
1159
42be9bda 1160static void usage_common(void)
cf1a99da 1161{
75efef98
KZ
1162 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
1163 fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout);
1164 fputs(USAGE_SEPARATOR, stdout);
1165
42be9bda
KZ
1166 fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
1167 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
1168 fputs(USAGE_SEPARATOR, stdout);
983652ab 1169
42be9bda
KZ
1170 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout);
1171 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
1172 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
1173 " and do not create a new session\n"), stdout);
1174 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
1175 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
927ded6b 1176 fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout);
42be9bda
KZ
1177
1178 fputs(USAGE_SEPARATOR, stdout);
1179 printf(USAGE_HELP_OPTIONS(33));
42be9bda 1180}
983652ab 1181
e192de65 1182static void usage_runuser(void)
42be9bda
KZ
1183{
1184 fputs(USAGE_HEADER, stdout);
1185 fprintf(stdout,
1186 _(" %1$s [options] -u <user> [[--] <command>]\n"
1187 " %1$s [options] [-] [<user> [<argument>...]]\n"),
1188 program_invocation_short_name);
983652ab
KZ
1189
1190 fputs(USAGE_SEPARATOR, stdout);
42be9bda
KZ
1191 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
1192 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
1193 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
1194
1195 fputs(USAGE_OPTIONS, stdout);
1196 fputs(_(" -u, --user <user> username\n"), stdout);
1197 usage_common();
1198 fputs(USAGE_SEPARATOR, stdout);
1199
1200 fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
42be9bda
KZ
1201}
1202
e192de65 1203static void usage_su(void)
42be9bda
KZ
1204{
1205 fputs(USAGE_HEADER, stdout);
1206 fprintf(stdout,
1207 _(" %s [options] [-] [<user> [<argument>...]]\n"),
1208 program_invocation_short_name);
1209
1210 fputs(USAGE_SEPARATOR, stdout);
1211 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
1212 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
1213
1214 fputs(USAGE_OPTIONS, stdout);
1215 usage_common();
1216
1217 fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
42be9bda
KZ
1218}
1219
e192de65 1220static void __attribute__((__noreturn__)) usage(int mode)
42be9bda
KZ
1221{
1222 if (mode == SU_MODE)
1223 usage_su();
1224 else
1225 usage_runuser();
e192de65
KZ
1226
1227 exit(EXIT_SUCCESS);
cf1a99da
KZ
1228}
1229
832f5cd5 1230static void load_config(void *data)
cf1a99da 1231{
832f5cd5 1232 struct su_context *su = (struct su_context *) data;
983652ab 1233
2260e493 1234 DBG(MISC, ul_debug("loading logindefs"));
832f5cd5 1235 logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
983652ab 1236 logindefs_load_file(_PATH_LOGINDEFS);
cf1a99da
KZ
1237}
1238
1239/*
1240 * Returns 1 if the current user is not root
1241 */
13de9b21 1242static int is_not_root(void)
cf1a99da 1243{
983652ab
KZ
1244 const uid_t ruid = getuid();
1245 const uid_t euid = geteuid();
cf1a99da 1246
983652ab
KZ
1247 /* if we're really root and aren't running setuid */
1248 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
cf1a99da
KZ
1249}
1250
e402d137 1251static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
c619d3d1 1252{
983652ab 1253 struct group *gr;
c619d3d1 1254
983652ab
KZ
1255 if (*ngroups >= NGROUPS_MAX)
1256 errx(EXIT_FAILURE,
e402d137
KZ
1257 P_("specifying more than %d supplemental group is not possible",
1258 "specifying more than %d supplemental groups is not possible",
1259 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
c619d3d1 1260
983652ab
KZ
1261 gr = getgrnam(name);
1262 if (!gr)
1263 errx(EXIT_FAILURE, _("group %s does not exist"), name);
c619d3d1 1264
2260e493
KZ
1265 DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid));
1266
983652ab
KZ
1267 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
1268 (*groups)[*ngroups] = gr->gr_gid;
1269 (*ngroups)++;
c619d3d1 1270
983652ab 1271 return gr->gr_gid;
c619d3d1
KZ
1272}
1273
e402d137 1274int su_main(int argc, char **argv, int mode)
cf1a99da 1275{
832f5cd5 1276 struct su_context _su = {
b9a92282 1277 .conv = { supam_conv, NULL },
832f5cd5 1278 .runuser = (mode == RUNUSER_MODE ? 1 : 0),
94c6730b 1279 .change_environment = 1,
eb7d0ad0
KZ
1280 .new_user = DEFAULT_USER,
1281#ifdef USE_PTY
1282 .pty_master = -1,
1283 .pty_slave = -1,
1284 .pty_sigfd = -1,
1285#endif
832f5cd5
KZ
1286 }, *su = &_su;
1287
983652ab 1288 int optc;
983652ab
KZ
1289 char *command = NULL;
1290 int request_same_session = 0;
1291 char *shell = NULL;
983652ab
KZ
1292
1293 gid_t *groups = NULL;
1294 size_t ngroups = 0;
1295 bool use_supp = false;
1296 bool use_gid = false;
1297 gid_t gid = 0;
1298
1299 static const struct option longopts[] = {
1300 {"command", required_argument, NULL, 'c'},
1301 {"session-command", required_argument, NULL, 'C'},
1302 {"fast", no_argument, NULL, 'f'},
1303 {"login", no_argument, NULL, 'l'},
1304 {"preserve-environment", no_argument, NULL, 'p'},
04845ec7 1305 {"pty", no_argument, NULL, 'P'},
983652ab
KZ
1306 {"shell", required_argument, NULL, 's'},
1307 {"group", required_argument, NULL, 'g'},
1308 {"supp-group", required_argument, NULL, 'G'},
1309 {"user", required_argument, NULL, 'u'}, /* runuser only */
75efef98 1310 {"whitelist-environment", required_argument, NULL, 'w'},
983652ab
KZ
1311 {"help", no_argument, 0, 'h'},
1312 {"version", no_argument, 0, 'V'},
1313 {NULL, 0, NULL, 0}
1314 };
75efef98
KZ
1315 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1316 { 'm', 'w' }, /* preserve-environment, whitelist-environment */
1317 { 'p', 'w' }, /* preserve-environment, whitelist-environment */
1318 { 0 }
1319 };
1320 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
983652ab
KZ
1321
1322 setlocale(LC_ALL, "");
1323 bindtextdomain(PACKAGE, LOCALEDIR);
1324 textdomain(PACKAGE);
1325 atexit(close_stdout);
1326
2260e493 1327 su_init_debug();
832f5cd5 1328 su->conv.appdata_ptr = (void *) su;
983652ab
KZ
1329
1330 while ((optc =
75efef98 1331 getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
983652ab 1332 NULL)) != -1) {
75efef98
KZ
1333
1334 err_exclusive_options(optc, longopts, excl, excl_st);
1335
983652ab
KZ
1336 switch (optc) {
1337 case 'c':
1338 command = optarg;
1339 break;
1340
1341 case 'C':
1342 command = optarg;
1343 request_same_session = 1;
1344 break;
1345
1346 case 'f':
832f5cd5 1347 su->fast_startup = true;
983652ab
KZ
1348 break;
1349
1350 case 'g':
1351 use_gid = true;
1352 gid = add_supp_group(optarg, &groups, &ngroups);
1353 break;
1354
1355 case 'G':
1356 use_supp = true;
1357 add_supp_group(optarg, &groups, &ngroups);
1358 break;
1359
1360 case 'l':
832f5cd5 1361 su->simulate_login = true;
983652ab
KZ
1362 break;
1363
1364 case 'm':
1365 case 'p':
832f5cd5 1366 su->change_environment = false;
983652ab
KZ
1367 break;
1368
75efef98
KZ
1369 case 'w':
1370 env_whitelist_from_string(su, optarg);
1371 break;
1372
04845ec7 1373 case 'P':
eb7d0ad0 1374#ifdef USE_PTY
04845ec7 1375 su->pty = 1;
eb7d0ad0 1376#else
ae6e2537 1377 errx(EXIT_FAILURE, _("--pty is not supported for your system"));
eb7d0ad0 1378#endif
04845ec7
KZ
1379 break;
1380
983652ab
KZ
1381 case 's':
1382 shell = optarg;
1383 break;
1384
1385 case 'u':
832f5cd5 1386 if (!su->runuser)
42be9bda 1387 errtryhelp(EXIT_FAILURE);
94c6730b
KZ
1388 su->runuser_uopt = 1;
1389 su->new_user = optarg;
983652ab
KZ
1390 break;
1391
1392 case 'h':
42be9bda 1393 usage(mode);
983652ab
KZ
1394
1395 case 'V':
1396 printf(UTIL_LINUX_VERSION);
1397 exit(EXIT_SUCCESS);
1398
1399 default:
42be9bda 1400 errtryhelp(EXIT_FAILURE);
983652ab
KZ
1401 }
1402 }
cf1a99da 1403
13de9b21 1404 su->restricted = is_not_root();
983652ab
KZ
1405
1406 if (optind < argc && !strcmp(argv[optind], "-")) {
832f5cd5 1407 su->simulate_login = true;
983652ab
KZ
1408 ++optind;
1409 }
1410
832f5cd5 1411 if (su->simulate_login && !su->change_environment) {
983652ab
KZ
1412 warnx(_
1413 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
832f5cd5 1414 su->change_environment = true;
983652ab
KZ
1415 }
1416
832f5cd5 1417 switch (mode) {
983652ab 1418 case RUNUSER_MODE:
e192de65
KZ
1419 /* runuser -u <user> <command>
1420 *
1421 * If -u <user> is not specified, then follow traditional su(1) behavior and
1422 * fallthrough
1423 */
94c6730b
KZ
1424 if (su->runuser_uopt) {
1425 if (shell || su->fast_startup || command || su->simulate_login)
983652ab 1426 errx(EXIT_FAILURE,
94c6730b 1427 _("options --{shell,fast,command,session-command,login} and "
983652ab 1428 "--user are mutually exclusive"));
983652ab 1429 if (optind == argc)
94c6730b 1430 errx(EXIT_FAILURE, _("no command was specified"));
983652ab
KZ
1431 break;
1432 }
e192de65 1433 /* fallthrough */
983652ab
KZ
1434 case SU_MODE:
1435 if (optind < argc)
94c6730b 1436 su->new_user = argv[optind++];
983652ab
KZ
1437 break;
1438 }
1439
832f5cd5 1440 if ((use_supp || use_gid) && su->restricted)
983652ab
KZ
1441 errx(EXIT_FAILURE,
1442 _("only root can specify alternative groups"));
1443
832f5cd5 1444 logindefs_set_loader(load_config, (void *) su);
302b7b65 1445 init_tty(su);
983652ab 1446
94c6730b 1447 su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
032d759a
KZ
1448 if (!su->pwd
1449 || !su->pwd->pw_passwd
1450 || !su->pwd->pw_name || !*su->pwd->pw_name
1451 || !su->pwd->pw_dir || !*su->pwd->pw_dir)
94c6730b
KZ
1452 errx(EXIT_FAILURE, _("user %s does not exist"), su->new_user);
1453
1454 su->new_user = su->pwd->pw_name;
1455 su->old_user = xgetlogin();
983652ab 1456
032d759a
KZ
1457 if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
1458 su->pwd->pw_shell = DEFAULT_SHELL;
983652ab
KZ
1459
1460 if (use_supp && !use_gid)
032d759a 1461 su->pwd->pw_gid = groups[0];
983652ab 1462 else if (use_gid)
032d759a 1463 su->pwd->pw_gid = gid;
983652ab 1464
032d759a 1465 supam_authenticate(su);
983652ab 1466
032d759a 1467 if (request_same_session || !command || !su->pwd->pw_uid)
832f5cd5 1468 su->same_session = 1;
983652ab
KZ
1469
1470 /* initialize shell variable only if "-u <user>" not specified */
94c6730b 1471 if (su->runuser_uopt) {
983652ab
KZ
1472 shell = NULL;
1473 } else {
832f5cd5 1474 if (!shell && !su->change_environment)
983652ab 1475 shell = getenv("SHELL");
581ddd37
KZ
1476
1477 if (shell
1478 && getuid() != 0
1479 && is_restricted_shell(su->pwd->pw_shell)) {
1480 /* The user being su'd to has a nonstandard shell, and
1481 * so is probably a uucp account or has restricted
1482 * access. Don't compromise the account by allowing
1483 * access with a standard shell.
1484 */
032d759a 1485 warnx(_("using restricted shell %s"), su->pwd->pw_shell);
983652ab
KZ
1486 shell = NULL;
1487 }
032d759a 1488 shell = xstrdup(shell ? shell : su->pwd->pw_shell);
cf1a99da 1489 }
cf1a99da 1490
032d759a 1491 init_groups(su, groups, ngroups);
983652ab 1492
832f5cd5
KZ
1493 if (!su->simulate_login || command)
1494 su->suppress_pam_info = 1; /* don't print PAM info messages */
983652ab 1495
a4440cd1
KZ
1496 supam_open_session(su);
1497
832f5cd5 1498 create_watching_parent(su);
983652ab
KZ
1499 /* Now we're in the child. */
1500
032d759a 1501 change_identity(su->pwd);
927ded6b
KZ
1502 if (!su->same_session || su->pty) {
1503 DBG(MISC, ul_debug("call setsid()"));
983652ab 1504 setsid();
927ded6b 1505 }
4365c810 1506#ifdef USE_PTY
eb7d0ad0
KZ
1507 if (su->pty)
1508 pty_init_slave(su);
4365c810 1509#endif
983652ab
KZ
1510 /* Set environment after pam_open_session, which may put KRB5CCNAME
1511 into the pam_env, etc. */
1512
032d759a 1513 modify_environment(su, shell);
983652ab 1514
032d759a
KZ
1515 if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
1516 warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
983652ab
KZ
1517
1518 if (shell)
832f5cd5
KZ
1519 run_shell(su, shell, command, argv + optind, max(0, argc - optind));
1520
1521 execvp(argv[optind], &argv[optind]);
1522 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
983652ab 1523}