]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
hwclock: free temporary variable before return
[thirdparty/util-linux.git] / login-utils / su-common.c
1 /*
2 * su(1) for Linux. Run a shell with substitute user and group IDs.
3 *
4 * Copyright (C) 1992-2006 Free Software Foundation, Inc.
5 * Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
6 * Copyright (C) 2016-2017 Karel Zak <kzak@redhat.com>
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2, or (at your option) any later
11 * version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 * more details. You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
19 * USA.
20 *
21 *
22 * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>.
23 */
24 #include <stdio.h>
25 #include <getopt.h>
26 #include <sys/types.h>
27 #include <pwd.h>
28 #include <grp.h>
29 #include <security/pam_appl.h>
30 #ifdef HAVE_SECURITY_PAM_MISC_H
31 # include <security/pam_misc.h>
32 #elif defined(HAVE_SECURITY_OPENPAM_H)
33 # include <security/openpam.h>
34 #endif
35 #include <signal.h>
36 #include <sys/wait.h>
37 #include <syslog.h>
38 #include <utmpx.h>
39
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
48 #include "err.h"
49
50 #include <stdbool.h>
51
52 #include "c.h"
53 #include "xalloc.h"
54 #include "nls.h"
55 #include "pathnames.h"
56 #include "env.h"
57 #include "closestream.h"
58 #include "strv.h"
59 #include "strutils.h"
60 #include "ttyutils.h"
61 #include "pwdutils.h"
62 #include "optutils.h"
63
64 #include "logindefs.h"
65 #include "su-common.h"
66
67 #include "debug.h"
68
69 UL_DEBUG_DEFINE_MASK(su);
70 UL_DEBUG_DEFINE_MASKNAMES(su) = UL_DEBUG_EMPTY_MASKNAMES;
71
72 #define SU_DEBUG_INIT (1 << 1)
73 #define SU_DEBUG_PAM (1 << 2)
74 #define SU_DEBUG_PARENT (1 << 3)
75 #define SU_DEBUG_TTY (1 << 4)
76 #define SU_DEBUG_LOG (1 << 5)
77 #define SU_DEBUG_MISC (1 << 6)
78 #define SU_DEBUG_SIG (1 << 7)
79 #define SU_DEBUG_PTY (1 << 8)
80 #define SU_DEBUG_ALL 0xFFFF
81
82 #define DBG(m, x) __UL_DBG(su, SU_DEBUG_, m, x)
83 #define ON_DBG(m, x) __UL_DBG_CALL(su, SU_DEBUG_, m, x)
84
85
86 /* name of the pam configuration files. separate configs for su and su - */
87 #define PAM_SRVNAME_SU "su"
88 #define PAM_SRVNAME_SU_L "su-l"
89
90 #define PAM_SRVNAME_RUNUSER "runuser"
91 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
92
93 #define _PATH_LOGINDEFS_SU "/etc/default/su"
94 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
95
96 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
97
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
105 extern char **environ;
106 #endif
107
108 enum {
109 SIGTERM_IDX = 0,
110 SIGINT_IDX,
111 SIGQUIT_IDX,
112
113 SIGNALS_IDX_COUNT
114 };
115
116 /*
117 * su/runuser control struct
118 */
119 struct su_context {
120 pam_handle_t *pamh; /* PAM handler */
121 struct pam_conv conv; /* PAM conversation */
122
123 struct passwd *pwd; /* new user info */
124 char *pwdbuf; /* pwd strings */
125
126 const char *tty_name; /* tty_path without /dev prefix */
127 const char *tty_number; /* end of the tty_path */
128
129 char *new_user; /* wanted user */
130 char *old_user; /* original user */
131
132 pid_t child; /* fork() baby */
133 int childstatus; /* wait() status */
134
135 char **env_whitelist_names; /* environment whitelist */
136 char **env_whitelist_vals;
137
138 struct sigaction oldact[SIGNALS_IDX_COUNT]; /* original sigactions indexed by SIG*_IDX */
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 */
147 sigset_t oldsig; /* original signal mask */
148 #endif
149 unsigned int runuser :1, /* flase=su, true=runuser */
150 runuser_uopt :1, /* runuser -u specified */
151 isterm :1, /* is stdin terminal? */
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 */
159 pty :1, /* create pseudo-terminal */
160 restricted :1; /* false for root user */
161 };
162
163
164 static sig_atomic_t volatile caught_signal = false;
165
166 /* Signal handler for parent process. */
167 static void
168 su_catch_sig(int sig)
169 {
170 caught_signal = sig;
171 }
172
173 static void su_init_debug(void)
174 {
175 __UL_INIT_DEBUG_FROM_ENV(su, SU_DEBUG_, 0, SU_DEBUG);
176 }
177
178 static void init_tty(struct su_context *su)
179 {
180 su->isterm = isatty(STDIN_FILENO) ? 1 : 0;
181 DBG(TTY, ul_debug("initialize [is-term=%s]", su->isterm ? "true" : "false"));
182 if (su->isterm)
183 get_terminal_name(NULL, &su->tty_name, &su->tty_number);
184 }
185
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 */
190 static int wait_for_child(struct su_context *su)
191 {
192 pid_t pid = (pid_t) -1;;
193 int status = 0;
194
195 if (su->child == (pid_t) -1)
196 return su->childstatus;
197
198 if (su->child != (pid_t) -1) {
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 */
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)) {
209 DBG(SIG, ul_debug(" child got SIGSTOP -- stop all session"));
210 kill(getpid(), SIGSTOP);
211 /* once we get here, we must have resumed */
212 kill(pid, SIGCONT);
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
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! */
237 su->childstatus = status;
238 } else if (caught_signal)
239 status = caught_signal + 128;
240 else
241 status = 1;
242
243 DBG(SIG, ul_debug("child status=%d", status));
244 return status;
245 }
246
247
248 #ifdef USE_PTY
249 static void pty_init_slave(struct su_context *su)
250 {
251 DBG(PTY, ul_debug("initialize slave"));
252
253 ioctl(su->pty_slave, TIOCSCTTY, 1);
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
272 static 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
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"));
283 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
284 /* create master+slave */
285 rc = openpty(&su->pty_master, &su->pty_slave, NULL, &su->stdin_attrs, &su->win);
286
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);
292 } else {
293 DBG(PTY, ul_debug("create for non-terminal"));
294 rc = openpty(&su->pty_master, &su->pty_slave, NULL, NULL, NULL);
295
296 if (!rc) {
297 tcgetattr(su->pty_slave, &slave_attrs);
298 slave_attrs.c_lflag &= ~ECHO;
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
309 static void pty_cleanup(struct su_context *su)
310 {
311 struct termios rtt;
312
313 if (su->pty_master == -1 || !su->isterm)
314 return;
315
316 DBG(PTY, ul_debug("cleanup"));
317 rtt = su->stdin_attrs;
318 tcsetattr(STDIN_FILENO, TCSADRAIN, &rtt);
319 }
320
321 static 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
334 static 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 */
357 static 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
378 static 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
420 static 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"));
437
438 /* The child terminated or stopped. Note that we ignore SIGCONT
439 * here, because stop/cont semantic is handled by wait_for_child() */
440 if (info.ssi_code == CLD_EXITED
441 || info.ssi_code == CLD_KILLED
442 || info.ssi_code == CLD_DUMPED
443 || info.ssi_status == SIGSTOP)
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;
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
473 static void pty_proxy_master(struct su_context *su)
474 {
475 sigset_t ourset;
476 int rc = 0, ret, eof = 0;
477 enum {
478 POLLFD_SIGNAL = 0,
479 POLLFD_MASTER,
480 POLLFD_STDIN
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);
494 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
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 */
524 ret = poll(pfd, ARRAY_SIZE(pfd), su->poll_timeout);
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
539 for (i = 0; i < ARRAY_SIZE(pfd); i++) {
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;
565 if (i == POLLFD_STDIN) {
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
580 close(su->pty_sigfd);
581 su->pty_sigfd = -1;
582 DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
583 }
584 #endif /* USE_PTY */
585
586
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
590 static void log_syslog(struct su_context *su, bool successful)
591 {
592 DBG(LOG, ul_debug("syslog logging"));
593
594 openlog(program_invocation_short_name, 0, LOG_AUTH);
595 syslog(LOG_NOTICE, "%s(to %s) %s on %s",
596 successful ? "" :
597 su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
598 su->new_user, su->old_user ? : "",
599 su->tty_name ? : "none");
600 closelog();
601 }
602
603 /*
604 * Log failed login attempts in _PATH_BTMP if that exists.
605 */
606 static void log_btmp(struct su_context *su)
607 {
608 struct utmpx ut;
609 struct timeval tv;
610
611 DBG(LOG, ul_debug("btmp logging"));
612
613 memset(&ut, 0, sizeof(ut));
614 str2memcpy(ut.ut_user,
615 su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
616 sizeof(ut.ut_user));
617
618 if (su->tty_number)
619 str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
620 if (su->tty_name)
621 str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
622
623 gettimeofday(&tv, NULL);
624 ut.ut_tv.tv_sec = tv.tv_sec;
625 ut.ut_tv.tv_usec = tv.tv_usec;
626 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
627 ut.ut_pid = getpid();
628
629 updwtmpx(_PATH_BTMP, &ut);
630 }
631
632 static int supam_conv( int num_msg,
633 const struct pam_message **msg,
634 struct pam_response **resp,
635 void *data)
636 {
637 struct su_context *su = (struct su_context *) data;
638
639 if (su->suppress_pam_info
640 && num_msg == 1
641 && msg && msg[0]->msg_style == PAM_TEXT_INFO)
642 return PAM_SUCCESS;
643
644 #ifdef HAVE_SECURITY_PAM_MISC_H
645 return misc_conv(num_msg, msg, resp, data);
646 #elif defined(HAVE_SECURITY_OPENPAM_H)
647 return openpam_ttyconv(num_msg, msg, resp, data);
648 #endif
649 }
650
651 static void supam_cleanup(struct su_context *su, int retcode)
652 {
653 const int errsv = errno;
654
655 DBG(PAM, ul_debug("cleanup"));
656
657 if (su->pam_has_session)
658 pam_close_session(su->pamh, 0);
659 if (su->pam_has_cred)
660 pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
661 pam_end(su->pamh, retcode);
662 errno = errsv;
663 }
664
665
666 static void supam_export_environment(struct su_context *su)
667 {
668 char **env;
669
670 DBG(PAM, ul_debug("init environ[]"));
671
672 /* This is a copy but don't care to free as we exec later anyways. */
673 env = pam_getenvlist(su->pamh);
674
675 while (env && *env) {
676 if (putenv(*env) != 0)
677 err(EXIT_FAILURE, _("failed to modify environment"));
678 env++;
679 }
680 }
681
682 static void supam_authenticate(struct su_context *su)
683 {
684 const char *srvname = NULL;
685 int rc;
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
691 DBG(PAM, ul_debug("start [name: %s]", srvname));
692
693 rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
694 if (is_pam_failure(rc))
695 goto done;
696
697 if (su->tty_name) {
698 rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
699 if (is_pam_failure(rc))
700 goto done;
701 }
702 if (su->old_user) {
703 rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
704 if (is_pam_failure(rc))
705 goto done;
706 }
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
717 rc = pam_authenticate(su->pamh, 0);
718 if (is_pam_failure(rc))
719 goto done;
720
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);
725 done:
726 log_syslog(su, !is_pam_failure(rc));
727
728 if (is_pam_failure(rc)) {
729 const char *msg;
730
731 DBG(PAM, ul_debug("authentication failed"));
732 log_btmp(su);
733
734 msg = pam_strerror(su->pamh, rc);
735 pam_end(su->pamh, rc);
736 sleep(getlogindefs_num("FAIL_DELAY", 1));
737 errx(EXIT_FAILURE, "%s", msg ? msg : _("authentication failed"));
738 }
739 }
740
741 static void supam_open_session(struct su_context *su)
742 {
743 int rc;
744
745 DBG(PAM, ul_debug("opening session"));
746
747 rc = pam_open_session(su->pamh, 0);
748 if (is_pam_failure(rc)) {
749 supam_cleanup(su, rc);
750 errx(EXIT_FAILURE, _("cannot open session: %s"),
751 pam_strerror(su->pamh, rc));
752 } else
753 su->pam_has_session = 1;
754 }
755
756 static void parent_setup_signals(struct su_context *su)
757 {
758 sigset_t ourset;
759
760 /*
761 * Signals setup
762 *
763 * 1) block all signals
764 */
765 DBG(SIG, ul_debug("initialize signals"));
766
767 sigfillset(&ourset);
768 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
769 warn(_("cannot block signals"));
770 caught_signal = true;
771 }
772
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;
778
779 sigemptyset(&ourset);
780
781 /* 2a) add wanted signals to the mask (for session) */
782 if (!su->same_session
783 && (sigaddset(&ourset, SIGINT)
784 || sigaddset(&ourset, SIGQUIT))) {
785
786 warn(_("cannot initialize signal mask for session"));
787 caught_signal = true;
788 }
789 /* 2b) add wanted generic signals to the mask */
790 if (!caught_signal
791 && (sigaddset(&ourset, SIGTERM)
792 || sigaddset(&ourset, SIGALRM))) {
793
794 warn(_("cannot initialize signal mask"));
795 caught_signal = true;
796 }
797
798 /* 3a) set signal handlers (for session) */
799 if (!caught_signal
800 && !su->same_session
801 && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX])
802 || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) {
803
804 warn(_("cannot set signal handler for session"));
805 caught_signal = true;
806 }
807
808 /* 3b) set signal handlers */
809 if (!caught_signal
810 && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) {
811
812 warn(_("cannot set signal handler"));
813 caught_signal = true;
814 }
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 }
823 }
824 }
825
826
827 static void create_watching_parent(struct su_context *su)
828 {
829 int status;
830
831 DBG(MISC, ul_debug("forking..."));
832 #ifdef USE_PTY
833 /* no-op, just save original signal mask to oldsig */
834 sigprocmask(SIG_BLOCK, NULL, &su->oldsig);
835
836 if (su->pty)
837 pty_create(su);
838 #endif
839 fflush(stdout); /* ??? */
840
841 switch ((int) (su->child = fork())) {
842 case -1: /* error */
843 supam_cleanup(su, PAM_ABORT);
844 #ifdef USE_PTY
845 if (su->pty)
846 pty_cleanup(su);
847 #endif
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
859 /* free unnecessary stuff */
860 free_getlogindefs_data();
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"), "/");
868 #ifdef USE_PTY
869 if (su->pty)
870 pty_proxy_master(su);
871 else
872 #endif
873 parent_setup_signals(su);
874
875 /*
876 * Wait for child
877 */
878 if (!caught_signal)
879 status = wait_for_child(su);
880 else
881 status = 1;
882
883 DBG(SIG, ul_debug("final child status=%d", status));
884
885 if (caught_signal && su->child != (pid_t)-1) {
886 fprintf(stderr, _("\nSession terminated, killing shell..."));
887 kill(su->child, SIGTERM);
888 }
889
890 supam_cleanup(su, PAM_SUCCESS);
891
892 if (caught_signal) {
893 if (su->child != (pid_t)-1) {
894 DBG(SIG, ul_debug("killing child"));
895 sleep(2);
896 kill(su->child, SIGKILL);
897 fprintf(stderr, _(" ...killed.\n"));
898 }
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 */
906 DBG(SIG, ul_debug("restore signals setting"));
907 switch (caught_signal) {
908 case SIGTERM:
909 sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL);
910 break;
911 case SIGINT:
912 sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL);
913 break;
914 case SIGQUIT:
915 sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL);
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 }
923 DBG(SIG, ul_debug("self-send %d signal", caught_signal));
924 kill(getpid(), caught_signal);
925 }
926
927 #ifdef USE_PTY
928 if (su->pty)
929 pty_cleanup(su);
930 #endif
931 DBG(MISC, ul_debug("exiting [rc=%d]", status));
932 exit(status);
933 }
934
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 */
938 static 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
951 static 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 */
968 static 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
985 static void setenv_path(const struct passwd *pw)
986 {
987 int rc;
988
989 DBG(MISC, ul_debug("setting PATH"));
990
991 if (pw->pw_uid)
992 rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
993
994 else if ((rc = logindefs_setenv("PATH", "ENV_SUPATH", NULL)) != 0)
995 rc = logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT);
996
997 if (rc)
998 err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
999 }
1000
1001 static void modify_environment(struct su_context *su, const char *shell)
1002 {
1003 const struct passwd *pw = su->pwd;
1004
1005
1006 DBG(MISC, ul_debug("modify environ[]"));
1007
1008 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
1009 *
1010 * Unset all other environment variables, but follow
1011 * --whitelist-environment if specified.
1012 */
1013 if (su->simulate_login) {
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
1019 * optimization as libc later realloc(current_size+2) and for
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 */
1030 if (shell)
1031 xsetenv("SHELL", shell, 1);
1032
1033 setenv_path(pw);
1034
1035 xsetenv("HOME", pw->pw_dir, 1);
1036 xsetenv("USER", pw->pw_name, 1);
1037 xsetenv("LOGNAME", pw->pw_name, 1);
1038
1039 /* apply all from whitelist, but no overwrite */
1040 env_whitelist_setenv(su, 0);
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);
1055 }
1056 }
1057
1058 supam_export_environment(su);
1059 }
1060
1061 static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
1062 {
1063 int rc;
1064
1065 DBG(MISC, ul_debug("initialize groups"));
1066
1067 errno = 0;
1068 if (ngroups)
1069 rc = setgroups(ngroups, groups);
1070 else
1071 rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
1072
1073 if (rc == -1) {
1074 supam_cleanup(su, PAM_ABORT);
1075 err(EXIT_FAILURE, _("cannot set groups"));
1076 }
1077 endgrent();
1078
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;
1084 }
1085
1086 static void change_identity(const struct passwd *pw)
1087 {
1088 DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid));
1089
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"));
1094 }
1095
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 */
1100 static void run_shell(
1101 struct su_context *su,
1102 char const *shell, char const *command, char **additional_args,
1103 size_t n_additional_args)
1104 {
1105 size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
1106 const char **args = xcalloc(n_args, sizeof *args);
1107 size_t argno = 1;
1108
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" : ""));
1113
1114 if (su->simulate_login) {
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);
1125
1126 if (su->fast_startup)
1127 args[argno++] = "-f";
1128 if (command) {
1129 args[argno++] = "-c";
1130 args[argno++] = command;
1131 }
1132
1133 memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
1134 args[argno + n_additional_args] = NULL;
1135 execv(shell, (char **)args);
1136 errexec(shell);
1137 }
1138
1139 /* Return true if SHELL is a restricted shell (one not returned by
1140 * getusershell), else false, meaning it is a standard shell.
1141 */
1142 static bool is_restricted_shell(const char *shell)
1143 {
1144 char *line;
1145
1146 setusershell();
1147 while ((line = getusershell()) != NULL) {
1148 if (*line != '#' && !strcmp(line, shell)) {
1149 endusershell();
1150 return false;
1151 }
1152 }
1153 endusershell();
1154
1155 DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell));
1156 return true;
1157 }
1158
1159 static void usage_common(void)
1160 {
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
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);
1168
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);
1175 fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout);
1176
1177 fputs(USAGE_SEPARATOR, stdout);
1178 printf(USAGE_HELP_OPTIONS(33));
1179 }
1180
1181 static void usage_runuser(void)
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);
1188
1189 fputs(USAGE_SEPARATOR, stdout);
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)"));
1200 }
1201
1202 static void usage_su(void)
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)"));
1217 }
1218
1219 static void __attribute__((__noreturn__)) usage(int mode)
1220 {
1221 if (mode == SU_MODE)
1222 usage_su();
1223 else
1224 usage_runuser();
1225
1226 exit(EXIT_SUCCESS);
1227 }
1228
1229 static void load_config(void *data)
1230 {
1231 struct su_context *su = (struct su_context *) data;
1232
1233 DBG(MISC, ul_debug("loading logindefs"));
1234 logindefs_load_file(_PATH_LOGINDEFS);
1235 logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
1236 }
1237
1238 /*
1239 * Returns 1 if the current user is not root
1240 */
1241 static int is_not_root(void)
1242 {
1243 const uid_t ruid = getuid();
1244 const uid_t euid = geteuid();
1245
1246 /* if we're really root and aren't running setuid */
1247 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
1248 }
1249
1250 static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
1251 {
1252 struct group *gr;
1253
1254 if (*ngroups >= NGROUPS_MAX)
1255 errx(EXIT_FAILURE,
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);
1259
1260 gr = getgrnam(name);
1261 if (!gr)
1262 errx(EXIT_FAILURE, _("group %s does not exist"), name);
1263
1264 DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid));
1265
1266 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
1267 (*groups)[*ngroups] = gr->gr_gid;
1268 (*ngroups)++;
1269
1270 return gr->gr_gid;
1271 }
1272
1273 int su_main(int argc, char **argv, int mode)
1274 {
1275 struct su_context _su = {
1276 .conv = { supam_conv, NULL },
1277 .runuser = (mode == RUNUSER_MODE ? 1 : 0),
1278 .change_environment = 1,
1279 .new_user = DEFAULT_USER,
1280 #ifdef USE_PTY
1281 .pty_master = -1,
1282 .pty_slave = -1,
1283 .pty_sigfd = -1,
1284 #endif
1285 }, *su = &_su;
1286
1287 int optc;
1288 char *command = NULL;
1289 int request_same_session = 0;
1290 char *shell = NULL;
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'},
1304 {"pty", no_argument, NULL, 'P'},
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 */
1309 {"whitelist-environment", required_argument, NULL, 'w'},
1310 {"help", no_argument, 0, 'h'},
1311 {"version", no_argument, 0, 'V'},
1312 {NULL, 0, NULL, 0}
1313 };
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;
1320
1321 setlocale(LC_ALL, "");
1322 bindtextdomain(PACKAGE, LOCALEDIR);
1323 textdomain(PACKAGE);
1324 close_stdout_atexit();
1325
1326 su_init_debug();
1327 su->conv.appdata_ptr = (void *) su;
1328
1329 while ((optc =
1330 getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
1331 NULL)) != -1) {
1332
1333 err_exclusive_options(optc, longopts, excl, excl_st);
1334
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':
1346 su->fast_startup = true;
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':
1360 su->simulate_login = true;
1361 break;
1362
1363 case 'm':
1364 case 'p':
1365 su->change_environment = false;
1366 break;
1367
1368 case 'w':
1369 env_whitelist_from_string(su, optarg);
1370 break;
1371
1372 case 'P':
1373 #ifdef USE_PTY
1374 su->pty = 1;
1375 #else
1376 errx(EXIT_FAILURE, _("--pty is not supported for your system"));
1377 #endif
1378 break;
1379
1380 case 's':
1381 shell = optarg;
1382 break;
1383
1384 case 'u':
1385 if (!su->runuser)
1386 errtryhelp(EXIT_FAILURE);
1387 su->runuser_uopt = 1;
1388 su->new_user = optarg;
1389 break;
1390
1391 case 'h':
1392 usage(mode);
1393
1394 case 'V':
1395 print_version(EXIT_SUCCESS);
1396 default:
1397 errtryhelp(EXIT_FAILURE);
1398 }
1399 }
1400
1401 su->restricted = is_not_root();
1402
1403 if (optind < argc && !strcmp(argv[optind], "-")) {
1404 su->simulate_login = true;
1405 ++optind;
1406 }
1407
1408 if (su->simulate_login && !su->change_environment) {
1409 warnx(_
1410 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
1411 su->change_environment = true;
1412 }
1413
1414 switch (mode) {
1415 case RUNUSER_MODE:
1416 /* runuser -u <user> <command>
1417 *
1418 * If -u <user> is not specified, then follow traditional su(1) behavior and
1419 * fallthrough
1420 */
1421 if (su->runuser_uopt) {
1422 if (shell || su->fast_startup || command || su->simulate_login)
1423 errx(EXIT_FAILURE,
1424 _("options --{shell,fast,command,session-command,login} and "
1425 "--user are mutually exclusive"));
1426 if (optind == argc)
1427 errx(EXIT_FAILURE, _("no command was specified"));
1428 break;
1429 }
1430 /* fallthrough */
1431 case SU_MODE:
1432 if (optind < argc)
1433 su->new_user = argv[optind++];
1434 break;
1435 }
1436
1437 if ((use_supp || use_gid) && su->restricted)
1438 errx(EXIT_FAILURE,
1439 _("only root can specify alternative groups"));
1440
1441 logindefs_set_loader(load_config, (void *) su);
1442 init_tty(su);
1443
1444 su->pwd = xgetpwnam(su->new_user, &su->pwdbuf);
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)
1449 errx(EXIT_FAILURE, _("user %s does not exist"), su->new_user);
1450
1451 su->new_user = su->pwd->pw_name;
1452 su->old_user = xgetlogin();
1453
1454 if (!su->pwd->pw_shell || !*su->pwd->pw_shell)
1455 su->pwd->pw_shell = DEFAULT_SHELL;
1456
1457 if (use_supp && !use_gid)
1458 su->pwd->pw_gid = groups[0];
1459 else if (use_gid)
1460 su->pwd->pw_gid = gid;
1461
1462 supam_authenticate(su);
1463
1464 if (request_same_session || !command || !su->pwd->pw_uid)
1465 su->same_session = 1;
1466
1467 /* initialize shell variable only if "-u <user>" not specified */
1468 if (su->runuser_uopt) {
1469 shell = NULL;
1470 } else {
1471 if (!shell && !su->change_environment)
1472 shell = getenv("SHELL");
1473
1474 if (shell
1475 && getuid() != 0
1476 && is_restricted_shell(su->pwd->pw_shell)) {
1477 /* The user being su'd to has a nonstandard shell, and
1478 * so is probably a uucp account or has restricted
1479 * access. Don't compromise the account by allowing
1480 * access with a standard shell.
1481 */
1482 warnx(_("using restricted shell %s"), su->pwd->pw_shell);
1483 shell = NULL;
1484 }
1485 shell = xstrdup(shell ? shell : su->pwd->pw_shell);
1486 }
1487
1488 init_groups(su, groups, ngroups);
1489
1490 if (!su->simulate_login || command)
1491 su->suppress_pam_info = 1; /* don't print PAM info messages */
1492
1493 supam_open_session(su);
1494
1495 create_watching_parent(su);
1496 /* Now we're in the child. */
1497
1498 change_identity(su->pwd);
1499 if (!su->same_session || su->pty) {
1500 DBG(MISC, ul_debug("call setsid()"));
1501 setsid();
1502 }
1503 #ifdef USE_PTY
1504 if (su->pty)
1505 pty_init_slave(su);
1506 #endif
1507 /* Set environment after pam_open_session, which may put KRB5CCNAME
1508 into the pam_env, etc. */
1509
1510 modify_environment(su, shell);
1511
1512 if (su->simulate_login && chdir(su->pwd->pw_dir) != 0)
1513 warn(_("warning: cannot change directory to %s"), su->pwd->pw_dir);
1514
1515 if (shell)
1516 run_shell(su, shell, command, argv + optind, max(0, argc - optind));
1517
1518 execvp(argv[optind], &argv[optind]);
1519 err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]);
1520 }