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 saved_errno
= 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 retval
= pam_start(srvname
, su
->pwd
->pw_name
, &su
->conv
, &su
->pamh
);
225 if (is_pam_failure(retval
))
229 retval
= pam_set_item(su
->pamh
, PAM_TTY
, su
->tty_name
);
230 if (is_pam_failure(retval
))
234 retval
= pam_set_item(su
->pamh
, PAM_RUSER
, (const void *) su
->old_user
);
235 if (is_pam_failure(retval
))
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 retval
= pam_authenticate(su
->pamh
, 0);
249 if (is_pam_failure(retval
))
252 retval
= pam_acct_mgmt(su
->pamh
, 0);
253 if (retval
== PAM_NEW_AUTHTOK_REQD
) {
254 /* Password has expired. Offer option to change it. */
255 retval
= pam_chauthtok(su
->pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
260 log_syslog(su
, !is_pam_failure(retval
));
262 if (is_pam_failure(retval
)) {
267 msg
= pam_strerror(su
->pamh
, retval
);
268 pam_end(su
->pamh
, retval
);
269 sleep(getlogindefs_num("FAIL_DELAY", 1));
270 errx(EXIT_FAILURE
, "%s", msg
? msg
: _("incorrect password"));
275 create_watching_parent(struct su_context
*su
)
279 struct sigaction oldact
[3];
283 retval
= pam_open_session(su
->pamh
, 0);
284 if (is_pam_failure(retval
)) {
285 supam_cleanup(su
, retval
);
286 errx(EXIT_FAILURE
, _("cannot open session: %s"),
287 pam_strerror(su
->pamh
, retval
));
289 su
->pam_has_session
= 1;
291 memset(oldact
, 0, sizeof(oldact
));
294 if (child
== (pid_t
) - 1) {
295 supam_cleanup(su
, PAM_ABORT
);
296 err(EXIT_FAILURE
, _("cannot create child process"));
299 /* the child proceeds to run the shell */
303 /* In the parent watch the child. */
305 /* su without pam support does not have a helper that keeps
306 sitting on any directory so let's go to /. */
308 warn(_("cannot change directory to %s"), "/");
311 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
312 warn(_("cannot block signals"));
313 caught_signal
= true;
315 if (!caught_signal
) {
316 struct sigaction action
;
317 action
.sa_handler
= su_catch_sig
;
318 sigemptyset(&action
.sa_mask
);
320 sigemptyset(&ourset
);
321 if (!su
->same_session
) {
322 if (sigaddset(&ourset
, SIGINT
)
323 || sigaddset(&ourset
, SIGQUIT
)) {
324 warn(_("cannot set signal handler"));
325 caught_signal
= true;
328 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
329 || sigaddset(&ourset
, SIGALRM
)
330 || sigaction(SIGTERM
, &action
,
332 || sigprocmask(SIG_UNBLOCK
, &ourset
,
334 warn(_("cannot set signal handler"));
335 caught_signal
= true;
337 if (!caught_signal
&& !su
->same_session
338 && (sigaction(SIGINT
, &action
, &oldact
[1])
339 || sigaction(SIGQUIT
, &action
, &oldact
[2]))) {
340 warn(_("cannot set signal handler"));
341 caught_signal
= true;
344 if (!caught_signal
) {
347 pid
= waitpid(child
, &status
, WUNTRACED
);
349 if (pid
!= (pid_t
) - 1 && WIFSTOPPED(status
)) {
350 kill(getpid(), SIGSTOP
);
351 /* once we get here, we must have resumed */
356 if (pid
!= (pid_t
) - 1) {
357 if (WIFSIGNALED(status
)) {
358 fprintf(stderr
, "%s%s\n",
359 strsignal(WTERMSIG(status
)),
360 WCOREDUMP(status
) ? _(" (core dumped)")
362 status
= WTERMSIG(status
) + 128;
364 status
= WEXITSTATUS(status
);
365 } else if (caught_signal
)
366 status
= caught_signal
+ 128;
373 fprintf(stderr
, _("\nSession terminated, killing shell..."));
374 kill(child
, SIGTERM
);
377 supam_cleanup(su
, PAM_SUCCESS
);
381 kill(child
, SIGKILL
);
382 fprintf(stderr
, _(" ...killed.\n"));
384 /* Let's terminate itself with the received signal.
386 * It seems that shells use WIFSIGNALED() rather than our exit status
387 * value to detect situations when is necessary to cleanup (reset)
388 * terminal settings (kzak -- Jun 2013).
390 switch (caught_signal
) {
392 sigaction(SIGTERM
, &oldact
[0], NULL
);
395 sigaction(SIGINT
, &oldact
[1], NULL
);
398 sigaction(SIGQUIT
, &oldact
[2], NULL
);
401 /* just in case that signal stuff initialization failed and
402 * caught_signal = true */
403 caught_signal
= SIGKILL
;
406 kill(getpid(), caught_signal
);
412 static void setenv_path(const struct passwd
*pw
)
417 r
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
419 else if ((r
= logindefs_setenv("PATH", "ENV_ROOTPATH", NULL
)) != 0)
420 r
= logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT
);
424 _("failed to set the %s environment variable"), "PATH");
427 static void modify_environment(struct su_context
*su
, const char *shell
)
429 const struct passwd
*pw
= su
->pwd
;
431 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
432 * Unset all other environment variables.
434 if (su
->simulate_login
) {
435 char *term
= getenv("TERM");
437 term
= xstrdup(term
);
439 environ
= xmalloc((6 + ! !term
) * sizeof(char *));
442 xsetenv("TERM", term
, 1);
446 xsetenv("HOME", pw
->pw_dir
, 1);
448 xsetenv("SHELL", shell
, 1);
449 xsetenv("USER", pw
->pw_name
, 1);
450 xsetenv("LOGNAME", pw
->pw_name
, 1);
453 /* Set HOME, SHELL, and (if not becoming a superuser) USER and LOGNAME.
455 } else if (su
->change_environment
) {
456 xsetenv("HOME", pw
->pw_dir
, 1);
458 xsetenv("SHELL", shell
, 1);
460 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
464 xsetenv("USER", pw
->pw_name
, 1);
465 xsetenv("LOGNAME", pw
->pw_name
, 1);
469 supam_export_environment(su
);
472 /* Become the user and group(s) specified by PW. */
475 init_groups(struct su_context
*su
, gid_t
* groups
, size_t num_groups
)
482 retval
= setgroups(num_groups
, groups
);
484 retval
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
487 supam_cleanup(su
, PAM_ABORT
);
488 err(EXIT_FAILURE
, _("cannot set groups"));
492 retval
= pam_setcred(su
->pamh
, PAM_ESTABLISH_CRED
);
493 if (is_pam_failure(retval
))
494 errx(EXIT_FAILURE
, "%s", pam_strerror(su
->pamh
, retval
));
496 su
->pam_has_cred
= 1;
500 change_identity (const struct passwd
* const pw
)
502 if (setgid(pw
->pw_gid
))
503 err(EXIT_FAILURE
, _("cannot set group id"));
504 if (setuid(pw
->pw_uid
))
505 err(EXIT_FAILURE
, _("cannot set user id"));
508 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
509 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
510 * N_ADDITIONAL_ARGS extra arguments.
512 static void run_shell(
513 struct su_context
*su
,
514 char const *shell
, char const *command
, char **additional_args
,
515 size_t n_additional_args
)
517 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
518 char const **args
= xcalloc(n_args
, sizeof *args
);
522 if (su
->simulate_login
) {
524 char *shell_basename
;
526 shell_basename
= basename(shell
);
527 arg0
= xmalloc(strlen(shell_basename
) + 2);
529 strcpy(arg0
+ 1, shell_basename
);
532 args
[0] = basename(shell
);
534 if (su
->fast_startup
)
535 args
[argno
++] = "-f";
537 args
[argno
++] = "-c";
538 args
[argno
++] = command
;
541 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
542 args
[argno
+ n_additional_args
] = NULL
;
543 execv(shell
, (char **)args
);
545 rc
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
546 err(rc
, _("failed to execute %s"), shell
);
549 /* Return true if SHELL is a restricted shell (one not returned by
550 * getusershell), else false, meaning it is a standard shell.
552 static bool is_restricted_shell(const char *shell
)
557 while ((line
= getusershell()) != NULL
) {
558 if (*line
!= '#' && !strcmp(line
, shell
)) {
567 static void usage_common(void)
569 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout
);
570 fputs(_(" -g, --group <group> specify the primary group\n"), stdout
);
571 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout
);
572 fputs(USAGE_SEPARATOR
, stdout
);
574 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout
);
575 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout
);
576 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
577 " and do not create a new session\n"), stdout
);
578 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout
);
579 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout
);
581 fputs(USAGE_SEPARATOR
, stdout
);
582 printf(USAGE_HELP_OPTIONS(33));
586 static void __attribute__ ((__noreturn__
)) usage_runuser(void)
588 fputs(USAGE_HEADER
, stdout
);
590 _(" %1$s [options] -u <user> [[--] <command>]\n"
591 " %1$s [options] [-] [<user> [<argument>...]]\n"),
592 program_invocation_short_name
);
594 fputs(USAGE_SEPARATOR
, stdout
);
595 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
596 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
597 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout
);
599 fputs(USAGE_OPTIONS
, stdout
);
600 fputs(_(" -u, --user <user> username\n"), stdout
);
602 fputs(USAGE_SEPARATOR
, stdout
);
604 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
608 static void __attribute__ ((__noreturn__
)) usage_su(void)
610 fputs(USAGE_HEADER
, stdout
);
612 _(" %s [options] [-] [<user> [<argument>...]]\n"),
613 program_invocation_short_name
);
615 fputs(USAGE_SEPARATOR
, stdout
);
616 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
617 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout
);
619 fputs(USAGE_OPTIONS
, stdout
);
622 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
626 static void usage(int mode
)
634 static void load_config(void *data
)
636 struct su_context
*su
= (struct su_context
*) data
;
638 logindefs_load_file(su
->runuser
? _PATH_LOGINDEFS_RUNUSER
: _PATH_LOGINDEFS_SU
);
639 logindefs_load_file(_PATH_LOGINDEFS
);
643 * Returns 1 if the current user is not root
648 const uid_t ruid
= getuid();
649 const uid_t euid
= geteuid();
651 /* if we're really root and aren't running setuid */
652 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
656 add_supp_group(const char *name
, gid_t
** groups
, size_t * ngroups
)
660 if (*ngroups
>= NGROUPS_MAX
)
663 ("specifying more than %d supplemental group is not possible",
664 "specifying more than %d supplemental groups is not possible",
665 NGROUPS_MAX
- 1), NGROUPS_MAX
- 1);
669 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
671 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
672 (*groups
)[*ngroups
] = gr
->gr_gid
;
679 su_main(int argc
, char **argv
, int mode
)
681 struct su_context _su
= {
682 .conv
= { supam_conv
, NULL
},
683 .runuser
= (mode
== RUNUSER_MODE
? 1 : 0),
684 .change_environment
= 1,
685 .new_user
= DEFAULT_USER
689 char *command
= NULL
;
690 int request_same_session
= 0;
693 gid_t
*groups
= NULL
;
695 bool use_supp
= false;
696 bool use_gid
= false;
699 static const struct option longopts
[] = {
700 {"command", required_argument
, NULL
, 'c'},
701 {"session-command", required_argument
, NULL
, 'C'},
702 {"fast", no_argument
, NULL
, 'f'},
703 {"login", no_argument
, NULL
, 'l'},
704 {"preserve-environment", no_argument
, NULL
, 'p'},
705 {"shell", required_argument
, NULL
, 's'},
706 {"group", required_argument
, NULL
, 'g'},
707 {"supp-group", required_argument
, NULL
, 'G'},
708 {"user", required_argument
, NULL
, 'u'}, /* runuser only */
709 {"help", no_argument
, 0, 'h'},
710 {"version", no_argument
, 0, 'V'},
714 setlocale(LC_ALL
, "");
715 bindtextdomain(PACKAGE
, LOCALEDIR
);
717 atexit(close_stdout
);
719 su
->conv
.appdata_ptr
= (void *) su
;
722 getopt_long(argc
, argv
, "c:fg:G:lmps:u:hV", longopts
,
731 request_same_session
= 1;
735 su
->fast_startup
= true;
740 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
745 add_supp_group(optarg
, &groups
, &ngroups
);
749 su
->simulate_login
= true;
754 su
->change_environment
= false;
763 errtryhelp(EXIT_FAILURE
);
764 su
->runuser_uopt
= 1;
765 su
->new_user
= optarg
;
772 printf(UTIL_LINUX_VERSION
);
776 errtryhelp(EXIT_FAILURE
);
780 su
->restricted
= evaluate_uid();
782 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
783 su
->simulate_login
= true;
787 if (su
->simulate_login
&& !su
->change_environment
) {
789 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
790 su
->change_environment
= true;
795 /* runuser -u <user> <command> */
796 if (su
->runuser_uopt
) {
797 if (shell
|| su
->fast_startup
|| command
|| su
->simulate_login
)
799 _("options --{shell,fast,command,session-command,login} and "
800 "--user are mutually exclusive"));
802 errx(EXIT_FAILURE
, _("no command was specified"));
805 /* fallthrough if -u <user> is not specified, then follow
806 * traditional su(1) behavior
810 su
->new_user
= argv
[optind
++];
814 if ((use_supp
|| use_gid
) && su
->restricted
)
816 _("only root can specify alternative groups"));
818 logindefs_set_loader(load_config
, (void *) su
);
821 su
->pwd
= xgetpwnam(su
->new_user
, &su
->pwdbuf
);
823 || !su
->pwd
->pw_passwd
824 || !su
->pwd
->pw_name
|| !*su
->pwd
->pw_name
825 || !su
->pwd
->pw_dir
|| !*su
->pwd
->pw_dir
)
826 errx(EXIT_FAILURE
, _("user %s does not exist"), su
->new_user
);
828 su
->new_user
= su
->pwd
->pw_name
;
829 su
->old_user
= xgetlogin();
831 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
832 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
834 if (use_supp
&& !use_gid
)
835 su
->pwd
->pw_gid
= groups
[0];
837 su
->pwd
->pw_gid
= gid
;
839 supam_authenticate(su
);
841 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
842 su
->same_session
= 1;
844 /* initialize shell variable only if "-u <user>" not specified */
845 if (su
->runuser_uopt
) {
848 if (!shell
&& !su
->change_environment
)
849 shell
= getenv("SHELL");
853 && is_restricted_shell(su
->pwd
->pw_shell
)) {
854 /* The user being su'd to has a nonstandard shell, and
855 * so is probably a uucp account or has restricted
856 * access. Don't compromise the account by allowing
857 * access with a standard shell.
859 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
862 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
865 init_groups(su
, groups
, ngroups
);
867 if (!su
->simulate_login
|| command
)
868 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
870 create_watching_parent(su
);
871 /* Now we're in the child. */
873 change_identity(su
->pwd
);
874 if (!su
->same_session
)
877 /* Set environment after pam_open_session, which may put KRB5CCNAME
878 into the pam_env, etc. */
880 modify_environment(su
, shell
);
882 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
883 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
886 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
888 execvp(argv
[optind
], &argv
[optind
]);
889 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);