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"));
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"));
285 /* reuse the current terminal setting for slave */
286 slave_attrs
= su
->stdin_attrs
;
287 cfmakeraw(&slave_attrs
);
289 ioctl(STDIN_FILENO
, TIOCGWINSZ
, (char *)&su
->win
);
291 /* create master+slave */
292 rc
= openpty(&su
->pty_master
, &su
->pty_slave
, NULL
, &slave_attrs
, &win
);
295 DBG(PTY
, ul_debug("create for non-terminal"));
296 rc
= openpty(&su
->pty_master
, &su
->pty_slave
, NULL
, NULL
, NULL
);
298 /* set slave attributes */
300 tcgetattr(su
->pty_slave
, &slave_attrs
);
301 cfmakeraw(&slave_attrs
);
302 tcsetattr(su
->pty_slave
, TCSANOW
, &slave_attrs
);
307 err(EXIT_FAILURE
, _("failed to create pseudo-terminal"));
309 DBG(PTY
, ul_debug("pty setup done [master=%d, slave=%d]", su
->pty_master
, su
->pty_slave
));
312 static void pty_cleanup(struct su_context
*su
)
314 if (su
->pty_master
== -1)
317 DBG(PTY
, ul_debug("cleanup"));
319 tcsetattr(STDIN_FILENO
, TCSADRAIN
, &su
->stdin_attrs
);
322 static int write_output(char *obuf
, ssize_t bytes
)
324 DBG(PTY
, ul_debug(" writing output"));
326 if (write_all(STDOUT_FILENO
, obuf
, bytes
)) {
327 DBG(PTY
, ul_debug(" writing output *failed*"));
328 warn(_("write failed"));
335 static int write_to_child(struct su_context
*su
,
336 char *buf
, size_t bufsz
)
338 return write_all(su
->pty_master
, buf
, bufsz
);
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:
350 * Unfortunately, the child (usually shell) can ignore stdin at all, so we
351 * don't wait forever to avoid dead locks...
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
358 static void write_eof_to_child(struct su_context
*su
)
360 unsigned int tries
= 0;
361 struct pollfd fds
[] = {
362 { .fd
= su
->pty_slave
, .events
= POLLIN
}
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"));
373 DBG(PTY
, ul_debug(" slave is empty now"));
375 DBG(PTY
, ul_debug(" sending EOF to master"));
376 write_to_child(su
, &c
, sizeof(char));
379 static int pty_handle_io(struct su_context
*su
, int fd
, int *eof
)
384 DBG(PTY
, ul_debug("%d FD active", fd
));
387 /* read from active FD */
388 bytes
= read(fd
, buf
, sizeof(buf
));
390 if (errno
== EAGAIN
|| errno
== EINTR
)
400 /* from stdin (user) to command */
401 if (fd
== STDIN_FILENO
) {
402 DBG(PTY
, ul_debug(" stdin --> master %zd bytes", bytes
));
404 if (write_to_child(su
, buf
, bytes
)) {
405 warn(_("write failed"));
408 /* without sync write_output() will write both input &
409 * shell output that looks like double echoing */
410 fdatasync(su
->pty_master
);
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
);
421 static int pty_handle_signal(struct su_context
*su
, int fd
)
423 struct signalfd_siginfo info
;
426 DBG(SIG
, ul_debug("signal FD %d active", fd
));
428 bytes
= read(fd
, &info
, sizeof(info
));
429 if (bytes
!= sizeof(info
)) {
430 if (bytes
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
435 switch (info
.ssi_signo
) {
437 DBG(SIG
, ul_debug(" get signal SIGCHLD"));
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
)
443 /* The child is dead, force poll() timeout. */
444 if (su
->child
== (pid_t
) -1)
445 su
->poll_timeout
= 10;
448 DBG(SIG
, ul_debug(" get signal SIGWINCH"));
450 ioctl(STDIN_FILENO
, TIOCGWINSZ
, (char *)&su
->win
);
451 ioctl(su
->pty_slave
, TIOCSWINSZ
, (char *)&su
->win
);
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
);
471 static void pty_proxy_master(struct su_context
*su
)
474 int rc
= 0, ret
, eof
= 0;
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
}
487 /* for PTY mode we use signalfd
489 * TODO: script(1) initializes this FD before fork, good or bad idea?
492 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
493 warn(_("cannot block signals"));
494 caught_signal
= true;
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
);
506 if ((su
->pty_sigfd
= signalfd(-1, &ourset
, SFD_CLOEXEC
)) < 0) {
507 warn(("cannot create signal file descriptor"));
508 caught_signal
= true;
512 pfd
[POLLFD_SIGNAL
].fd
= su
->pty_sigfd
;
513 su
->poll_timeout
= -1;
515 while (!caught_signal
) {
519 DBG(PTY
, ul_debug("calling poll()"));
521 /* wait for input or signal */
522 ret
= poll(pfd
, ARRAY_SIZE(pfd
), su
->poll_timeout
);
524 DBG(PTY
, ul_debug("poll() rc=%d", ret
));
529 warn(_("poll failed"));
533 DBG(PTY
, ul_debug("leaving poll() loop [timeout=%d]", su
->poll_timeout
));
537 for (i
= 0; i
< ARRAY_SIZE(pfd
); i
++) {
540 if (pfd
[i
].revents
== 0)
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" : "???",
548 pfd
[i
].revents
& POLLIN
? "POLLIN" : "",
549 pfd
[i
].revents
& POLLHUP
? "POLLHUP" : "",
550 pfd
[i
].revents
& POLLERR
? "POLLERR" : ""));
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"));
563 if (i
== POLLFD_STDIN
) {
564 write_eof_to_child(su
);
565 DBG(PTY
, ul_debug(" ignore STDIN"));
570 rc
= pty_handle_signal(su
, pfd
[i
].fd
);
578 close(su
->pty_sigfd
);
580 DBG(PTY
, ul_debug("poll() done [signal=%d, rc=%d]", caught_signal
, rc
));
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. */
588 static void log_syslog(struct su_context
*su
, bool successful
)
590 DBG(LOG
, ul_debug("syslog logging"));
592 openlog(program_invocation_short_name
, 0, LOG_AUTH
);
593 syslog(LOG_NOTICE
, "%s(to %s) %s on %s",
595 su
->runuser
? "FAILED RUNUSER " : "FAILED SU ",
596 su
->new_user
, su
->old_user
? : "",
597 su
->tty_name
? : "none");
602 * Log failed login attempts in _PATH_BTMP if that exists.
604 static void log_btmp(struct su_context
*su
)
609 DBG(LOG
, ul_debug("btmp logging"));
611 memset(&ut
, 0, sizeof(ut
));
612 str2memcpy(ut
.ut_user
,
613 su
->pwd
&& su
->pwd
->pw_name
? su
->pwd
->pw_name
: "(unknown)",
617 str2memcpy(ut
.ut_id
, su
->tty_number
, sizeof(ut
.ut_id
));
619 str2memcpy(ut
.ut_line
, su
->tty_name
, sizeof(ut
.ut_line
));
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();
627 updwtmpx(_PATH_BTMP
, &ut
);
630 static int supam_conv( int num_msg
,
631 const struct pam_message
**msg
,
632 struct pam_response
**resp
,
635 struct su_context
*su
= (struct su_context
*) data
;
637 if (su
->suppress_pam_info
639 && msg
&& msg
[0]->msg_style
== PAM_TEXT_INFO
)
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
);
649 static void supam_cleanup(struct su_context
*su
, int retcode
)
651 const int errsv
= errno
;
653 DBG(PAM
, ul_debug("cleanup"));
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
);
664 static void supam_export_environment(struct su_context
*su
)
668 DBG(PAM
, ul_debug("init environ[]"));
670 /* This is a copy but don't care to free as we exec later anyways. */
671 env
= pam_getenvlist(su
->pamh
);
673 while (env
&& *env
) {
674 if (putenv(*env
) != 0)
675 err(EXIT_FAILURE
, _("failed to modify environment"));
680 static void supam_authenticate(struct su_context
*su
)
682 const char *srvname
= NULL
;
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
);
689 DBG(PAM
, ul_debug("start [name: %s]", srvname
));
691 rc
= pam_start(srvname
, su
->pwd
->pw_name
, &su
->conv
, &su
->pamh
);
692 if (is_pam_failure(rc
))
696 rc
= pam_set_item(su
->pamh
, PAM_TTY
, su
->tty_name
);
697 if (is_pam_failure(rc
))
701 rc
= pam_set_item(su
->pamh
, PAM_RUSER
, (const void *) su
->old_user
);
702 if (is_pam_failure(rc
))
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.
711 errx(EXIT_FAILURE
, _("may not be used by non-root users"));
715 rc
= pam_authenticate(su
->pamh
, 0);
716 if (is_pam_failure(rc
))
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
);
724 log_syslog(su
, !is_pam_failure(rc
));
726 if (is_pam_failure(rc
)) {
729 DBG(PAM
, ul_debug("authentication failed"));
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"));
739 static void supam_open_session(struct su_context
*su
)
743 DBG(PAM
, ul_debug("opening session"));
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
));
751 su
->pam_has_session
= 1;
754 static void parent_setup_signals(struct su_context
*su
)
761 * 1) block all signals
763 DBG(SIG
, ul_debug("initialize signals"));
766 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
767 warn(_("cannot block signals"));
768 caught_signal
= true;
771 if (!caught_signal
) {
772 struct sigaction action
;
773 action
.sa_handler
= su_catch_sig
;
774 sigemptyset(&action
.sa_mask
);
777 sigemptyset(&ourset
);
779 /* 2a) add wanted signals to the mask (for session) */
780 if (!su
->same_session
781 && (sigaddset(&ourset
, SIGINT
)
782 || sigaddset(&ourset
, SIGQUIT
))) {
784 warn(_("cannot initialize signal mask for session"));
785 caught_signal
= true;
787 /* 2b) add wanted generic signals to the mask */
789 && (sigaddset(&ourset
, SIGTERM
)
790 || sigaddset(&ourset
, SIGALRM
))) {
792 warn(_("cannot initialize signal mask"));
793 caught_signal
= true;
796 /* 3a) set signal handlers (for session) */
799 && (sigaction(SIGINT
, &action
, &su
->oldact
[SIGINT_IDX
])
800 || sigaction(SIGQUIT
, &action
, &su
->oldact
[SIGQUIT_IDX
]))) {
802 warn(_("cannot set signal handler for session"));
803 caught_signal
= true;
806 /* 3b) set signal handlers */
808 && sigaction(SIGTERM
, &action
, &su
->oldact
[SIGTERM_IDX
])) {
810 warn(_("cannot set signal handler"));
811 caught_signal
= true;
814 /* 4) unblock wanted signals */
816 && sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
)) {
818 warn(_("cannot set signal mask"));
819 caught_signal
= true;
825 static void create_watching_parent(struct su_context
*su
)
829 DBG(MISC
, ul_debug("forking..."));
831 /* no-op, just save original signal mask to oldsig */
832 sigprocmask(SIG_BLOCK
, NULL
, &su
->oldsig
);
837 fflush(stdout
); /* ??? */
839 switch ((int) (su
->child
= fork())) {
841 supam_cleanup(su
, PAM_ABORT
);
846 err(EXIT_FAILURE
, _("cannot create child process"));
852 default: /* parent */
853 DBG(MISC
, ul_debug("child [pid=%d]", (int) su
->child
));
857 /* free unnecessary stuff */
858 free_getlogindefs_data();
860 /* In the parent watch the child. */
862 /* su without pam support does not have a helper that keeps
863 sitting on any directory so let's go to /. */
865 warn(_("cannot change directory to %s"), "/");
868 pty_proxy_master(su
);
871 parent_setup_signals(su
);
877 status
= wait_for_child(su
);
881 DBG(SIG
, ul_debug("final child status=%d", status
));
883 if (caught_signal
&& su
->child
!= (pid_t
)-1) {
884 fprintf(stderr
, _("\nSession terminated, killing shell..."));
885 kill(su
->child
, SIGTERM
);
888 supam_cleanup(su
, PAM_SUCCESS
);
891 if (su
->child
!= (pid_t
)-1) {
892 DBG(SIG
, ul_debug("killing child"));
894 kill(su
->child
, SIGKILL
);
895 fprintf(stderr
, _(" ...killed.\n"));
898 /* Let's terminate itself with the received signal.
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).
904 DBG(SIG
, ul_debug("restore signals setting"));
905 switch (caught_signal
) {
907 sigaction(SIGTERM
, &su
->oldact
[SIGTERM_IDX
], NULL
);
910 sigaction(SIGINT
, &su
->oldact
[SIGINT_IDX
], NULL
);
913 sigaction(SIGQUIT
, &su
->oldact
[SIGQUIT_IDX
], NULL
);
916 /* just in case that signal stuff initialization failed and
917 * caught_signal = true */
918 caught_signal
= SIGKILL
;
921 DBG(SIG
, ul_debug("self-send %d signal", caught_signal
));
922 kill(getpid(), caught_signal
);
929 DBG(MISC
, ul_debug("exiting [rc=%d]", status
));
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.
936 static int env_whitelist_add(struct su_context
*su
, const char *name
)
938 const char *env
= getenv(name
);
942 if (strv_extend(&su
->env_whitelist_names
, name
))
944 if (strv_extend(&su
->env_whitelist_vals
, env
))
949 static int env_whitelist_setenv(struct su_context
*su
, int overwrite
)
955 STRV_FOREACH(one
, su
->env_whitelist_names
) {
956 rc
= setenv(*one
, su
->env_whitelist_vals
[i
], overwrite
);
965 /* Creates (add to) whitelist from comma delimited string */
966 static int env_whitelist_from_string(struct su_context
*su
, const char *str
)
968 char **all
= strv_split(str
, ",");
977 STRV_FOREACH(one
, all
)
978 env_whitelist_add(su
, *one
);
983 static void setenv_path(const struct passwd
*pw
)
987 DBG(MISC
, ul_debug("setting PATH"));
990 rc
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
992 else if ((rc
= logindefs_setenv("PATH", "ENV_ROOTPATH", NULL
)) != 0)
993 rc
= logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT
);
996 err(EXIT_FAILURE
, _("failed to set the PATH environment variable"));
999 static void modify_environment(struct su_context
*su
, const char *shell
)
1001 const struct passwd
*pw
= su
->pwd
;
1004 DBG(MISC
, ul_debug("modify environ[]"));
1006 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
1008 * Unset all other environment variables, but follow
1009 * --whitelist-environment if specified.
1011 if (su
->simulate_login
) {
1012 /* leave TERM unchanged */
1013 env_whitelist_add(su
, "TERM");
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.
1022 #ifdef HAVE_CLEARENV
1029 xsetenv("SHELL", shell
, 1);
1033 xsetenv("HOME", pw
->pw_dir
, 1);
1034 xsetenv("USER", pw
->pw_name
, 1);
1035 xsetenv("LOGNAME", pw
->pw_name
, 1);
1037 /* apply all from whitelist, but no overwrite */
1038 env_whitelist_setenv(su
, 0);
1040 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
1042 } else if (su
->change_environment
) {
1043 xsetenv("HOME", pw
->pw_dir
, 1);
1045 xsetenv("SHELL", shell
, 1);
1047 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
1051 xsetenv("USER", pw
->pw_name
, 1);
1052 xsetenv("LOGNAME", pw
->pw_name
, 1);
1056 supam_export_environment(su
);
1059 static void init_groups(struct su_context
*su
, gid_t
*groups
, size_t ngroups
)
1063 DBG(MISC
, ul_debug("initialize groups"));
1067 rc
= setgroups(ngroups
, groups
);
1069 rc
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
1072 supam_cleanup(su
, PAM_ABORT
);
1073 err(EXIT_FAILURE
, _("cannot set groups"));
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;
1084 static void change_identity(const struct passwd
*pw
)
1086 DBG(MISC
, ul_debug("changing identity [GID=%d, UID=%d]", pw
->pw_gid
, pw
->pw_uid
));
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"));
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.
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
)
1103 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
1104 const char **args
= xcalloc(n_args
, sizeof *args
);
1107 DBG(MISC
, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
1109 su
->simulate_login
? " login" : "",
1110 su
->fast_startup
? " fast-start" : ""));
1112 if (su
->simulate_login
) {
1114 char *shell_basename
;
1116 shell_basename
= basename(shell
);
1117 arg0
= xmalloc(strlen(shell_basename
) + 2);
1119 strcpy(arg0
+ 1, shell_basename
);
1122 args
[0] = basename(shell
);
1124 if (su
->fast_startup
)
1125 args
[argno
++] = "-f";
1127 args
[argno
++] = "-c";
1128 args
[argno
++] = command
;
1131 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
1132 args
[argno
+ n_additional_args
] = NULL
;
1133 execv(shell
, (char **)args
);
1137 /* Return true if SHELL is a restricted shell (one not returned by
1138 * getusershell), else false, meaning it is a standard shell.
1140 static bool is_restricted_shell(const char *shell
)
1145 while ((line
= getusershell()) != NULL
) {
1146 if (*line
!= '#' && !strcmp(line
, shell
)) {
1153 DBG(MISC
, ul_debug("%s is restricted shell (not in /etc/shells)", shell
));
1157 static void usage_common(void)
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
);
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
);
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
);
1175 fputs(USAGE_SEPARATOR
, stdout
);
1176 printf(USAGE_HELP_OPTIONS(33));
1179 static void usage_runuser(void)
1181 fputs(USAGE_HEADER
, stdout
);
1183 _(" %1$s [options] -u <user> [[--] <command>]\n"
1184 " %1$s [options] [-] [<user> [<argument>...]]\n"),
1185 program_invocation_short_name
);
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
);
1192 fputs(USAGE_OPTIONS
, stdout
);
1193 fputs(_(" -u, --user <user> username\n"), stdout
);
1195 fputs(USAGE_SEPARATOR
, stdout
);
1197 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
1200 static void usage_su(void)
1202 fputs(USAGE_HEADER
, stdout
);
1204 _(" %s [options] [-] [<user> [<argument>...]]\n"),
1205 program_invocation_short_name
);
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
);
1211 fputs(USAGE_OPTIONS
, stdout
);
1214 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
1217 static void __attribute__((__noreturn__
)) usage(int mode
)
1219 if (mode
== SU_MODE
)
1227 static void load_config(void *data
)
1229 struct su_context
*su
= (struct su_context
*) data
;
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
);
1237 * Returns 1 if the current user is not root
1239 static int is_not_root(void)
1241 const uid_t ruid
= getuid();
1242 const uid_t euid
= geteuid();
1244 /* if we're really root and aren't running setuid */
1245 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
1248 static gid_t
add_supp_group(const char *name
, gid_t
**groups
, size_t *ngroups
)
1252 if (*ngroups
>= NGROUPS_MAX
)
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);
1258 gr
= getgrnam(name
);
1260 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
1262 DBG(MISC
, ul_debug("add %s group [name=%s, GID=%d]", name
, gr
->gr_name
, (int) gr
->gr_gid
));
1264 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
1265 (*groups
)[*ngroups
] = gr
->gr_gid
;
1271 int su_main(int argc
, char **argv
, int mode
)
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
,
1286 char *command
= NULL
;
1287 int request_same_session
= 0;
1290 gid_t
*groups
= NULL
;
1292 bool use_supp
= false;
1293 bool use_gid
= false;
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'},
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 */
1317 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1319 setlocale(LC_ALL
, "");
1320 bindtextdomain(PACKAGE
, LOCALEDIR
);
1321 textdomain(PACKAGE
);
1322 atexit(close_stdout
);
1325 su
->conv
.appdata_ptr
= (void *) su
;
1328 getopt_long(argc
, argv
, "c:fg:G:lmpPs:u:hVw:", longopts
,
1331 err_exclusive_options(optc
, longopts
, excl
, excl_st
);
1340 request_same_session
= 1;
1344 su
->fast_startup
= true;
1349 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
1354 add_supp_group(optarg
, &groups
, &ngroups
);
1358 su
->simulate_login
= true;
1363 su
->change_environment
= false;
1367 env_whitelist_from_string(su
, optarg
);
1374 errx(EXIT_FAILURE
, _("--pty is not supported for your system"));
1384 errtryhelp(EXIT_FAILURE
);
1385 su
->runuser_uopt
= 1;
1386 su
->new_user
= optarg
;
1393 printf(UTIL_LINUX_VERSION
);
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
)
1449 errx(EXIT_FAILURE
, _("user %s does not exist"), su
->new_user
);
1451 su
->new_user
= su
->pwd
->pw_name
;
1452 su
->old_user
= xgetlogin();
1454 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
1455 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
1457 if (use_supp
&& !use_gid
)
1458 su
->pwd
->pw_gid
= groups
[0];
1460 su
->pwd
->pw_gid
= gid
;
1462 supam_authenticate(su
);
1464 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
1465 su
->same_session
= 1;
1467 /* initialize shell variable only if "-u <user>" not specified */
1468 if (su
->runuser_uopt
) {
1471 if (!shell
&& !su
->change_environment
)
1472 shell
= getenv("SHELL");
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.
1482 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
1485 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
1488 init_groups(su
, groups
, ngroups
);
1490 if (!su
->simulate_login
|| command
)
1491 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
1493 supam_open_session(su
);
1495 create_watching_parent(su
);
1496 /* Now we're in the child. */
1498 change_identity(su
->pwd
);
1499 if (!su
->same_session
|| su
->pty
) {
1500 DBG(MISC
, ul_debug("call setsid()"));
1507 /* Set environment after pam_open_session, which may put KRB5CCNAME
1508 into the pam_env, etc. */
1510 modify_environment(su
, shell
);
1512 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
1513 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
1516 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
1518 execvp(argv
[optind
], &argv
[optind
]);
1519 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);