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>
41 #ifdef HAVE_SYS_RESOURCE_H
42 # include <sys/resource.h>
48 # include <sys/signalfd.h>
49 # include "pty-session.h"
60 #include "pathnames.h"
62 #include "closestream.h"
69 #include "logindefs.h"
70 #include "su-common.h"
74 UL_DEBUG_DEFINE_MASK(su
);
75 UL_DEBUG_DEFINE_MASKNAMES(su
) = UL_DEBUG_EMPTY_MASKNAMES
;
77 #define SU_DEBUG_INIT (1 << 1)
78 #define SU_DEBUG_PAM (1 << 2)
79 #define SU_DEBUG_PARENT (1 << 3)
80 #define SU_DEBUG_TTY (1 << 4)
81 #define SU_DEBUG_LOG (1 << 5)
82 #define SU_DEBUG_MISC (1 << 6)
83 #define SU_DEBUG_SIG (1 << 7)
84 #define SU_DEBUG_PTY (1 << 8)
85 #define SU_DEBUG_ALL 0xFFFF
87 #define DBG(m, x) __UL_DBG(su, SU_DEBUG_, m, x)
88 #define ON_DBG(m, x) __UL_DBG_CALL(su, SU_DEBUG_, m, x)
90 /* name of the pam configuration files. separate configs for su and su - */
91 #define PAM_SRVNAME_SU "su"
92 #define PAM_SRVNAME_SU_L "su-l"
94 #define PAM_SRVNAME_RUNUSER "runuser"
95 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
98 #define _PATH_LOGINDEFS_SU "default/su"
99 #define _PATH_LOGINDEFS_RUNUSER "default/runuser"
101 #define _PATH_LOGINDEFS_SU "/etc/default/su"
102 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
105 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
107 /* The shell to run if none is given in the user's passwd entry. */
108 #define DEFAULT_SHELL "/bin/sh"
110 /* The user to become if none is specified. */
111 #define DEFAULT_USER "root"
113 #ifndef HAVE_ENVIRON_DECL
114 extern char **environ
;
126 * su/runuser control struct
129 pam_handle_t
*pamh
; /* PAM handler */
130 struct pam_conv conv
; /* PAM conversation */
132 struct passwd
*pwd
; /* new user info */
133 char *pwdbuf
; /* pwd strings */
135 const char *tty_path
; /* tty device path */
136 const char *tty_name
; /* tty_path without /dev prefix */
137 const char *tty_number
; /* end of the tty_path */
139 char *new_user
; /* wanted user */
140 char *old_user
; /* original user */
142 pid_t child
; /* fork() baby */
143 int childstatus
; /* wait() status */
145 char **env_whitelist_names
; /* environment whitelist */
146 char **env_whitelist_vals
;
148 struct sigaction oldact
[SIGNALS_IDX_COUNT
]; /* original sigactions indexed by SIG*_IDX */
150 struct ul_pty
*pty
; /* pseudo terminal handler (for --pty) */
152 unsigned int runuser
:1, /* flase=su, true=runuser */
153 runuser_uopt
:1, /* runuser -u specified */
154 isterm
:1, /* is stdin terminal? */
155 fast_startup
:1, /* pass the `-f' option to the subshell. */
156 simulate_login
:1, /* simulate a login instead of just starting a shell. */
157 change_environment
:1, /* change some environment vars to indicate the user su'd to.*/
158 same_session
:1, /* don't call setsid() with a command. */
159 suppress_pam_info
:1, /* don't print PAM info messages (Last login, etc.). */
160 pam_has_session
:1, /* PAM session opened */
161 pam_has_cred
:1, /* PAM cred established */
162 force_pty
:1, /* create pseudo-terminal */
163 restricted
:1; /* false for root user */
167 static sig_atomic_t volatile caught_signal
= false;
169 /* Signal handler for parent process. */
171 su_catch_sig(int sig
)
176 static void su_init_debug(void)
178 __UL_INIT_DEBUG_FROM_ENV(su
, SU_DEBUG_
, 0, SU_DEBUG
);
181 static void init_tty(struct su_context
*su
)
183 su
->isterm
= isatty(STDIN_FILENO
) ? 1 : 0;
184 DBG(TTY
, ul_debug("initialize [is-term=%s]", su
->isterm
? "true" : "false"));
186 get_terminal_name(&su
->tty_path
, &su
->tty_name
, &su
->tty_number
);
190 * Note, this function has to be possible call more than once. If the child is
191 * already dead than it returns saved result from the previous call.
193 static int wait_for_child(struct su_context
*su
)
195 pid_t pid
= (pid_t
) -1;
198 if (su
->child
== (pid_t
) -1)
199 return su
->childstatus
;
201 if (su
->child
!= (pid_t
) -1) {
203 * The "su" parent process spends all time here in waitpid(),
204 * but "su --pty" uses pty_proxy_master() and waitpid() is only
205 * called to pick up child status or to react to SIGSTOP.
207 DBG(SIG
, ul_debug("waiting for child [%d]...", su
->child
));
209 pid
= waitpid(su
->child
, &status
, WUNTRACED
);
211 if (pid
!= (pid_t
) - 1 && WIFSTOPPED(status
)) {
212 DBG(SIG
, ul_debug(" child got SIGSTOP -- stop all session"));
213 kill(getpid(), SIGSTOP
);
214 /* once we get here, we must have resumed */
216 DBG(SIG
, ul_debug(" session resumed -- continue"));
218 /* Let's go back to pty_proxy_master() */
219 if (su
->force_pty
&& ul_pty_is_running(su
->pty
)) {
220 DBG(SIG
, ul_debug(" leaving on child SIGSTOP"));
228 if (pid
!= (pid_t
) -1) {
229 if (WIFSIGNALED(status
)) {
230 fprintf(stderr
, "%s%s\n",
231 strsignal(WTERMSIG(status
)),
232 WCOREDUMP(status
) ? _(" (core dumped)")
234 status
= WTERMSIG(status
) + 128;
236 status
= WEXITSTATUS(status
);
238 DBG(SIG
, ul_debug("child %d is dead", su
->child
));
239 su
->child
= (pid_t
) -1; /* Don't use the PID anymore! */
240 su
->childstatus
= status
;
242 /* inform pty suff that we have no child anymore */
244 ul_pty_set_child(su
->pty
, (pid_t
) -1);
246 } else if (caught_signal
)
247 status
= caught_signal
+ 128;
251 DBG(SIG
, ul_debug("child status=%d", status
));
256 static void wait_for_child_cb(
258 pid_t child
__attribute__((__unused__
)))
260 wait_for_child((struct su_context
*) data
);
263 static void chownmod_pty(struct su_context
*su
)
265 gid_t gid
= su
->pwd
->pw_gid
;
266 mode_t mode
= (mode_t
) getlogindefs_num("TTYPERM", TTY_MODE
);
267 const char *grname
= getlogindefs_str("TTYGROUP", TTYGRPNAME
);
269 if (grname
&& *grname
) {
270 struct group
*gr
= getgrnam(grname
);
271 if (gr
) /* group by name */
273 else /* group by ID */
274 gid
= (gid_t
) getlogindefs_num("TTYGROUP", gid
);
277 if (ul_pty_chownmod_slave(su
->pty
,
280 warn(_("change owner or mode for pseudo-terminal failed"));
284 /* Log the fact that someone has run su to the user given by PW;
285 if SUCCESSFUL is true, they gave the correct password, etc. */
287 static void log_syslog(struct su_context
*su
, bool successful
)
289 DBG(LOG
, ul_debug("syslog logging"));
291 openlog(program_invocation_short_name
, LOG_PID
, LOG_AUTH
);
292 syslog(LOG_NOTICE
, "%s(to %s) %s on %s",
294 su
->runuser
? "FAILED RUNUSER " : "FAILED SU ",
295 su
->new_user
, su
->old_user
? : "",
296 su
->tty_name
? : "none");
301 * Log failed login attempts in _PATH_BTMP if that exists.
303 static void log_btmp(struct su_context
*su
)
308 DBG(LOG
, ul_debug("btmp logging"));
310 memset(&ut
, 0, sizeof(ut
));
311 str2memcpy(ut
.ut_user
,
312 su
->pwd
&& su
->pwd
->pw_name
? su
->pwd
->pw_name
: "(unknown)",
316 str2memcpy(ut
.ut_id
, su
->tty_number
, sizeof(ut
.ut_id
));
318 str2memcpy(ut
.ut_line
, su
->tty_name
, sizeof(ut
.ut_line
));
320 gettimeofday(&tv
, NULL
);
321 ut
.ut_tv
.tv_sec
= tv
.tv_sec
;
322 ut
.ut_tv
.tv_usec
= tv
.tv_usec
;
323 ut
.ut_type
= LOGIN_PROCESS
; /* XXX doesn't matter */
324 ut
.ut_pid
= getpid();
326 updwtmpx(_PATH_BTMP
, &ut
);
329 static int supam_conv( int num_msg
,
330 const struct pam_message
**msg
,
331 struct pam_response
**resp
,
334 struct su_context
*su
= (struct su_context
*) data
;
336 if (su
->suppress_pam_info
338 && msg
&& msg
[0]->msg_style
== PAM_TEXT_INFO
)
341 #ifdef HAVE_SECURITY_PAM_MISC_H
342 return misc_conv(num_msg
, msg
, resp
, data
);
343 #elif defined(HAVE_SECURITY_OPENPAM_H)
344 return openpam_ttyconv(num_msg
, msg
, resp
, data
);
348 static void supam_cleanup(struct su_context
*su
, int retcode
)
350 const int errsv
= errno
;
352 DBG(PAM
, ul_debug("cleanup"));
354 if (su
->pam_has_session
)
355 pam_close_session(su
->pamh
, 0);
356 if (su
->pam_has_cred
)
357 pam_setcred(su
->pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
358 pam_end(su
->pamh
, retcode
);
363 static void supam_export_environment(struct su_context
*su
)
367 DBG(PAM
, ul_debug("init environ[]"));
369 /* This is a copy but don't care to free as we exec later anyways. */
370 env
= pam_getenvlist(su
->pamh
);
372 while (env
&& *env
) {
373 if (putenv(*env
) != 0)
374 err(EXIT_FAILURE
, _("failed to modify environment"));
379 static void supam_authenticate(struct su_context
*su
)
381 const char *srvname
= NULL
;
384 srvname
= su
->runuser
?
385 (su
->simulate_login
? PAM_SRVNAME_RUNUSER_L
: PAM_SRVNAME_RUNUSER
) :
386 (su
->simulate_login
? PAM_SRVNAME_SU_L
: PAM_SRVNAME_SU
);
388 DBG(PAM
, ul_debug("start [name: %s]", srvname
));
390 rc
= pam_start(srvname
, su
->pwd
->pw_name
, &su
->conv
, &su
->pamh
);
391 if (is_pam_failure(rc
))
395 rc
= pam_set_item(su
->pamh
, PAM_TTY
, su
->tty_path
);
396 if (is_pam_failure(rc
))
400 rc
= pam_set_item(su
->pamh
, PAM_RUSER
, (const void *) su
->old_user
);
401 if (is_pam_failure(rc
))
406 * This is the only difference between runuser(1) and su(1). The command
407 * runuser(1) does not required authentication, because user is root.
410 errx(EXIT_FAILURE
, _("may not be used by non-root users"));
414 rc
= pam_authenticate(su
->pamh
, 0);
415 if (is_pam_failure(rc
))
418 /* Check password expiration and offer option to change it. */
419 rc
= pam_acct_mgmt(su
->pamh
, 0);
420 if (rc
== PAM_NEW_AUTHTOK_REQD
)
421 rc
= pam_chauthtok(su
->pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
423 log_syslog(su
, !is_pam_failure(rc
));
425 if (is_pam_failure(rc
)) {
428 DBG(PAM
, ul_debug("authentication failed"));
431 msg
= pam_strerror(su
->pamh
, rc
);
432 pam_end(su
->pamh
, rc
);
433 sleep(getlogindefs_num("FAIL_DELAY", 1));
434 errx(EXIT_FAILURE
, "%s", msg
? msg
: _("authentication failed"));
438 static void supam_open_session(struct su_context
*su
)
442 DBG(PAM
, ul_debug("opening session"));
444 rc
= pam_open_session(su
->pamh
, 0);
445 if (is_pam_failure(rc
)) {
446 supam_cleanup(su
, rc
);
447 errx(EXIT_FAILURE
, _("cannot open session: %s"),
448 pam_strerror(su
->pamh
, rc
));
450 su
->pam_has_session
= 1;
453 static void parent_setup_signals(struct su_context
*su
)
460 * 1) block all signals
462 DBG(SIG
, ul_debug("initialize signals"));
465 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
466 warn(_("cannot block signals"));
467 caught_signal
= true;
470 if (!caught_signal
) {
471 struct sigaction action
;
472 action
.sa_handler
= su_catch_sig
;
473 sigemptyset(&action
.sa_mask
);
476 sigemptyset(&ourset
);
478 /* 2a) add wanted signals to the mask (for session) */
479 if (!su
->same_session
480 && (sigaddset(&ourset
, SIGINT
)
481 || sigaddset(&ourset
, SIGQUIT
))) {
483 warn(_("cannot initialize signal mask for session"));
484 caught_signal
= true;
486 /* 2b) add wanted generic signals to the mask */
488 && (sigaddset(&ourset
, SIGTERM
)
489 || sigaddset(&ourset
, SIGALRM
))) {
491 warn(_("cannot initialize signal mask"));
492 caught_signal
= true;
495 /* 3a) set signal handlers (for session) */
498 && (sigaction(SIGINT
, &action
, &su
->oldact
[SIGINT_IDX
])
499 || sigaction(SIGQUIT
, &action
, &su
->oldact
[SIGQUIT_IDX
]))) {
501 warn(_("cannot set signal handler for session"));
502 caught_signal
= true;
505 /* 3b) set signal handlers */
507 && sigaction(SIGTERM
, &action
, &su
->oldact
[SIGTERM_IDX
])) {
509 warn(_("cannot set signal handler"));
510 caught_signal
= true;
513 /* 4) unblock wanted signals */
515 && sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
)) {
517 warn(_("cannot set signal mask"));
518 caught_signal
= true;
523 static void create_watching_parent(struct su_context
*su
)
525 struct sigaction action
;
528 DBG(MISC
, ul_debug("forking..."));
531 struct ul_pty_callbacks
*cb
;
534 ul_pty_set_callback_data(su
->pty
, (void *) su
);
536 cb
= ul_pty_get_callbacks(su
->pty
);
537 cb
->child_wait
= wait_for_child_cb
;
538 cb
->child_sigstop
= wait_for_child_cb
;
540 ul_pty_slave_echo(su
->pty
, 1);
543 if (ul_pty_setup(su
->pty
))
544 err(EXIT_FAILURE
, _("failed to create pseudo-terminal"));
547 fflush(stdout
); /* ??? */
549 /* set default handler for SIGCHLD */
550 sigemptyset(&action
.sa_mask
);
552 action
.sa_handler
= SIG_DFL
;
553 if (sigaction(SIGCHLD
, &action
, NULL
)) {
554 supam_cleanup(su
, PAM_ABORT
);
557 ul_pty_cleanup(su
->pty
);
559 err(EXIT_FAILURE
, _("cannot set child signal handler"));
562 switch ((int) (su
->child
= fork())) {
564 supam_cleanup(su
, PAM_ABORT
);
567 ul_pty_cleanup(su
->pty
);
569 err(EXIT_FAILURE
, _("cannot create child process"));
575 default: /* parent */
576 DBG(MISC
, ul_debug("child [pid=%d]", (int) su
->child
));
580 /* free unnecessary stuff */
581 free_getlogindefs_data();
583 /* In the parent watch the child. */
585 /* su without pam support does not have a helper that keeps
586 sitting on any directory so let's go to /. */
588 warn(_("cannot change directory to %s"), "/");
591 ul_pty_set_child(su
->pty
, su
->child
);
593 ul_pty_proxy_master(su
->pty
);
595 /* ul_pty_proxy_master() keeps classic signal handler are out of game */
596 caught_signal
= ul_pty_get_delivered_signal(su
->pty
);
598 ul_pty_cleanup(su
->pty
);
601 parent_setup_signals(su
);
607 status
= wait_for_child(su
);
611 DBG(SIG
, ul_debug("final child status=%d", status
));
613 if (caught_signal
&& su
->child
!= (pid_t
)-1) {
614 fprintf(stderr
, _("\nSession terminated, killing shell..."));
615 kill(su
->child
, SIGTERM
);
618 supam_cleanup(su
, PAM_SUCCESS
);
621 if (su
->child
!= (pid_t
)-1) {
622 DBG(SIG
, ul_debug("killing child"));
624 kill(su
->child
, SIGKILL
);
625 fprintf(stderr
, _(" ...killed.\n"));
628 /* Let's terminate itself with the received signal.
630 * It seems that shells use WIFSIGNALED() rather than our exit status
631 * value to detect situations when is necessary to cleanup (reset)
632 * terminal settings (kzak -- Jun 2013).
634 DBG(SIG
, ul_debug("restore signals setting"));
635 switch (caught_signal
) {
637 sigaction(SIGTERM
, &su
->oldact
[SIGTERM_IDX
], NULL
);
640 sigaction(SIGINT
, &su
->oldact
[SIGINT_IDX
], NULL
);
643 sigaction(SIGQUIT
, &su
->oldact
[SIGQUIT_IDX
], NULL
);
646 /* just in case that signal stuff initialization failed and
647 * caught_signal = true */
648 caught_signal
= SIGKILL
;
651 DBG(SIG
, ul_debug("self-send %d signal", caught_signal
));
652 kill(getpid(), caught_signal
);
655 DBG(MISC
, ul_debug("exiting [rc=%d]", status
));
659 /* Adds @name from the current environment to the whitelist. If @name is not
660 * set then nothing is added to the whitelist and returns 1.
662 static int env_whitelist_add(struct su_context
*su
, const char *name
)
664 const char *env
= getenv(name
);
668 if (strv_extend(&su
->env_whitelist_names
, name
))
670 if (strv_extend(&su
->env_whitelist_vals
, env
))
675 static int env_whitelist_setenv(struct su_context
*su
, int overwrite
)
681 STRV_FOREACH(one
, su
->env_whitelist_names
) {
682 rc
= setenv(*one
, su
->env_whitelist_vals
[i
], overwrite
);
691 /* Creates (add to) whitelist from comma delimited string */
692 static int env_whitelist_from_string(struct su_context
*su
, const char *str
)
694 char **all
= strv_split(str
, ",");
703 STRV_FOREACH(one
, all
)
704 env_whitelist_add(su
, *one
);
709 static void setenv_path(const struct passwd
*pw
)
713 DBG(MISC
, ul_debug("setting PATH"));
716 rc
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
718 else if ((rc
= logindefs_setenv("PATH", "ENV_SUPATH", NULL
)) != 0)
719 rc
= logindefs_setenv("PATH", "ENV_ROOTPATH", _PATH_DEFPATH_ROOT
);
722 err(EXIT_FAILURE
, _("failed to set the PATH environment variable"));
725 static void modify_environment(struct su_context
*su
, const char *shell
)
727 const struct passwd
*pw
= su
->pwd
;
730 DBG(MISC
, ul_debug("modify environ[]"));
732 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
734 * Unset all other environment variables, but follow
735 * --whitelist-environment if specified.
737 if (su
->simulate_login
) {
738 /* leave TERM unchanged */
739 env_whitelist_add(su
, "TERM");
741 /* Note that original su(1) has allocated environ[] by malloc
742 * to the number of expected variables. This seems unnecessary
743 * optimization as libc later re-alloc(current_size+2) and for
744 * empty environ[] the curren_size is zero. It seems better to
745 * keep all logic around environment in glibc's hands.
755 xsetenv("SHELL", shell
, 1);
759 xsetenv("HOME", pw
->pw_dir
, 1);
760 xsetenv("USER", pw
->pw_name
, 1);
761 xsetenv("LOGNAME", pw
->pw_name
, 1);
763 /* apply all from whitelist, but no overwrite */
764 env_whitelist_setenv(su
, 0);
766 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
768 } else if (su
->change_environment
) {
769 xsetenv("HOME", pw
->pw_dir
, 1);
771 xsetenv("SHELL", shell
, 1);
773 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
777 xsetenv("USER", pw
->pw_name
, 1);
778 xsetenv("LOGNAME", pw
->pw_name
, 1);
782 supam_export_environment(su
);
785 static void init_groups(struct su_context
*su
, gid_t
*groups
, size_t ngroups
)
789 DBG(MISC
, ul_debug("initialize groups"));
793 rc
= setgroups(ngroups
, groups
);
795 rc
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
798 supam_cleanup(su
, PAM_ABORT
);
799 err(EXIT_FAILURE
, _("cannot set groups"));
803 rc
= pam_setcred(su
->pamh
, PAM_ESTABLISH_CRED
);
804 if (is_pam_failure(rc
))
805 errx(EXIT_FAILURE
, _("failed to establish user credentials: %s"),
806 pam_strerror(su
->pamh
, rc
));
807 su
->pam_has_cred
= 1;
810 static void change_identity(const struct passwd
*pw
)
812 DBG(MISC
, ul_debug("changing identity [GID=%d, UID=%d]", pw
->pw_gid
, pw
->pw_uid
));
814 if (setgid(pw
->pw_gid
))
815 err(EXIT_FAILURE
, _("cannot set group id"));
816 if (setuid(pw
->pw_uid
))
817 err(EXIT_FAILURE
, _("cannot set user id"));
820 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
821 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
822 * N_ADDITIONAL_ARGS extra arguments.
824 static void run_shell(
825 struct su_context
*su
,
826 char const *shell
, char const *command
, char **additional_args
,
827 size_t n_additional_args
)
829 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
830 const char **args
= xcalloc(n_args
, sizeof *args
);
833 DBG(MISC
, ul_debug("starting shell [shell=%s, command=\"%s\"%s%s]",
835 su
->simulate_login
? " login" : "",
836 su
->fast_startup
? " fast-start" : ""));
838 if (su
->simulate_login
) {
840 char *shell_basename
;
842 shell_basename
= basename(shell
);
843 arg0
= xmalloc(strlen(shell_basename
) + 2);
845 strcpy(arg0
+ 1, shell_basename
);
848 args
[0] = basename(shell
);
850 if (su
->fast_startup
)
851 args
[argno
++] = "-f";
853 args
[argno
++] = "-c";
854 args
[argno
++] = command
;
857 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
858 args
[argno
+ n_additional_args
] = NULL
;
859 execv(shell
, (char **)args
);
863 /* Return true if SHELL is a restricted shell (one not returned by
864 * getusershell), else false, meaning it is a standard shell.
866 static bool is_restricted_shell(const char *shell
)
871 while ((line
= getusershell()) != NULL
) {
872 if (*line
!= '#' && !strcmp(line
, shell
)) {
879 DBG(MISC
, ul_debug("%s is restricted shell (not in /etc/shells)", shell
));
883 static void usage_common(void)
885 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout
);
886 fputs(_(" -w, --whitelist-environment <list> don't reset specified variables\n"), stdout
);
887 fputs(USAGE_SEPARATOR
, stdout
);
889 fputs(_(" -g, --group <group> specify the primary group\n"), stdout
);
890 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout
);
891 fputs(USAGE_SEPARATOR
, stdout
);
893 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout
);
894 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout
);
895 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
896 " and do not create a new session\n"), stdout
);
897 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout
);
898 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout
);
899 fputs(_(" -P, --pty create a new pseudo-terminal\n"), stdout
);
901 fputs(USAGE_SEPARATOR
, stdout
);
902 printf(USAGE_HELP_OPTIONS(33));
905 static void usage_runuser(void)
907 fputs(USAGE_HEADER
, stdout
);
909 _(" %1$s [options] -u <user> [[--] <command>]\n"
910 " %1$s [options] [-] [<user> [<argument>...]]\n"),
911 program_invocation_short_name
);
913 fputs(USAGE_SEPARATOR
, stdout
);
914 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
915 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
916 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout
);
918 fputs(USAGE_OPTIONS
, stdout
);
919 fputs(_(" -u, --user <user> username\n"), stdout
);
921 fputs(USAGE_SEPARATOR
, stdout
);
923 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
926 static void usage_su(void)
928 fputs(USAGE_HEADER
, stdout
);
930 _(" %s [options] [-] [<user> [<argument>...]]\n"),
931 program_invocation_short_name
);
933 fputs(USAGE_SEPARATOR
, stdout
);
934 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
935 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout
);
937 fputs(USAGE_OPTIONS
, stdout
);
940 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
943 static void __attribute__((__noreturn__
)) usage(int mode
)
953 static void load_config(void *data
)
955 struct su_context
*su
= (struct su_context
*) data
;
957 DBG(MISC
, ul_debug("loading logindefs"));
958 #ifndef HAVE_LIBECONF
959 logindefs_load_file(_PATH_LOGINDEFS
);
961 logindefs_load_file(su
->runuser
? _PATH_LOGINDEFS_RUNUSER
: _PATH_LOGINDEFS_SU
);
965 * Returns 1 if the current user is not root
967 static int is_not_root(void)
969 const uid_t ruid
= getuid();
970 const uid_t euid
= geteuid();
972 /* if we're really root and aren't running setuid */
973 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
976 /* Don't rely on PAM and reset the most important limits. */
977 static void sanitize_prlimits(void)
979 #ifdef HAVE_SYS_RESOURCE_H
980 struct rlimit lm
= { .rlim_cur
= 0, .rlim_max
= 0 };
984 setrlimit(RLIMIT_NICE
, &lm
);
987 setrlimit(RLIMIT_RTPRIO
, &lm
);
990 /* reset to unlimited */
991 lm
.rlim_cur
= RLIM_INFINITY
;
992 lm
.rlim_max
= RLIM_INFINITY
;
993 setrlimit(RLIMIT_FSIZE
, &lm
);
994 setrlimit(RLIMIT_AS
, &lm
);
996 /* reset soft limit only */
997 getrlimit(RLIMIT_NOFILE
, &lm
);
998 if (lm
.rlim_cur
!= FD_SETSIZE
) {
999 lm
.rlim_cur
= FD_SETSIZE
;
1000 setrlimit(RLIMIT_NOFILE
, &lm
);
1005 static gid_t
add_supp_group(const char *name
, gid_t
**groups
, size_t *ngroups
)
1009 if (*ngroups
>= NGROUPS_MAX
)
1011 P_("specifying more than %d supplemental group is not possible",
1012 "specifying more than %d supplemental groups is not possible",
1013 NGROUPS_MAX
- 1), NGROUPS_MAX
- 1);
1015 gr
= getgrnam(name
);
1017 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
1019 DBG(MISC
, ul_debug("add %s group [name=%s, GID=%d]", name
, gr
->gr_name
, (int) gr
->gr_gid
));
1021 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
1022 (*groups
)[*ngroups
] = gr
->gr_gid
;
1028 int su_main(int argc
, char **argv
, int mode
)
1030 struct su_context _su
= {
1031 .conv
= { supam_conv
, NULL
},
1032 .runuser
= (mode
== RUNUSER_MODE
? 1 : 0),
1033 .change_environment
= 1,
1034 .new_user
= DEFAULT_USER
1038 char *command
= NULL
;
1039 int request_same_session
= 0;
1042 gid_t
*groups
= NULL
;
1044 bool use_supp
= false;
1045 bool use_gid
= false;
1048 static const struct option longopts
[] = {
1049 {"command", required_argument
, NULL
, 'c'},
1050 {"session-command", required_argument
, NULL
, 'C'},
1051 {"fast", no_argument
, NULL
, 'f'},
1052 {"login", no_argument
, NULL
, 'l'},
1053 {"preserve-environment", no_argument
, NULL
, 'p'},
1054 {"pty", no_argument
, NULL
, 'P'},
1055 {"shell", required_argument
, NULL
, 's'},
1056 {"group", required_argument
, NULL
, 'g'},
1057 {"supp-group", required_argument
, NULL
, 'G'},
1058 {"user", required_argument
, NULL
, 'u'}, /* runuser only */
1059 {"whitelist-environment", required_argument
, NULL
, 'w'},
1060 {"help", no_argument
, 0, 'h'},
1061 {"version", no_argument
, 0, 'V'},
1064 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
1065 { 'm', 'w' }, /* preserve-environment, whitelist-environment */
1066 { 'p', 'w' }, /* preserve-environment, whitelist-environment */
1069 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
1071 setlocale(LC_ALL
, "");
1072 bindtextdomain(PACKAGE
, LOCALEDIR
);
1073 textdomain(PACKAGE
);
1074 close_stdout_atexit();
1077 su
->conv
.appdata_ptr
= (void *) su
;
1080 getopt_long(argc
, argv
, "c:fg:G:lmpPs:u:hVw:", longopts
,
1083 err_exclusive_options(optc
, longopts
, excl
, excl_st
);
1092 request_same_session
= 1;
1096 su
->fast_startup
= true;
1101 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
1106 add_supp_group(optarg
, &groups
, &ngroups
);
1110 su
->simulate_login
= true;
1115 su
->change_environment
= false;
1119 env_whitelist_from_string(su
, optarg
);
1126 errx(EXIT_FAILURE
, _("--pty is not supported for your system"));
1136 errtryhelp(EXIT_FAILURE
);
1137 su
->runuser_uopt
= 1;
1138 su
->new_user
= optarg
;
1145 print_version(EXIT_SUCCESS
);
1147 errtryhelp(EXIT_FAILURE
);
1151 su
->restricted
= is_not_root();
1153 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
1154 su
->simulate_login
= true;
1158 if (su
->simulate_login
&& !su
->change_environment
) {
1160 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
1161 su
->change_environment
= true;
1166 /* runuser -u <user> <command>
1168 * If -u <user> is not specified, then follow traditional su(1) behavior and
1171 if (su
->runuser_uopt
) {
1172 if (shell
|| su
->fast_startup
|| command
|| su
->simulate_login
)
1174 _("options --{shell,fast,command,session-command,login} and "
1175 "--user are mutually exclusive"));
1177 errx(EXIT_FAILURE
, _("no command was specified"));
1183 su
->new_user
= argv
[optind
++];
1187 if ((use_supp
|| use_gid
) && su
->restricted
)
1189 _("only root can specify alternative groups"));
1191 logindefs_set_loader(load_config
, (void *) su
);
1194 su
->pwd
= xgetpwnam(su
->new_user
, &su
->pwdbuf
);
1196 || !su
->pwd
->pw_passwd
1197 || !su
->pwd
->pw_name
|| !*su
->pwd
->pw_name
1198 || !su
->pwd
->pw_dir
|| !*su
->pwd
->pw_dir
)
1200 _("user %s does not exist or the user entry does not "
1201 "contain all the required fields"), su
->new_user
);
1203 su
->new_user
= su
->pwd
->pw_name
;
1204 su
->old_user
= xgetlogin();
1206 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
1207 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
1209 if (use_supp
&& !use_gid
)
1210 su
->pwd
->pw_gid
= groups
[0];
1212 su
->pwd
->pw_gid
= gid
;
1214 supam_authenticate(su
);
1216 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
1217 su
->same_session
= 1;
1219 /* initialize shell variable only if "-u <user>" not specified */
1220 if (su
->runuser_uopt
) {
1223 if (!shell
&& !su
->change_environment
)
1224 shell
= getenv("SHELL");
1227 && strcmp(shell
, su
->pwd
->pw_shell
) != 0
1229 && is_restricted_shell(su
->pwd
->pw_shell
)) {
1230 /* The user being su'd to has a nonstandard shell, and
1231 * so is probably a uucp account or has restricted
1232 * access. Don't compromise the account by allowing
1233 * access with a standard shell.
1235 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
1238 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
1241 init_groups(su
, groups
, ngroups
);
1243 if (!su
->simulate_login
|| command
)
1244 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
1246 sanitize_prlimits();
1248 supam_open_session(su
);
1251 if (su
->force_pty
) {
1252 ON_DBG(PTY
, ul_pty_init_debug(0xffff));
1254 su
->pty
= ul_new_pty(su
->isterm
);
1256 err(EXIT_FAILURE
, _("failed to allocate pty handler"));
1259 create_watching_parent(su
);
1260 /* Now we're in the child. */
1266 change_identity(su
->pwd
);
1267 if (!su
->same_session
) {
1268 /* note that on --pty we call setsid() in ul_pty_init_slave() */
1269 DBG(MISC
, ul_debug("call setsid()"));
1274 ul_pty_init_slave(su
->pty
);
1276 /* Set environment after pam_open_session, which may put KRB5CCNAME
1277 into the pam_env, etc. */
1279 modify_environment(su
, shell
);
1281 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
1282 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
1284 /* http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_end */
1285 (void) pam_end(su
->pamh
, PAM_SUCCESS
|PAM_DATA_SILENT
);
1288 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
1290 execvp(argv
[optind
], &argv
[optind
]);
1291 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);