]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su-common.c
su: cleanup code to copy to log strings
[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 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 */
299 if (!rc) {
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
312 static 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
322 static 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
335 static 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 */
358 static 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
379 static 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
421 static 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"));
438
439 /* The child terminated or stopped. Note that we ignore SIGCONT
440 * here, because stop/cont semantic is handled by wait_for_child() */
441 if (info.ssi_code == CLD_EXITED || info.ssi_status == SIGSTOP)
442 wait_for_child(su);
443 /* The child is dead, force poll() timeout. */
444 if (su->child == (pid_t) -1)
445 su->poll_timeout = 10;
446 return 0;
447 case SIGWINCH:
448 DBG(SIG, ul_debug(" get signal SIGWINCH"));
449 if (su->isterm) {
450 ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&su->win);
451 ioctl(su->pty_slave, TIOCSWINSZ, (char *)&su->win);
452 }
453 break;
454 case SIGTERM:
455 /* fallthrough */
456 case SIGINT:
457 /* fallthrough */
458 case SIGQUIT:
459 DBG(SIG, ul_debug(" get signal SIG{TERM,INT,QUIT}"));
460 caught_signal = info.ssi_signo;
461 /* Child termination is going to generate SIGCHILD (see above) */
462 kill(su->child, SIGTERM);
463 break;
464 default:
465 abort();
466 }
467
468 return 0;
469 }
470
471 static void pty_proxy_master(struct su_context *su)
472 {
473 sigset_t ourset;
474 int rc = 0, ret, eof = 0;
475 enum {
476 POLLFD_SIGNAL = 0,
477 POLLFD_MASTER,
478 POLLFD_STDIN
479
480 };
481 struct pollfd pfd[] = {
482 [POLLFD_SIGNAL] = { .fd = -1, .events = POLLIN | POLLERR | POLLHUP },
483 [POLLFD_MASTER] = { .fd = su->pty_master, .events = POLLIN | POLLERR | POLLHUP },
484 [POLLFD_STDIN] = { .fd = STDIN_FILENO, .events = POLLIN | POLLERR | POLLHUP }
485 };
486
487 /* for PTY mode we use signalfd
488 *
489 * TODO: script(1) initializes this FD before fork, good or bad idea?
490 */
491 sigfillset(&ourset);
492 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
493 warn(_("cannot block signals"));
494 caught_signal = true;
495 return;
496 }
497
498 sigemptyset(&ourset);
499 sigaddset(&ourset, SIGCHLD);
500 sigaddset(&ourset, SIGWINCH);
501 sigaddset(&ourset, SIGALRM);
502 sigaddset(&ourset, SIGTERM);
503 sigaddset(&ourset, SIGINT);
504 sigaddset(&ourset, SIGQUIT);
505
506 if ((su->pty_sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) {
507 warn(("cannot create signal file descriptor"));
508 caught_signal = true;
509 return;
510 }
511
512 pfd[POLLFD_SIGNAL].fd = su->pty_sigfd;
513 su->poll_timeout = -1;
514
515 while (!caught_signal) {
516 size_t i;
517 int errsv;
518
519 DBG(PTY, ul_debug("calling poll()"));
520
521 /* wait for input or signal */
522 ret = poll(pfd, ARRAY_SIZE(pfd), su->poll_timeout);
523 errsv = errno;
524 DBG(PTY, ul_debug("poll() rc=%d", ret));
525
526 if (ret < 0) {
527 if (errsv == EAGAIN)
528 continue;
529 warn(_("poll failed"));
530 break;
531 }
532 if (ret == 0) {
533 DBG(PTY, ul_debug("leaving poll() loop [timeout=%d]", su->poll_timeout));
534 break;
535 }
536
537 for (i = 0; i < ARRAY_SIZE(pfd); i++) {
538 rc = 0;
539
540 if (pfd[i].revents == 0)
541 continue;
542
543 DBG(PTY, ul_debug(" active pfd[%s].fd=%d %s %s %s",
544 i == POLLFD_STDIN ? "stdin" :
545 i == POLLFD_MASTER ? "master" :
546 i == POLLFD_SIGNAL ? "signal" : "???",
547 pfd[i].fd,
548 pfd[i].revents & POLLIN ? "POLLIN" : "",
549 pfd[i].revents & POLLHUP ? "POLLHUP" : "",
550 pfd[i].revents & POLLERR ? "POLLERR" : ""));
551 switch (i) {
552 case POLLFD_STDIN:
553 case POLLFD_MASTER:
554 /* data */
555 if (pfd[i].revents & POLLIN)
556 rc = pty_handle_io(su, pfd[i].fd, &eof);
557 /* EOF maybe detected by two ways:
558 * A) poll() return POLLHUP event after close()
559 * B) read() returns 0 (no data) */
560 if ((pfd[i].revents & POLLHUP) || eof) {
561 DBG(PTY, ul_debug(" ignore FD"));
562 pfd[i].fd = -1;
563 if (i == POLLFD_STDIN) {
564 write_eof_to_child(su);
565 DBG(PTY, ul_debug(" ignore STDIN"));
566 }
567 }
568 continue;
569 case POLLFD_SIGNAL:
570 rc = pty_handle_signal(su, pfd[i].fd);
571 break;
572 }
573 if (rc)
574 break;
575 }
576 }
577
578 close(su->pty_sigfd);
579 su->pty_sigfd = -1;
580 DBG(PTY, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal, rc));
581 }
582 #endif /* USE_PTY */
583
584
585 /* Log the fact that someone has run su to the user given by PW;
586 if SUCCESSFUL is true, they gave the correct password, etc. */
587
588 static void log_syslog(struct su_context *su, bool successful)
589 {
590 DBG(LOG, ul_debug("syslog logging"));
591
592 openlog(program_invocation_short_name, 0, LOG_AUTH);
593 syslog(LOG_NOTICE, "%s(to %s) %s on %s",
594 successful ? "" :
595 su->runuser ? "FAILED RUNUSER " : "FAILED SU ",
596 su->new_user, su->old_user ? : "",
597 su->tty_name ? : "none");
598 closelog();
599 }
600
601 /*
602 * Log failed login attempts in _PATH_BTMP if that exists.
603 */
604 static void log_btmp(struct su_context *su)
605 {
606 struct utmpx ut;
607 struct timeval tv;
608
609 DBG(LOG, ul_debug("btmp logging"));
610
611 memset(&ut, 0, sizeof(ut));
612 str2memcpy(ut.ut_user,
613 su->pwd && su->pwd->pw_name ? su->pwd->pw_name : "(unknown)",
614 sizeof(ut.ut_user));
615
616 if (su->tty_number)
617 str2memcpy(ut.ut_id, su->tty_number, sizeof(ut.ut_id));
618 if (su->tty_name)
619 str2memcpy(ut.ut_line, su->tty_name, sizeof(ut.ut_line));
620
621 gettimeofday(&tv, NULL);
622 ut.ut_tv.tv_sec = tv.tv_sec;
623 ut.ut_tv.tv_usec = tv.tv_usec;
624 ut.ut_type = LOGIN_PROCESS; /* XXX doesn't matter */
625 ut.ut_pid = getpid();
626
627 updwtmpx(_PATH_BTMP, &ut);
628 }
629
630 static int supam_conv( int num_msg,
631 const struct pam_message **msg,
632 struct pam_response **resp,
633 void *data)
634 {
635 struct su_context *su = (struct su_context *) data;
636
637 if (su->suppress_pam_info
638 && num_msg == 1
639 && msg && msg[0]->msg_style == PAM_TEXT_INFO)
640 return PAM_SUCCESS;
641
642 #ifdef HAVE_SECURITY_PAM_MISC_H
643 return misc_conv(num_msg, msg, resp, data);
644 #elif defined(HAVE_SECURITY_OPENPAM_H)
645 return openpam_ttyconv(num_msg, msg, resp, data);
646 #endif
647 }
648
649 static void supam_cleanup(struct su_context *su, int retcode)
650 {
651 const int errsv = errno;
652
653 DBG(PAM, ul_debug("cleanup"));
654
655 if (su->pam_has_session)
656 pam_close_session(su->pamh, 0);
657 if (su->pam_has_cred)
658 pam_setcred(su->pamh, PAM_DELETE_CRED | PAM_SILENT);
659 pam_end(su->pamh, retcode);
660 errno = errsv;
661 }
662
663
664 static void supam_export_environment(struct su_context *su)
665 {
666 char **env;
667
668 DBG(PAM, ul_debug("init environ[]"));
669
670 /* This is a copy but don't care to free as we exec later anyways. */
671 env = pam_getenvlist(su->pamh);
672
673 while (env && *env) {
674 if (putenv(*env) != 0)
675 err(EXIT_FAILURE, _("failed to modify environment"));
676 env++;
677 }
678 }
679
680 static void supam_authenticate(struct su_context *su)
681 {
682 const char *srvname = NULL;
683 int rc;
684
685 srvname = su->runuser ?
686 (su->simulate_login ? PAM_SRVNAME_RUNUSER_L : PAM_SRVNAME_RUNUSER) :
687 (su->simulate_login ? PAM_SRVNAME_SU_L : PAM_SRVNAME_SU);
688
689 DBG(PAM, ul_debug("start [name: %s]", srvname));
690
691 rc = pam_start(srvname, su->pwd->pw_name, &su->conv, &su->pamh);
692 if (is_pam_failure(rc))
693 goto done;
694
695 if (su->tty_name) {
696 rc = pam_set_item(su->pamh, PAM_TTY, su->tty_name);
697 if (is_pam_failure(rc))
698 goto done;
699 }
700 if (su->old_user) {
701 rc = pam_set_item(su->pamh, PAM_RUSER, (const void *) su->old_user);
702 if (is_pam_failure(rc))
703 goto done;
704 }
705 if (su->runuser) {
706 /*
707 * This is the only difference between runuser(1) and su(1). The command
708 * runuser(1) does not required authentication, because user is root.
709 */
710 if (su->restricted)
711 errx(EXIT_FAILURE, _("may not be used by non-root users"));
712 return;
713 }
714
715 rc = pam_authenticate(su->pamh, 0);
716 if (is_pam_failure(rc))
717 goto done;
718
719 /* Check password expiration and offer option to change it. */
720 rc = pam_acct_mgmt(su->pamh, 0);
721 if (rc == PAM_NEW_AUTHTOK_REQD)
722 rc = pam_chauthtok(su->pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
723 done:
724 log_syslog(su, !is_pam_failure(rc));
725
726 if (is_pam_failure(rc)) {
727 const char *msg;
728
729 DBG(PAM, ul_debug("authentication failed"));
730 log_btmp(su);
731
732 msg = pam_strerror(su->pamh, rc);
733 pam_end(su->pamh, rc);
734 sleep(getlogindefs_num("FAIL_DELAY", 1));
735 errx(EXIT_FAILURE, "%s", msg ? msg : _("incorrect password"));
736 }
737 }
738
739 static void supam_open_session(struct su_context *su)
740 {
741 int rc;
742
743 DBG(PAM, ul_debug("opening session"));
744
745 rc = pam_open_session(su->pamh, 0);
746 if (is_pam_failure(rc)) {
747 supam_cleanup(su, rc);
748 errx(EXIT_FAILURE, _("cannot open session: %s"),
749 pam_strerror(su->pamh, rc));
750 } else
751 su->pam_has_session = 1;
752 }
753
754 static void parent_setup_signals(struct su_context *su)
755 {
756 sigset_t ourset;
757
758 /*
759 * Signals setup
760 *
761 * 1) block all signals
762 */
763 DBG(SIG, ul_debug("initialize signals"));
764
765 sigfillset(&ourset);
766 if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
767 warn(_("cannot block signals"));
768 caught_signal = true;
769 }
770
771 if (!caught_signal) {
772 struct sigaction action;
773 action.sa_handler = su_catch_sig;
774 sigemptyset(&action.sa_mask);
775 action.sa_flags = 0;
776
777 sigemptyset(&ourset);
778
779 /* 2a) add wanted signals to the mask (for session) */
780 if (!su->same_session
781 && (sigaddset(&ourset, SIGINT)
782 || sigaddset(&ourset, SIGQUIT))) {
783
784 warn(_("cannot initialize signal mask for session"));
785 caught_signal = true;
786 }
787 /* 2b) add wanted generic signals to the mask */
788 if (!caught_signal
789 && (sigaddset(&ourset, SIGTERM)
790 || sigaddset(&ourset, SIGALRM))) {
791
792 warn(_("cannot initialize signal mask"));
793 caught_signal = true;
794 }
795
796 /* 3a) set signal handlers (for session) */
797 if (!caught_signal
798 && !su->same_session
799 && (sigaction(SIGINT, &action, &su->oldact[SIGINT_IDX])
800 || sigaction(SIGQUIT, &action, &su->oldact[SIGQUIT_IDX]))) {
801
802 warn(_("cannot set signal handler for session"));
803 caught_signal = true;
804 }
805
806 /* 3b) set signal handlers */
807 if (!caught_signal
808 && sigaction(SIGTERM, &action, &su->oldact[SIGTERM_IDX])) {
809
810 warn(_("cannot set signal handler"));
811 caught_signal = true;
812 }
813
814 /* 4) unblock wanted signals */
815 if (!caught_signal
816 && sigprocmask(SIG_UNBLOCK, &ourset, NULL)) {
817
818 warn(_("cannot set signal mask"));
819 caught_signal = true;
820 }
821 }
822 }
823
824
825 static void create_watching_parent(struct su_context *su)
826 {
827 int status;
828
829 DBG(MISC, ul_debug("forking..."));
830 #ifdef USE_PTY
831 /* no-op, just save original signal mask to oldsig */
832 sigprocmask(SIG_BLOCK, NULL, &su->oldsig);
833
834 if (su->pty)
835 pty_create(su);
836 #endif
837 fflush(stdout); /* ??? */
838
839 switch ((int) (su->child = fork())) {
840 case -1: /* error */
841 supam_cleanup(su, PAM_ABORT);
842 #ifdef USE_PTY
843 if (su->pty)
844 pty_cleanup(su);
845 #endif
846 err(EXIT_FAILURE, _("cannot create child process"));
847 break;
848
849 case 0: /* child */
850 return;
851
852 default: /* parent */
853 DBG(MISC, ul_debug("child [pid=%d]", (int) su->child));
854 break;
855 }
856
857 /* free unnecessary stuff */
858 free_getlogindefs_data();
859
860 /* In the parent watch the child. */
861
862 /* su without pam support does not have a helper that keeps
863 sitting on any directory so let's go to /. */
864 if (chdir("/") != 0)
865 warn(_("cannot change directory to %s"), "/");
866 #ifdef USE_PTY
867 if (su->pty)
868 pty_proxy_master(su);
869 else
870 #endif
871 parent_setup_signals(su);
872
873 /*
874 * Wait for child
875 */
876 if (!caught_signal)
877 status = wait_for_child(su);
878 else
879 status = 1;
880
881 DBG(SIG, ul_debug("final child status=%d", status));
882
883 if (caught_signal && su->child != (pid_t)-1) {
884 fprintf(stderr, _("\nSession terminated, killing shell..."));
885 kill(su->child, SIGTERM);
886 }
887
888 supam_cleanup(su, PAM_SUCCESS);
889
890 if (caught_signal) {
891 if (su->child != (pid_t)-1) {
892 DBG(SIG, ul_debug("killing child"));
893 sleep(2);
894 kill(su->child, SIGKILL);
895 fprintf(stderr, _(" ...killed.\n"));
896 }
897
898 /* Let's terminate itself with the received signal.
899 *
900 * It seems that shells use WIFSIGNALED() rather than our exit status
901 * value to detect situations when is necessary to cleanup (reset)
902 * terminal settings (kzak -- Jun 2013).
903 */
904 DBG(SIG, ul_debug("restore signals setting"));
905 switch (caught_signal) {
906 case SIGTERM:
907 sigaction(SIGTERM, &su->oldact[SIGTERM_IDX], NULL);
908 break;
909 case SIGINT:
910 sigaction(SIGINT, &su->oldact[SIGINT_IDX], NULL);
911 break;
912 case SIGQUIT:
913 sigaction(SIGQUIT, &su->oldact[SIGQUIT_IDX], NULL);
914 break;
915 default:
916 /* just in case that signal stuff initialization failed and
917 * caught_signal = true */
918 caught_signal = SIGKILL;
919 break;
920 }
921 DBG(SIG, ul_debug("self-send %d signal", caught_signal));
922 kill(getpid(), caught_signal);
923 }
924
925 #ifdef USE_PTY
926 if (su->pty)
927 pty_cleanup(su);
928 #endif
929 DBG(MISC, ul_debug("exiting [rc=%d]", status));
930 exit(status);
931 }
932
933 /* Adds @name from the current environment to the whitelist. If @name is not
934 * set then nothing is added to the whitelist and returns 1.
935 */
936 static int env_whitelist_add(struct su_context *su, const char *name)
937 {
938 const char *env = getenv(name);
939
940 if (!env)
941 return 1;
942 if (strv_extend(&su->env_whitelist_names, name))
943 err_oom();
944 if (strv_extend(&su->env_whitelist_vals, env))
945 err_oom();
946 return 0;
947 }
948
949 static int env_whitelist_setenv(struct su_context *su, int overwrite)
950 {
951 char **one;
952 size_t i = 0;
953 int rc;
954
955 STRV_FOREACH(one, su->env_whitelist_names) {
956 rc = setenv(*one, su->env_whitelist_vals[i], overwrite);
957 if (rc)
958 return rc;
959 i++;
960 }
961
962 return 0;
963 }
964
965 /* Creates (add to) whitelist from comma delimited string */
966 static int env_whitelist_from_string(struct su_context *su, const char *str)
967 {
968 char **all = strv_split(str, ",");
969 char **one;
970
971 if (!all) {
972 if (errno == ENOMEM)
973 err_oom();
974 return -EINVAL;
975 }
976
977 STRV_FOREACH(one, all)
978 env_whitelist_add(su, *one);
979 strv_free(all);
980 return 0;
981 }
982
983 static void setenv_path(const struct passwd *pw)
984 {
985 int rc;
986
987 DBG(MISC, ul_debug("setting PATH"));
988
989 if (pw->pw_uid)
990 rc = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
991
992 else if ((rc = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
993 rc = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
994
995 if (rc)
996 err(EXIT_FAILURE, _("failed to set the PATH environment variable"));
997 }
998
999 static void modify_environment(struct su_context *su, const char *shell)
1000 {
1001 const struct passwd *pw = su->pwd;
1002
1003
1004 DBG(MISC, ul_debug("modify environ[]"));
1005
1006 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
1007 *
1008 * Unset all other environment variables, but follow
1009 * --whitelist-environment if specified.
1010 */
1011 if (su->simulate_login) {
1012 /* leave TERM unchanged */
1013 env_whitelist_add(su, "TERM");
1014
1015 /* Note that original su(1) has allocated environ[] by malloc
1016 * to the number of expected variables. This seems unnecessary
1017 * optimization as libc later realloc(current_size+2) and for
1018 * empty environ[] the curren_size is zero. It seems better to
1019 * keep all logic around environment in glibc's hands.
1020 * --kzak [Aug 2018]
1021 */
1022 #ifdef HAVE_CLEARENV
1023 clearenv();
1024 #else
1025 environ = NULL;
1026 #endif
1027 /* always reset */
1028 if (shell)
1029 xsetenv("SHELL", shell, 1);
1030
1031 setenv_path(pw);
1032
1033 xsetenv("HOME", pw->pw_dir, 1);
1034 xsetenv("USER", pw->pw_name, 1);
1035 xsetenv("LOGNAME", pw->pw_name, 1);
1036
1037 /* apply all from whitelist, but no overwrite */
1038 env_whitelist_setenv(su, 0);
1039
1040 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
1041 */
1042 } else if (su->change_environment) {
1043 xsetenv("HOME", pw->pw_dir, 1);
1044 if (shell)
1045 xsetenv("SHELL", shell, 1);
1046
1047 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
1048 setenv_path(pw);
1049
1050 if (pw->pw_uid) {
1051 xsetenv("USER", pw->pw_name, 1);
1052 xsetenv("LOGNAME", pw->pw_name, 1);
1053 }
1054 }
1055
1056 supam_export_environment(su);
1057 }
1058
1059 static void init_groups(struct su_context *su, gid_t *groups, size_t ngroups)
1060 {
1061 int rc;
1062
1063 DBG(MISC, ul_debug("initialize groups"));
1064
1065 errno = 0;
1066 if (ngroups)
1067 rc = setgroups(ngroups, groups);
1068 else
1069 rc = initgroups(su->pwd->pw_name, su->pwd->pw_gid);
1070
1071 if (rc == -1) {
1072 supam_cleanup(su, PAM_ABORT);
1073 err(EXIT_FAILURE, _("cannot set groups"));
1074 }
1075 endgrent();
1076
1077 rc = pam_setcred(su->pamh, PAM_ESTABLISH_CRED);
1078 if (is_pam_failure(rc))
1079 errx(EXIT_FAILURE, _("failed to user credentials: %s"),
1080 pam_strerror(su->pamh, rc));
1081 su->pam_has_cred = 1;
1082 }
1083
1084 static void change_identity(const struct passwd *pw)
1085 {
1086 DBG(MISC, ul_debug("changing identity [GID=%d, UID=%d]", pw->pw_gid, pw->pw_uid));
1087
1088 if (setgid(pw->pw_gid))
1089 err(EXIT_FAILURE, _("cannot set group id"));
1090 if (setuid(pw->pw_uid))
1091 err(EXIT_FAILURE, _("cannot set user id"));
1092 }
1093
1094 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
1095 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
1096 * N_ADDITIONAL_ARGS extra arguments.
1097 */
1098 static void run_shell(
1099 struct su_context *su,
1100 char const *shell, char const *command, char **additional_args,
1101 size_t n_additional_args)
1102 {
1103 size_t n_args = 1 + su->fast_startup + 2 * ! !command + n_additional_args + 1;
1104 const char **args = xcalloc(n_args, sizeof *args);
1105 size_t argno = 1;
1106
1107 DBG(MISC, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
1108 shell, command,
1109 su->simulate_login ? " login" : "",
1110 su->fast_startup ? " fast-start" : ""));
1111
1112 if (su->simulate_login) {
1113 char *arg0;
1114 char *shell_basename;
1115
1116 shell_basename = basename(shell);
1117 arg0 = xmalloc(strlen(shell_basename) + 2);
1118 arg0[0] = '-';
1119 strcpy(arg0 + 1, shell_basename);
1120 args[0] = arg0;
1121 } else
1122 args[0] = basename(shell);
1123
1124 if (su->fast_startup)
1125 args[argno++] = "-f";
1126 if (command) {
1127 args[argno++] = "-c";
1128 args[argno++] = command;
1129 }
1130
1131 memcpy(args + argno, additional_args, n_additional_args * sizeof *args);
1132 args[argno + n_additional_args] = NULL;
1133 execv(shell, (char **)args);
1134 errexec(shell);
1135 }
1136
1137 /* Return true if SHELL is a restricted shell (one not returned by
1138 * getusershell), else false, meaning it is a standard shell.
1139 */
1140 static bool is_restricted_shell(const char *shell)
1141 {
1142 char *line;
1143
1144 setusershell();
1145 while ((line = getusershell()) != NULL) {
1146 if (*line != '#' && !strcmp(line, shell)) {
1147 endusershell();
1148 return false;
1149 }
1150 }
1151 endusershell();
1152
1153 DBG(MISC, ul_debug("%s is restricted shell (not in /etc/shells)", shell));
1154 return true;
1155 }
1156
1157 static void usage_common(void)
1158 {
1159 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout);
1160 fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout);
1161 fputs(USAGE_SEPARATOR, stdout);
1162
1163 fputs(_(" -g, --group <group> specify the primary group\n"), stdout);
1164 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout);
1165 fputs(USAGE_SEPARATOR, stdout);
1166
1167 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout);
1168 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout);
1169 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
1170 " and do not create a new session\n"), stdout);
1171 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout);
1172 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout);
1173 fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout);
1174
1175 fputs(USAGE_SEPARATOR, stdout);
1176 printf(USAGE_HELP_OPTIONS(33));
1177 }
1178
1179 static void usage_runuser(void)
1180 {
1181 fputs(USAGE_HEADER, stdout);
1182 fprintf(stdout,
1183 _(" %1$s [options] -u <user> [[--] <command>]\n"
1184 " %1$s [options] [-] [<user> [<argument>...]]\n"),
1185 program_invocation_short_name);
1186
1187 fputs(USAGE_SEPARATOR, stdout);
1188 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
1189 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
1190 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout);
1191
1192 fputs(USAGE_OPTIONS, stdout);
1193 fputs(_(" -u, --user <user> username\n"), stdout);
1194 usage_common();
1195 fputs(USAGE_SEPARATOR, stdout);
1196
1197 fprintf(stdout, USAGE_MAN_TAIL("runuser(1)"));
1198 }
1199
1200 static void usage_su(void)
1201 {
1202 fputs(USAGE_HEADER, stdout);
1203 fprintf(stdout,
1204 _(" %s [options] [-] [<user> [<argument>...]]\n"),
1205 program_invocation_short_name);
1206
1207 fputs(USAGE_SEPARATOR, stdout);
1208 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
1209 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout);
1210
1211 fputs(USAGE_OPTIONS, stdout);
1212 usage_common();
1213
1214 fprintf(stdout, USAGE_MAN_TAIL("su(1)"));
1215 }
1216
1217 static void __attribute__((__noreturn__)) usage(int mode)
1218 {
1219 if (mode == SU_MODE)
1220 usage_su();
1221 else
1222 usage_runuser();
1223
1224 exit(EXIT_SUCCESS);
1225 }
1226
1227 static void load_config(void *data)
1228 {
1229 struct su_context *su = (struct su_context *) data;
1230
1231 DBG(MISC, ul_debug("loading logindefs"));
1232 logindefs_load_file(su->runuser ? _PATH_LOGINDEFS_RUNUSER : _PATH_LOGINDEFS_SU);
1233 logindefs_load_file(_PATH_LOGINDEFS);
1234 }
1235
1236 /*
1237 * Returns 1 if the current user is not root
1238 */
1239 static int is_not_root(void)
1240 {
1241 const uid_t ruid = getuid();
1242 const uid_t euid = geteuid();
1243
1244 /* if we're really root and aren't running setuid */
1245 return (uid_t) 0 == ruid && ruid == euid ? 0 : 1;
1246 }
1247
1248 static gid_t add_supp_group(const char *name, gid_t **groups, size_t *ngroups)
1249 {
1250 struct group *gr;
1251
1252 if (*ngroups >= NGROUPS_MAX)
1253 errx(EXIT_FAILURE,
1254 P_("specifying more than %d supplemental group is not possible",
1255 "specifying more than %d supplemental groups is not possible",
1256 NGROUPS_MAX - 1), NGROUPS_MAX - 1);
1257
1258 gr = getgrnam(name);
1259 if (!gr)
1260 errx(EXIT_FAILURE, _("group %s does not exist"), name);
1261
1262 DBG(MISC, ul_debug("add %s group [name=%s, GID=%d]", name, gr->gr_name, (int) gr->gr_gid));
1263
1264 *groups = xrealloc(*groups, sizeof(gid_t) * (*ngroups + 1));
1265 (*groups)[*ngroups] = gr->gr_gid;
1266 (*ngroups)++;
1267
1268 return gr->gr_gid;
1269 }
1270
1271 int su_main(int argc, char **argv, int mode)
1272 {
1273 struct su_context _su = {
1274 .conv = { supam_conv, NULL },
1275 .runuser = (mode == RUNUSER_MODE ? 1 : 0),
1276 .change_environment = 1,
1277 .new_user = DEFAULT_USER,
1278 #ifdef USE_PTY
1279 .pty_master = -1,
1280 .pty_slave = -1,
1281 .pty_sigfd = -1,
1282 #endif
1283 }, *su = &_su;
1284
1285 int optc;
1286 char *command = NULL;
1287 int request_same_session = 0;
1288 char *shell = NULL;
1289
1290 gid_t *groups = NULL;
1291 size_t ngroups = 0;
1292 bool use_supp = false;
1293 bool use_gid = false;
1294 gid_t gid = 0;
1295
1296 static const struct option longopts[] = {
1297 {"command", required_argument, NULL, 'c'},
1298 {"session-command", required_argument, NULL, 'C'},
1299 {"fast", no_argument, NULL, 'f'},
1300 {"login", no_argument, NULL, 'l'},
1301 {"preserve-environment", no_argument, NULL, 'p'},
1302 {"pty", no_argument, NULL, 'P'},
1303 {"shell", required_argument, NULL, 's'},
1304 {"group", required_argument, NULL, 'g'},
1305 {"supp-group", required_argument, NULL, 'G'},
1306 {"user", required_argument, NULL, 'u'}, /* runuser only */
1307 {"whitelist-environment", required_argument, NULL, 'w'},
1308 {"help", no_argument, 0, 'h'},
1309 {"version", no_argument, 0, 'V'},
1310 {NULL, 0, NULL, 0}
1311 };
1312 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
1313 { 'm', 'w' }, /* preserve-environment, whitelist-environment */
1314 { 'p', 'w' }, /* preserve-environment, whitelist-environment */
1315 { 0 }
1316 };
1317 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
1318
1319 setlocale(LC_ALL, "");
1320 bindtextdomain(PACKAGE, LOCALEDIR);
1321 textdomain(PACKAGE);
1322 atexit(close_stdout);
1323
1324 su_init_debug();
1325 su->conv.appdata_ptr = (void *) su;
1326
1327 while ((optc =
1328 getopt_long(argc, argv, "c:fg:G:lmpPs:u:hVw:", longopts,
1329 NULL)) != -1) {
1330
1331 err_exclusive_options(optc, longopts, excl, excl_st);
1332
1333 switch (optc) {
1334 case 'c':
1335 command = optarg;
1336 break;
1337
1338 case 'C':
1339 command = optarg;
1340 request_same_session = 1;
1341 break;
1342
1343 case 'f':
1344 su->fast_startup = true;
1345 break;
1346
1347 case 'g':
1348 use_gid = true;
1349 gid = add_supp_group(optarg, &groups, &ngroups);
1350 break;
1351
1352 case 'G':
1353 use_supp = true;
1354 add_supp_group(optarg, &groups, &ngroups);
1355 break;
1356
1357 case 'l':
1358 su->simulate_login = true;
1359 break;
1360
1361 case 'm':
1362 case 'p':
1363 su->change_environment = false;
1364 break;
1365
1366 case 'w':
1367 env_whitelist_from_string(su, optarg);
1368 break;
1369
1370 case 'P':
1371 #ifdef USE_PTY
1372 su->pty = 1;
1373 #else
1374 errx(EXIT_FAILURE, _("--pty is not supported for your system"));
1375 #endif
1376 break;
1377
1378 case 's':
1379 shell = optarg;
1380 break;
1381
1382 case 'u':
1383 if (!su->runuser)
1384 errtryhelp(EXIT_FAILURE);
1385 su->runuser_uopt = 1;
1386 su->new_user = optarg;
1387 break;
1388
1389 case 'h':
1390 usage(mode);
1391
1392 case 'V':
1393 printf(UTIL_LINUX_VERSION);
1394 exit(EXIT_SUCCESS);
1395
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 }