2 * su(1) for Linux. Run a shell with substitute user and group IDs.
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>
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
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,
22 * Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>.
26 #include <sys/types.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>
40 #if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H) && defined(HAVE_SYS_SIGNALFD_H)
43 # include <sys/signalfd.h>
55 #include "pathnames.h"
57 #include "closestream.h"
64 #include "logindefs.h"
65 #include "su-common.h"
69 UL_DEBUG_DEFINE_MASK(su
);
70 UL_DEBUG_DEFINE_MASKNAMES(su
) = UL_DEBUG_EMPTY_MASKNAMES
;
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
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)
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"
90 #define PAM_SRVNAME_RUNUSER "runuser"
91 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
93 #define _PATH_LOGINDEFS_SU "/etc/default/su"
94 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
96 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
98 /* The shell to run if none is given in the user's passwd entry. */
99 #define DEFAULT_SHELL "/bin/sh"
101 /* The user to become if none is specified. */
102 #define DEFAULT_USER "root"
104 #ifndef HAVE_ENVIRON_DECL
105 extern char **environ
;
117 * su/runuser control struct
120 pam_handle_t
*pamh
; /* PAM handler */
121 struct pam_conv conv
; /* PAM conversation */
123 struct passwd
*pwd
; /* new user info */
124 char *pwdbuf
; /* pwd strings */
126 const char *tty_name
; /* tty_path without /dev prefix */
127 const char *tty_number
; /* end of the tty_path */
129 char *new_user
; /* wanted user */
130 char *old_user
; /* original user */
132 pid_t child
; /* fork() baby */
133 int childstatus
; /* wait() status */
135 char **env_whitelist_names
; /* environment whitelist */
136 char **env_whitelist_vals
;
138 struct sigaction oldact
[SIGNALS_IDX_COUNT
]; /* original sigactions indexed by SIG*_IDX */
141 struct termios stdin_attrs
; /* stdin and slave terminal runtime attributes */
144 int pty_sigfd
; /* signalfd() */
146 struct winsize win
; /* terminal window size */
147 sigset_t oldsig
; /* original signal mask */
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 */
164 static sig_atomic_t volatile caught_signal
= false;
166 /* Signal handler for parent process. */
168 su_catch_sig(int sig
)
173 static void su_init_debug(void)
175 __UL_INIT_DEBUG_FROM_ENV(su
, SU_DEBUG_
, 0, SU_DEBUG
);
178 static void init_tty(struct su_context
*su
)
180 su
->isterm
= isatty(STDIN_FILENO
) ? 1 : 0;
181 DBG(TTY
, ul_debug("initialize [is-term=%s]", su
->isterm
? "true" : "false"));
183 get_terminal_name(NULL
, &su
->tty_name
, &su
->tty_number
);
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.
190 static int wait_for_child(struct su_context
*su
)
192 pid_t pid
= (pid_t
) -1;;
195 if (su
->child
== (pid_t
) -1)
196 return su
->childstatus
;
198 if (su
->child
!= (pid_t
) -1) {
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.
204 DBG(SIG
, ul_debug("waiting for child [%d]...", su
->child
));
206 pid
= waitpid(su
->child
, &status
, WUNTRACED
);
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 */
213 DBG(SIG
, ul_debug(" session resumed -- continue"));
215 /* Let's go back to pty_proxy_master() */
216 if (su
->pty_sigfd
!= -1) {
217 DBG(SIG
, ul_debug(" leaving on child SIGSTOP"));
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)")
231 status
= WTERMSIG(status
) + 128;
233 status
= WEXITSTATUS(status
);
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;
243 DBG(SIG
, ul_debug("child status=%d", status
));
249 static void pty_init_slave(struct su_context
*su
)
251 DBG(PTY
, ul_debug("initialize slave"));
253 ioctl(su
->pty_slave
, TIOCSCTTY
, 1);
254 close(su
->pty_master
);
256 dup2(su
->pty_slave
, STDIN_FILENO
);
257 dup2(su
->pty_slave
, STDOUT_FILENO
);
258 dup2(su
->pty_slave
, STDERR_FILENO
);
260 close(su
->pty_slave
);
261 close(su
->pty_sigfd
);
267 sigprocmask(SIG_SETMASK
, &su
->oldsig
, NULL
);
269 DBG(PTY
, ul_debug("... initialize slave done"));
272 static void pty_create(struct su_context
*su
)
274 struct termios slave_attrs
;
278 DBG(PTY
, ul_debug("create for terminal"));
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
);
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
);
293 DBG(PTY
, ul_debug("create for non-terminal"));
294 rc
= openpty(&su
->pty_master
, &su
->pty_slave
, NULL
, NULL
, NULL
);
297 tcgetattr(su
->pty_slave
, &slave_attrs
);
298 slave_attrs
.c_lflag
&= ~ECHO
;
299 tcsetattr(su
->pty_slave
, TCSANOW
, &slave_attrs
);
304 err(EXIT_FAILURE
, _("failed to create pseudo-terminal"));
306 DBG(PTY
, ul_debug("pty setup done [master=%d, slave=%d]", su
->pty_master
, su
->pty_slave
));
309 static void pty_cleanup(struct su_context
*su
)
313 if (su
->pty_master
== -1 || !su
->isterm
)
316 DBG(PTY
, ul_debug("cleanup"));
317 rtt
= su
->stdin_attrs
;
318 tcsetattr(STDIN_FILENO
, TCSADRAIN
, &rtt
);
321 static int write_output(char *obuf
, ssize_t bytes
)
323 DBG(PTY
, ul_debug(" writing output"));
325 if (write_all(STDOUT_FILENO
, obuf
, bytes
)) {
326 DBG(PTY
, ul_debug(" writing output *failed*"));
327 warn(_("write failed"));
334 static int write_to_child(struct su_context
*su
,
335 char *buf
, size_t bufsz
)
337 return write_all(su
->pty_master
, buf
, bufsz
);
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:
349 * Unfortunately, the child (usually shell) can ignore stdin at all, so we
350 * don't wait forever to avoid dead locks...
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
357 static void write_eof_to_child(struct su_context
*su
)
359 unsigned int tries
= 0;
360 struct pollfd fds
[] = {
361 { .fd
= su
->pty_slave
, .events
= POLLIN
}
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"));
372 DBG(PTY
, ul_debug(" slave is empty now"));
374 DBG(PTY
, ul_debug(" sending EOF to master"));
375 write_to_child(su
, &c
, sizeof(char));
378 static int pty_handle_io(struct su_context
*su
, int fd
, int *eof
)
383 DBG(PTY
, ul_debug("%d FD active", fd
));
386 /* read from active FD */
387 bytes
= read(fd
, buf
, sizeof(buf
));
389 if (errno
== EAGAIN
|| errno
== EINTR
)
399 /* from stdin (user) to command */
400 if (fd
== STDIN_FILENO
) {
401 DBG(PTY
, ul_debug(" stdin --> master %zd bytes", bytes
));
403 if (write_to_child(su
, buf
, bytes
)) {
404 warn(_("write failed"));
407 /* without sync write_output() will write both input &
408 * shell output that looks like double echoing */
409 fdatasync(su
->pty_master
);
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
);
420 static int pty_handle_signal(struct su_context
*su
, int fd
)
422 struct signalfd_siginfo info
;
425 DBG(SIG
, ul_debug("signal FD %d active", fd
));
427 bytes
= read(fd
, &info
, sizeof(info
));
428 if (bytes
!= sizeof(info
)) {
429 if (bytes
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
434 switch (info
.ssi_signo
) {
436 DBG(SIG
, ul_debug(" get signal SIGCHLD"));
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
)
445 /* The child is dead, force poll() timeout. */
446 if (su
->child
== (pid_t
) -1)
447 su
->poll_timeout
= 10;
450 DBG(SIG
, ul_debug(" get signal SIGWINCH"));
452 ioctl(STDIN_FILENO
, TIOCGWINSZ
, (char *)&su
->win
);
453 ioctl(su
->pty_slave
, TIOCSWINSZ
, (char *)&su
->win
);
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
);
473 static void pty_proxy_master(struct su_context
*su
)
476 int rc
= 0, ret
, eof
= 0;
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
}
489 /* for PTY mode we use signalfd
491 * TODO: script(1) initializes this FD before fork, good or bad idea?
494 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
495 warn(_("cannot block signals"));
496 caught_signal
= true;
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
);
508 if ((su
->pty_sigfd
= signalfd(-1, &ourset
, SFD_CLOEXEC
)) < 0) {
509 warn(("cannot create signal file descriptor"));
510 caught_signal
= true;
514 pfd
[POLLFD_SIGNAL
].fd
= su
->pty_sigfd
;
515 su
->poll_timeout
= -1;
517 while (!caught_signal
) {
521 DBG(PTY
, ul_debug("calling poll()"));
523 /* wait for input or signal */
524 ret
= poll(pfd
, ARRAY_SIZE(pfd
), su
->poll_timeout
);
526 DBG(PTY
, ul_debug("poll() rc=%d", ret
));
531 warn(_("poll failed"));
535 DBG(PTY
, ul_debug("leaving poll() loop [timeout=%d]", su
->poll_timeout
));
539 for (i
= 0; i
< ARRAY_SIZE(pfd
); i
++) {
542 if (pfd
[i
].revents
== 0)
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" : "???",
550 pfd
[i
].revents
& POLLIN
? "POLLIN" : "",
551 pfd
[i
].revents
& POLLHUP
? "POLLHUP" : "",
552 pfd
[i
].revents
& POLLERR
? "POLLERR" : ""));
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"));
565 if (i
== POLLFD_STDIN
) {
566 write_eof_to_child(su
);
567 DBG(PTY
, ul_debug(" ignore STDIN"));
572 rc
= pty_handle_signal(su
, pfd
[i
].fd
);
580 close(su
->pty_sigfd
);
582 DBG(PTY
, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal
, rc
));
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. */
590 static void log_syslog(struct su_context
*su
, bool successful
)
592 DBG(LOG
, ul_debug("syslog logging"));
594 openlog(program_invocation_short_name
, 0, LOG_AUTH
);
595 syslog(LOG_NOTICE
, "%s(to %s) %s on %s",
597 su
->runuser
? "FAILED RUNUSER " : "FAILED SU ",
598 su
->new_user
, su
->old_user
? : "",
599 su
->tty_name
? : "none");
604 * Log failed login attempts in _PATH_BTMP if that exists.
606 static void log_btmp(struct su_context
*su
)
611 DBG(LOG
, ul_debug("btmp logging"));
613 memset(&ut
, 0, sizeof(ut
));
614 str2memcpy(ut
.ut_user
,
615 su
->pwd
&& su
->pwd
->pw_name
? su
->pwd
->pw_name
: "(unknown)",
619 str2memcpy(ut
.ut_id
, su
->tty_number
, sizeof(ut
.ut_id
));
621 str2memcpy(ut
.ut_line
, su
->tty_name
, sizeof(ut
.ut_line
));
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();
629 updwtmpx(_PATH_BTMP
, &ut
);
632 static int supam_conv( int num_msg
,
633 const struct pam_message
**msg
,
634 struct pam_response
**resp
,
637 struct su_context
*su
= (struct su_context
*) data
;
639 if (su
->suppress_pam_info
641 && msg
&& msg
[0]->msg_style
== PAM_TEXT_INFO
)
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
);
651 static void supam_cleanup(struct su_context
*su
, int retcode
)
653 const int errsv
= errno
;
655 DBG(PAM
, ul_debug("cleanup"));
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
);
666 static void supam_export_environment(struct su_context
*su
)
670 DBG(PAM
, ul_debug("init environ[]"));
672 /* This is a copy but don't care to free as we exec later anyways. */
673 env
= pam_getenvlist(su
->pamh
);
675 while (env
&& *env
) {
676 if (putenv(*env
) != 0)
677 err(EXIT_FAILURE
, _("failed to modify environment"));
682 static void supam_authenticate(struct su_context
*su
)
684 const char *srvname
= NULL
;
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
);
691 DBG(PAM
, ul_debug("start [name: %s]", srvname
));
693 rc
= pam_start(srvname
, su
->pwd
->pw_name
, &su
->conv
, &su
->pamh
);
694 if (is_pam_failure(rc
))
698 rc
= pam_set_item(su
->pamh
, PAM_TTY
, su
->tty_name
);
699 if (is_pam_failure(rc
))
703 rc
= pam_set_item(su
->pamh
, PAM_RUSER
, (const void *) su
->old_user
);
704 if (is_pam_failure(rc
))
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.
713 errx(EXIT_FAILURE
, _("may not be used by non-root users"));
717 rc
= pam_authenticate(su
->pamh
, 0);
718 if (is_pam_failure(rc
))
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
);
726 log_syslog(su
, !is_pam_failure(rc
));
728 if (is_pam_failure(rc
)) {
731 DBG(PAM
, ul_debug("authentication failed"));
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"));
741 static void supam_open_session(struct su_context
*su
)
745 DBG(PAM
, ul_debug("opening session"));
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
));
753 su
->pam_has_session
= 1;
756 static void parent_setup_signals(struct su_context
*su
)
763 * 1) block all signals
765 DBG(SIG
, ul_debug("initialize signals"));
768 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
769 warn(_("cannot block signals"));
770 caught_signal
= true;
773 if (!caught_signal
) {
774 struct sigaction action
;
775 action
.sa_handler
= su_catch_sig
;
776 sigemptyset(&action
.sa_mask
);
779 sigemptyset(&ourset
);
781 /* 2a) add wanted signals to the mask (for session) */
782 if (!su
->same_session
783 && (sigaddset(&ourset
, SIGINT
)
784 || sigaddset(&ourset
, SIGQUIT
))) {
786 warn(_("cannot initialize signal mask for session"));
787 caught_signal
= true;
789 /* 2b) add wanted generic signals to the mask */
791 && (sigaddset(&ourset
, SIGTERM
)
792 || sigaddset(&ourset
, SIGALRM
))) {
794 warn(_("cannot initialize signal mask"));
795 caught_signal
= true;
798 /* 3a) set signal handlers (for session) */
801 && (sigaction(SIGINT
, &action
, &su
->oldact
[SIGINT_IDX
])
802 || sigaction(SIGQUIT
, &action
, &su
->oldact
[SIGQUIT_IDX
]))) {
804 warn(_("cannot set signal handler for session"));
805 caught_signal
= true;
808 /* 3b) set signal handlers */
810 && sigaction(SIGTERM
, &action
, &su
->oldact
[SIGTERM_IDX
])) {
812 warn(_("cannot set signal handler"));
813 caught_signal
= true;
816 /* 4) unblock wanted signals */
818 && sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
)) {
820 warn(_("cannot set signal mask"));
821 caught_signal
= true;
827 static void create_watching_parent(struct su_context
*su
)
831 DBG(MISC
, ul_debug("forking..."));
833 /* no-op, just save original signal mask to oldsig */
834 sigprocmask(SIG_BLOCK
, NULL
, &su
->oldsig
);
839 fflush(stdout
); /* ??? */
841 switch ((int) (su
->child
= fork())) {
843 supam_cleanup(su
, PAM_ABORT
);
848 err(EXIT_FAILURE
, _("cannot create child process"));
854 default: /* parent */
855 DBG(MISC
, ul_debug("child [pid=%d]", (int) su
->child
));
859 /* free unnecessary stuff */
860 free_getlogindefs_data();
862 /* In the parent watch the child. */
864 /* su without pam support does not have a helper that keeps
865 sitting on any directory so let's go to /. */
867 warn(_("cannot change directory to %s"), "/");
870 pty_proxy_master(su
);
873 parent_setup_signals(su
);
879 status
= wait_for_child(su
);
883 DBG(SIG
, ul_debug("final child status=%d", status
));
885 if (caught_signal
&& su
->child
!= (pid_t
)-1) {
886 fprintf(stderr
, _("\nSession terminated, killing shell..."));
887 kill(su
->child
, SIGTERM
);
890 supam_cleanup(su
, PAM_SUCCESS
);
893 if (su
->child
!= (pid_t
)-1) {
894 DBG(SIG
, ul_debug("killing child"));
896 kill(su
->child
, SIGKILL
);
897 fprintf(stderr
, _(" ...killed.\n"));
900 /* Let's terminate itself with the received signal.
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).
906 DBG(SIG
, ul_debug("restore signals setting"));
907 switch (caught_signal
) {
909 sigaction(SIGTERM
, &su
->oldact
[SIGTERM_IDX
], NULL
);
912 sigaction(SIGINT
, &su
->oldact
[SIGINT_IDX
], NULL
);
915 sigaction(SIGQUIT
, &su
->oldact
[SIGQUIT_IDX
], NULL
);
918 /* just in case that signal stuff initialization failed and
919 * caught_signal = true */
920 caught_signal
= SIGKILL
;
923 DBG(SIG
, ul_debug("self-send %d signal", caught_signal
));
924 kill(getpid(), caught_signal
);
931 DBG(MISC
, ul_debug("exiting [rc=%d]", status
));
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.
938 static int env_whitelist_add(struct su_context
*su
, const char *name
)
940 const char *env
= getenv(name
);
944 if (strv_extend(&su
->env_whitelist_names
, name
))
946 if (strv_extend(&su
->env_whitelist_vals
, env
))
951 static int env_whitelist_setenv(struct su_context
*su
, int overwrite
)
957 STRV_FOREACH(one
, su
->env_whitelist_names
) {
958 rc
= setenv(*one
, su
->env_whitelist_vals
[i
], overwrite
);
967 /* Creates (add to) whitelist from comma delimited string */
968 static int env_whitelist_from_string(struct su_context
*su
, const char *str
)
970 char **all
= strv_split(str
, ",");
979 STRV_FOREACH(one
, all
)
980 env_whitelist_add(su
, *one
);
985 static void setenv_path(const struct passwd
*pw
)
989 DBG(MISC
, ul_debug("setting PATH"));
992 rc
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
994 else if ((rc
= logindefs_setenv("PATH", "ENV_SUPATH", NULL
)) != 0)
995 rc
= logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT
);
998 err(EXIT_FAILURE
, _("failed to set the PATH environment variable"));
1001 static void modify_environment(struct su_context
*su
, const char *shell
)
1003 const struct passwd
*pw
= su
->pwd
;
1006 DBG(MISC
, ul_debug("modify environ[]"));
1008 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
1010 * Unset all other environment variables, but follow
1011 * --whitelist-environment if specified.
1013 if (su
->simulate_login
) {
1014 /* leave TERM unchanged */
1015 env_whitelist_add(su
, "TERM");
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 re-alloc(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.
1024 #ifdef HAVE_CLEARENV
1031 xsetenv("SHELL", shell
, 1);
1035 xsetenv("HOME", pw
->pw_dir
, 1);
1036 xsetenv("USER", pw
->pw_name
, 1);
1037 xsetenv("LOGNAME", pw
->pw_name
, 1);
1039 /* apply all from whitelist, but no overwrite */
1040 env_whitelist_setenv(su
, 0);
1042 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
1044 } else if (su
->change_environment
) {
1045 xsetenv("HOME", pw
->pw_dir
, 1);
1047 xsetenv("SHELL", shell
, 1);
1049 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
1053 xsetenv("USER", pw
->pw_name
, 1);
1054 xsetenv("LOGNAME", pw
->pw_name
, 1);
1058 supam_export_environment(su
);
1061 static void init_groups(struct su_context
*su
, gid_t
*groups
, size_t ngroups
)
1065 DBG(MISC
, ul_debug("initialize groups"));
1069 rc
= setgroups(ngroups
, groups
);
1071 rc
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
1074 supam_cleanup(su
, PAM_ABORT
);
1075 err(EXIT_FAILURE
, _("cannot set groups"));
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;
1086 static void change_identity(const struct passwd
*pw
)
1088 DBG(MISC
, ul_debug("changing identity [GID=%d, UID=%d]", pw
->pw_gid
, pw
->pw_uid
));
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"));
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.
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
)
1105 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
1106 const char **args
= xcalloc(n_args
, sizeof *args
);
1109 DBG(MISC
, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
1111 su
->simulate_login
? " login" : "",
1112 su
->fast_startup
? " fast-start" : ""));
1114 if (su
->simulate_login
) {
1116 char *shell_basename
;
1118 shell_basename
= basename(shell
);
1119 arg0
= xmalloc(strlen(shell_basename
) + 2);
1121 strcpy(arg0
+ 1, shell_basename
);
1124 args
[0] = basename(shell
);
1126 if (su
->fast_startup
)
1127 args
[argno
++] = "-f";
1129 args
[argno
++] = "-c";
1130 args
[argno
++] = command
;
1133 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
1134 args
[argno
+ n_additional_args
] = NULL
;
1135 execv(shell
, (char **)args
);
1139 /* Return true if SHELL is a restricted shell (one not returned by
1140 * getusershell), else false, meaning it is a standard shell.
1142 static bool is_restricted_shell(const char *shell
)
1147 while ((line
= getusershell()) != NULL
) {
1148 if (*line
!= '#' && !strcmp(line
, shell
)) {
1155 DBG(MISC
, ul_debug("%s is restricted shell (not in /etc/shells)", shell
));
1159 static void usage_common(void)
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
);
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
);
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
);
1177 fputs(USAGE_SEPARATOR
, stdout
);
1178 printf(USAGE_HELP_OPTIONS(33));
1181 static void usage_runuser(void)
1183 fputs(USAGE_HEADER
, stdout
);
1185 _(" %1$s [options] -u <user> [[--] <command>]\n"
1186 " %1$s [options] [-] [<user> [<argument>...]]\n"),
1187 program_invocation_short_name
);
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
);
1194 fputs(USAGE_OPTIONS
, stdout
);
1195 fputs(_(" -u, --user <user> username\n"), stdout
);
1197 fputs(USAGE_SEPARATOR
, stdout
);
1199 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
1202 static void usage_su(void)
1204 fputs(USAGE_HEADER
, stdout
);
1206 _(" %s [options] [-] [<user> [<argument>...]]\n"),
1207 program_invocation_short_name
);
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
);
1213 fputs(USAGE_OPTIONS
, stdout
);
1216 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
1219 static void __attribute__((__noreturn__
)) usage(int mode
)
1221 if (mode
== SU_MODE
)
1229 static void load_config(void *data
)
1231 struct su_context
*su
= (struct su_context
*) data
;
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
);
1239 * Returns 1 if the current user is not root
1241 static int is_not_root(void)
1243 const uid_t ruid
= getuid();
1244 const uid_t euid
= geteuid();
1246 /* if we're really root and aren't running setuid */
1247 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
1250 static gid_t
add_supp_group(const char *name
, gid_t
**groups
, size_t *ngroups
)
1254 if (*ngroups
>= NGROUPS_MAX
)
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);
1260 gr
= getgrnam(name
);
1262 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
1264 DBG(MISC
, ul_debug("add %s group [name=%s, GID=%d]", name
, gr
->gr_name
, (int) gr
->gr_gid
));
1266 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
1267 (*groups
)[*ngroups
] = gr
->gr_gid
;
1273 int su_main(int argc
, char **argv
, int mode
)
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
,
1288 char *command
= NULL
;
1289 int request_same_session
= 0;
1292 gid_t
*groups
= NULL
;
1294 bool use_supp
= false;
1295 bool use_gid
= false;
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'},
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 */
1319 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1321 setlocale(LC_ALL
, "");
1322 bindtextdomain(PACKAGE
, LOCALEDIR
);
1323 textdomain(PACKAGE
);
1324 close_stdout_atexit();
1327 su
->conv
.appdata_ptr
= (void *) su
;
1330 getopt_long(argc
, argv
, "c:fg:G:lmpPs:u:hVw:", longopts
,
1333 err_exclusive_options(optc
, longopts
, excl
, excl_st
);
1342 request_same_session
= 1;
1346 su
->fast_startup
= true;
1351 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
1356 add_supp_group(optarg
, &groups
, &ngroups
);
1360 su
->simulate_login
= true;
1365 su
->change_environment
= false;
1369 env_whitelist_from_string(su
, optarg
);
1376 errx(EXIT_FAILURE
, _("--pty is not supported for your system"));
1386 errtryhelp(EXIT_FAILURE
);
1387 su
->runuser_uopt
= 1;
1388 su
->new_user
= optarg
;
1395 print_version(EXIT_SUCCESS
);
1397 errtryhelp(EXIT_FAILURE
);
1401 su
->restricted
= is_not_root();
1403 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
1404 su
->simulate_login
= true;
1408 if (su
->simulate_login
&& !su
->change_environment
) {
1410 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
1411 su
->change_environment
= true;
1416 /* runuser -u <user> <command>
1418 * If -u <user> is not specified, then follow traditional su(1) behavior and
1421 if (su
->runuser_uopt
) {
1422 if (shell
|| su
->fast_startup
|| command
|| su
->simulate_login
)
1424 _("options --{shell,fast,command,session-command,login} and "
1425 "--user are mutually exclusive"));
1427 errx(EXIT_FAILURE
, _("no command was specified"));
1433 su
->new_user
= argv
[optind
++];
1437 if ((use_supp
|| use_gid
) && su
->restricted
)
1439 _("only root can specify alternative groups"));
1441 logindefs_set_loader(load_config
, (void *) su
);
1444 su
->pwd
= xgetpwnam(su
->new_user
, &su
->pwdbuf
);
1446 || !su
->pwd
->pw_passwd
1447 || !su
->pwd
->pw_name
|| !*su
->pwd
->pw_name
1448 || !su
->pwd
->pw_dir
|| !*su
->pwd
->pw_dir
)
1450 _("user %s does not exist or the user entry does not "
1451 "contain all the required fields"), su
->new_user
);
1453 su
->new_user
= su
->pwd
->pw_name
;
1454 su
->old_user
= xgetlogin();
1456 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
1457 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
1459 if (use_supp
&& !use_gid
)
1460 su
->pwd
->pw_gid
= groups
[0];
1462 su
->pwd
->pw_gid
= gid
;
1464 supam_authenticate(su
);
1466 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
1467 su
->same_session
= 1;
1469 /* initialize shell variable only if "-u <user>" not specified */
1470 if (su
->runuser_uopt
) {
1473 if (!shell
&& !su
->change_environment
)
1474 shell
= getenv("SHELL");
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.
1484 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
1487 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
1490 init_groups(su
, groups
, ngroups
);
1492 if (!su
->simulate_login
|| command
)
1493 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
1495 supam_open_session(su
);
1497 create_watching_parent(su
);
1498 /* Now we're in the child. */
1500 change_identity(su
->pwd
);
1501 if (!su
->same_session
|| su
->pty
) {
1502 DBG(MISC
, ul_debug("call setsid()"));
1509 /* Set environment after pam_open_session, which may put KRB5CCNAME
1510 into the pam_env, etc. */
1512 modify_environment(su
, shell
);
1514 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
1515 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
1518 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
1520 execvp(argv
[optind
], &argv
[optind
]);
1521 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);