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 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>
47 #include "pathnames.h"
49 #include "closestream.h"
54 #include "logindefs.h"
55 #include "su-common.h"
57 /* name of the pam configuration files. separate configs for su and su - */
58 #define PAM_SRVNAME_SU "su"
59 #define PAM_SRVNAME_SU_L "su-l"
61 #define PAM_SRVNAME_RUNUSER "runuser"
62 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
64 #define _PATH_LOGINDEFS_SU "/etc/default/su"
65 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
67 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
69 /* The shell to run if none is given in the user's passwd entry. */
70 #define DEFAULT_SHELL "/bin/sh"
72 /* The user to become if none is specified. */
73 #define DEFAULT_USER "root"
75 #ifndef HAVE_ENVIRON_DECL
76 extern char **environ
;
80 EXIT_CANNOT_INVOKE
= 126,
85 * su/runuser control struct
88 pam_handle_t
*pamh
; /* PAM handler */
89 struct pam_conv conv
; /* PAM conversation */
91 struct passwd
*pwd
; /* new user info */
92 char *pwdbuf
; /* pwd strings */
94 const char *tty_name
; /* tty_path without /dev prefix */
95 const char *tty_number
; /* end of the tty_path */
97 char *new_user
; /* wanted user */
98 char *old_user
; /* orginal user */
100 unsigned int runuser
:1, /* flase=su, true=runuser */
101 runuser_uopt
:1, /* runuser -u specified */
102 isterm
:1, /* is stdin terminal? */
103 fast_startup
:1, /* pass the `-f' option to the subshell. */
104 simulate_login
:1, /* simulate a login instead of just starting a shell. */
105 change_environment
:1, /* change some environment vars to indicate the user su'd to.*/
106 same_session
:1, /* don't call setsid() with a command. */
107 suppress_pam_info
:1, /* don't print PAM info messages (Last login, etc.). */
108 pam_has_session
:1, /* PAM session opened */
109 pam_has_cred
:1, /* PAM cred established */
110 restricted
:1; /* false for root user */
114 static sig_atomic_t volatile caught_signal
= false;
116 /* Signal handler for parent process. */
118 su_catch_sig(int sig
)
123 static void init_tty(struct su_context
*su
)
125 su
->isterm
= isatty(STDIN_FILENO
) ? 1 : 0;
127 get_terminal_name(NULL
, &su
->tty_name
, &su
->tty_number
);
130 /* Log the fact that someone has run su to the user given by PW;
131 if SUCCESSFUL is true, they gave the correct password, etc. */
133 static void log_syslog(struct su_context
*su
, bool successful
)
135 openlog(program_invocation_short_name
, 0, LOG_AUTH
);
136 syslog(LOG_NOTICE
, "%s(to %s) %s on %s",
138 su
->runuser
? "FAILED RUNUSER " : "FAILED SU ",
139 su
->new_user
, su
->old_user
? : "",
140 su
->tty_name
? : "none");
145 * Log failed login attempts in _PATH_BTMP if that exists.
147 static void log_btmp(struct su_context
*su
)
152 memset(&ut
, 0, sizeof(ut
));
154 su
->pwd
&& su
->pwd
->pw_name
? su
->pwd
->pw_name
: "(unknown)",
158 xstrncpy(ut
.ut_id
, su
->tty_number
, sizeof(ut
.ut_id
));
160 xstrncpy(ut
.ut_line
, su
->tty_name
, sizeof(ut
.ut_line
));
162 gettimeofday(&tv
, NULL
);
163 ut
.ut_tv
.tv_sec
= tv
.tv_sec
;
164 ut
.ut_tv
.tv_usec
= tv
.tv_usec
;
165 ut
.ut_type
= LOGIN_PROCESS
; /* XXX doesn't matter */
166 ut
.ut_pid
= getpid();
168 updwtmpx(_PATH_BTMP
, &ut
);
171 static int supam_conv( int num_msg
,
172 const struct pam_message
**msg
,
173 struct pam_response
**resp
,
176 struct su_context
*su
= (struct su_context
*) data
;
178 if (su
->suppress_pam_info
180 && msg
&& msg
[0]->msg_style
== PAM_TEXT_INFO
)
183 #ifdef HAVE_SECURITY_PAM_MISC_H
184 return misc_conv(num_msg
, msg
, resp
, data
);
185 #elif defined(HAVE_SECURITY_OPENPAM_H)
186 return openpam_ttyconv(num_msg
, msg
, resp
, data
);
190 static void supam_cleanup(struct su_context
*su
, int retcode
)
192 const int errsv
= errno
;
194 if (su
->pam_has_session
)
195 pam_close_session(su
->pamh
, 0);
196 if (su
->pam_has_cred
)
197 pam_setcred(su
->pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
198 pam_end(su
->pamh
, retcode
);
203 static void supam_export_environment(struct su_context
*su
)
205 /* This is a copy but don't care to free as we exec later anyways. */
206 char **env
= pam_getenvlist(su
->pamh
);
208 while (env
&& *env
) {
209 if (putenv(*env
) != 0)
210 err(EXIT_FAILURE
, _("failed to modify environment"));
215 static void supam_authenticate(struct su_context
*su
)
217 const char *srvname
= NULL
;
220 srvname
= su
->runuser
?
221 (su
->simulate_login
? PAM_SRVNAME_RUNUSER_L
: PAM_SRVNAME_RUNUSER
) :
222 (su
->simulate_login
? PAM_SRVNAME_SU_L
: PAM_SRVNAME_SU
);
224 rc
= pam_start(srvname
, su
->pwd
->pw_name
, &su
->conv
, &su
->pamh
);
225 if (is_pam_failure(rc
))
229 rc
= pam_set_item(su
->pamh
, PAM_TTY
, su
->tty_name
);
230 if (is_pam_failure(rc
))
234 rc
= pam_set_item(su
->pamh
, PAM_RUSER
, (const void *) su
->old_user
);
235 if (is_pam_failure(rc
))
240 * This is the only difference between runuser(1) and su(1). The command
241 * runuser(1) does not required authentication, because user is root.
244 errx(EXIT_FAILURE
, _("may not be used by non-root users"));
248 rc
= pam_authenticate(su
->pamh
, 0);
249 if (is_pam_failure(rc
))
252 /* Check password expiration and offer option to change it. */
253 rc
= pam_acct_mgmt(su
->pamh
, 0);
254 if (rc
== PAM_NEW_AUTHTOK_REQD
)
255 rc
= pam_chauthtok(su
->pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
257 log_syslog(su
, !is_pam_failure(rc
));
259 if (is_pam_failure(rc
)) {
264 msg
= pam_strerror(su
->pamh
, rc
);
265 pam_end(su
->pamh
, rc
);
266 sleep(getlogindefs_num("FAIL_DELAY", 1));
267 errx(EXIT_FAILURE
, "%s", msg
? msg
: _("incorrect password"));
271 static void supam_open_session(struct su_context
*su
)
273 int rc
= pam_open_session(su
->pamh
, 0);
275 if (is_pam_failure(rc
)) {
276 supam_cleanup(su
, rc
);
277 errx(EXIT_FAILURE
, _("cannot open session: %s"),
278 pam_strerror(su
->pamh
, rc
));
280 su
->pam_has_session
= 1;
284 static void create_watching_parent(struct su_context
*su
)
288 struct sigaction oldact
[3];
291 switch ((int) (child
= fork())) {
293 supam_cleanup(su
, PAM_ABORT
);
294 err(EXIT_FAILURE
, _("cannot create child process"));
300 default: /* parent */
304 memset(oldact
, 0, sizeof(oldact
));
307 /* In the parent watch the child. */
309 /* su without pam support does not have a helper that keeps
310 sitting on any directory so let's go to /. */
312 warn(_("cannot change directory to %s"), "/");
315 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
316 warn(_("cannot block signals"));
317 caught_signal
= true;
319 if (!caught_signal
) {
320 struct sigaction action
;
321 action
.sa_handler
= su_catch_sig
;
322 sigemptyset(&action
.sa_mask
);
324 sigemptyset(&ourset
);
325 if (!su
->same_session
) {
326 if (sigaddset(&ourset
, SIGINT
)
327 || sigaddset(&ourset
, SIGQUIT
)) {
328 warn(_("cannot set signal handler"));
329 caught_signal
= true;
332 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
333 || sigaddset(&ourset
, SIGALRM
)
334 || sigaction(SIGTERM
, &action
,
336 || sigprocmask(SIG_UNBLOCK
, &ourset
,
338 warn(_("cannot set signal handler"));
339 caught_signal
= true;
341 if (!caught_signal
&& !su
->same_session
342 && (sigaction(SIGINT
, &action
, &oldact
[1])
343 || sigaction(SIGQUIT
, &action
, &oldact
[2]))) {
344 warn(_("cannot set signal handler"));
345 caught_signal
= true;
348 if (!caught_signal
) {
351 pid
= waitpid(child
, &status
, WUNTRACED
);
353 if (pid
!= (pid_t
) - 1 && WIFSTOPPED(status
)) {
354 kill(getpid(), SIGSTOP
);
355 /* once we get here, we must have resumed */
360 if (pid
!= (pid_t
) - 1) {
361 if (WIFSIGNALED(status
)) {
362 fprintf(stderr
, "%s%s\n",
363 strsignal(WTERMSIG(status
)),
364 WCOREDUMP(status
) ? _(" (core dumped)")
366 status
= WTERMSIG(status
) + 128;
368 status
= WEXITSTATUS(status
);
369 } else if (caught_signal
)
370 status
= caught_signal
+ 128;
377 fprintf(stderr
, _("\nSession terminated, killing shell..."));
378 kill(child
, SIGTERM
);
381 supam_cleanup(su
, PAM_SUCCESS
);
385 kill(child
, SIGKILL
);
386 fprintf(stderr
, _(" ...killed.\n"));
388 /* Let's terminate itself with the received signal.
390 * It seems that shells use WIFSIGNALED() rather than our exit status
391 * value to detect situations when is necessary to cleanup (reset)
392 * terminal settings (kzak -- Jun 2013).
394 switch (caught_signal
) {
396 sigaction(SIGTERM
, &oldact
[0], NULL
);
399 sigaction(SIGINT
, &oldact
[1], NULL
);
402 sigaction(SIGQUIT
, &oldact
[2], NULL
);
405 /* just in case that signal stuff initialization failed and
406 * caught_signal = true */
407 caught_signal
= SIGKILL
;
410 kill(getpid(), caught_signal
);
415 static void setenv_path(const struct passwd
*pw
)
420 rc
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
422 else if ((rc
= logindefs_setenv("PATH", "ENV_ROOTPATH", NULL
)) != 0)
423 rc
= logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT
);
426 err(EXIT_FAILURE
, _("failed to set the PATH environment variable"));
429 static void modify_environment(struct su_context
*su
, const char *shell
)
431 const struct passwd
*pw
= su
->pwd
;
433 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
434 * Unset all other environment variables.
436 if (su
->simulate_login
) {
437 char *term
= getenv("TERM");
439 term
= xstrdup(term
);
441 environ
= xmalloc((6 + ! !term
) * sizeof(char *));
444 xsetenv("TERM", term
, 1);
448 xsetenv("HOME", pw
->pw_dir
, 1);
450 xsetenv("SHELL", shell
, 1);
451 xsetenv("USER", pw
->pw_name
, 1);
452 xsetenv("LOGNAME", pw
->pw_name
, 1);
455 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
457 } else if (su
->change_environment
) {
458 xsetenv("HOME", pw
->pw_dir
, 1);
460 xsetenv("SHELL", shell
, 1);
462 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
466 xsetenv("USER", pw
->pw_name
, 1);
467 xsetenv("LOGNAME", pw
->pw_name
, 1);
471 supam_export_environment(su
);
474 static void init_groups(struct su_context
*su
, gid_t
*groups
, size_t ngroups
)
480 rc
= setgroups(ngroups
, groups
);
482 rc
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
485 supam_cleanup(su
, PAM_ABORT
);
486 err(EXIT_FAILURE
, _("cannot set groups"));
490 rc
= pam_setcred(su
->pamh
, PAM_ESTABLISH_CRED
);
491 if (is_pam_failure(rc
))
492 errx(EXIT_FAILURE
, _("failed to user credentials: %s"),
493 pam_strerror(su
->pamh
, rc
));
494 su
->pam_has_cred
= 1;
497 static void change_identity(const struct passwd
*pw
)
499 if (setgid(pw
->pw_gid
))
500 err(EXIT_FAILURE
, _("cannot set group id"));
501 if (setuid(pw
->pw_uid
))
502 err(EXIT_FAILURE
, _("cannot set user id"));
505 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
506 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
507 * N_ADDITIONAL_ARGS extra arguments.
509 static void run_shell(
510 struct su_context
*su
,
511 char const *shell
, char const *command
, char **additional_args
,
512 size_t n_additional_args
)
514 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
515 char const **args
= xcalloc(n_args
, sizeof *args
);
519 if (su
->simulate_login
) {
521 char *shell_basename
;
523 shell_basename
= basename(shell
);
524 arg0
= xmalloc(strlen(shell_basename
) + 2);
526 strcpy(arg0
+ 1, shell_basename
);
529 args
[0] = basename(shell
);
531 if (su
->fast_startup
)
532 args
[argno
++] = "-f";
534 args
[argno
++] = "-c";
535 args
[argno
++] = command
;
538 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
539 args
[argno
+ n_additional_args
] = NULL
;
540 execv(shell
, (char **)args
);
542 rc
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
543 err(rc
, _("failed to execute %s"), shell
);
546 /* Return true if SHELL is a restricted shell (one not returned by
547 * getusershell), else false, meaning it is a standard shell.
549 static bool is_restricted_shell(const char *shell
)
554 while ((line
= getusershell()) != NULL
) {
555 if (*line
!= '#' && !strcmp(line
, shell
)) {
564 static void usage_common(void)
566 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout
);
567 fputs(_(" -g, --group <group> specify the primary group\n"), stdout
);
568 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout
);
569 fputs(USAGE_SEPARATOR
, stdout
);
571 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout
);
572 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout
);
573 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
574 " and do not create a new session\n"), stdout
);
575 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout
);
576 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout
);
578 fputs(USAGE_SEPARATOR
, stdout
);
579 printf(USAGE_HELP_OPTIONS(33));
583 static void __attribute__ ((__noreturn__
)) usage_runuser(void)
585 fputs(USAGE_HEADER
, stdout
);
587 _(" %1$s [options] -u <user> [[--] <command>]\n"
588 " %1$s [options] [-] [<user> [<argument>...]]\n"),
589 program_invocation_short_name
);
591 fputs(USAGE_SEPARATOR
, stdout
);
592 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
593 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
594 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout
);
596 fputs(USAGE_OPTIONS
, stdout
);
597 fputs(_(" -u, --user <user> username\n"), stdout
);
599 fputs(USAGE_SEPARATOR
, stdout
);
601 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
605 static void __attribute__ ((__noreturn__
)) usage_su(void)
607 fputs(USAGE_HEADER
, stdout
);
609 _(" %s [options] [-] [<user> [<argument>...]]\n"),
610 program_invocation_short_name
);
612 fputs(USAGE_SEPARATOR
, stdout
);
613 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
614 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout
);
616 fputs(USAGE_OPTIONS
, stdout
);
619 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
623 static void usage(int mode
)
631 static void load_config(void *data
)
633 struct su_context
*su
= (struct su_context
*) data
;
635 logindefs_load_file(su
->runuser
? _PATH_LOGINDEFS_RUNUSER
: _PATH_LOGINDEFS_SU
);
636 logindefs_load_file(_PATH_LOGINDEFS
);
640 * Returns 1 if the current user is not root
642 static int is_not_root(void)
644 const uid_t ruid
= getuid();
645 const uid_t euid
= geteuid();
647 /* if we're really root and aren't running setuid */
648 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
651 static gid_t
add_supp_group(const char *name
, gid_t
**groups
, size_t *ngroups
)
655 if (*ngroups
>= NGROUPS_MAX
)
657 P_("specifying more than %d supplemental group is not possible",
658 "specifying more than %d supplemental groups is not possible",
659 NGROUPS_MAX
- 1), NGROUPS_MAX
- 1);
663 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
665 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
666 (*groups
)[*ngroups
] = gr
->gr_gid
;
672 int su_main(int argc
, char **argv
, int mode
)
674 struct su_context _su
= {
675 .conv
= { supam_conv
, NULL
},
676 .runuser
= (mode
== RUNUSER_MODE
? 1 : 0),
677 .change_environment
= 1,
678 .new_user
= DEFAULT_USER
682 char *command
= NULL
;
683 int request_same_session
= 0;
686 gid_t
*groups
= NULL
;
688 bool use_supp
= false;
689 bool use_gid
= false;
692 static const struct option longopts
[] = {
693 {"command", required_argument
, NULL
, 'c'},
694 {"session-command", required_argument
, NULL
, 'C'},
695 {"fast", no_argument
, NULL
, 'f'},
696 {"login", no_argument
, NULL
, 'l'},
697 {"preserve-environment", no_argument
, NULL
, 'p'},
698 {"shell", required_argument
, NULL
, 's'},
699 {"group", required_argument
, NULL
, 'g'},
700 {"supp-group", required_argument
, NULL
, 'G'},
701 {"user", required_argument
, NULL
, 'u'}, /* runuser only */
702 {"help", no_argument
, 0, 'h'},
703 {"version", no_argument
, 0, 'V'},
707 setlocale(LC_ALL
, "");
708 bindtextdomain(PACKAGE
, LOCALEDIR
);
710 atexit(close_stdout
);
712 su
->conv
.appdata_ptr
= (void *) su
;
715 getopt_long(argc
, argv
, "c:fg:G:lmps:u:hV", longopts
,
724 request_same_session
= 1;
728 su
->fast_startup
= true;
733 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
738 add_supp_group(optarg
, &groups
, &ngroups
);
742 su
->simulate_login
= true;
747 su
->change_environment
= false;
756 errtryhelp(EXIT_FAILURE
);
757 su
->runuser_uopt
= 1;
758 su
->new_user
= optarg
;
765 printf(UTIL_LINUX_VERSION
);
769 errtryhelp(EXIT_FAILURE
);
773 su
->restricted
= is_not_root();
775 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
776 su
->simulate_login
= true;
780 if (su
->simulate_login
&& !su
->change_environment
) {
782 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
783 su
->change_environment
= true;
788 /* runuser -u <user> <command> */
789 if (su
->runuser_uopt
) {
790 if (shell
|| su
->fast_startup
|| command
|| su
->simulate_login
)
792 _("options --{shell,fast,command,session-command,login} and "
793 "--user are mutually exclusive"));
795 errx(EXIT_FAILURE
, _("no command was specified"));
798 /* fallthrough if -u <user> is not specified, then follow
799 * traditional su(1) behavior
803 su
->new_user
= argv
[optind
++];
807 if ((use_supp
|| use_gid
) && su
->restricted
)
809 _("only root can specify alternative groups"));
811 logindefs_set_loader(load_config
, (void *) su
);
814 su
->pwd
= xgetpwnam(su
->new_user
, &su
->pwdbuf
);
816 || !su
->pwd
->pw_passwd
817 || !su
->pwd
->pw_name
|| !*su
->pwd
->pw_name
818 || !su
->pwd
->pw_dir
|| !*su
->pwd
->pw_dir
)
819 errx(EXIT_FAILURE
, _("user %s does not exist"), su
->new_user
);
821 su
->new_user
= su
->pwd
->pw_name
;
822 su
->old_user
= xgetlogin();
824 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
825 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
827 if (use_supp
&& !use_gid
)
828 su
->pwd
->pw_gid
= groups
[0];
830 su
->pwd
->pw_gid
= gid
;
832 supam_authenticate(su
);
834 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
835 su
->same_session
= 1;
837 /* initialize shell variable only if "-u <user>" not specified */
838 if (su
->runuser_uopt
) {
841 if (!shell
&& !su
->change_environment
)
842 shell
= getenv("SHELL");
846 && is_restricted_shell(su
->pwd
->pw_shell
)) {
847 /* The user being su'd to has a nonstandard shell, and
848 * so is probably a uucp account or has restricted
849 * access. Don't compromise the account by allowing
850 * access with a standard shell.
852 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
855 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
858 init_groups(su
, groups
, ngroups
);
860 if (!su
->simulate_login
|| command
)
861 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
863 supam_open_session(su
);
865 create_watching_parent(su
);
866 /* Now we're in the child. */
868 change_identity(su
->pwd
);
869 if (!su
->same_session
)
872 /* Set environment after pam_open_session, which may put KRB5CCNAME
873 into the pam_env, etc. */
875 modify_environment(su
, shell
);
877 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
878 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
881 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
883 execvp(argv
[optind
], &argv
[optind
]);
884 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);