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