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
);
413 set_path(const struct passwd
* const 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 /* Update `environ' for the new shell based on PW, with SHELL being
428 the value for the SHELL environment variable. */
431 modify_environment(struct su_context
*su
, const char *shell
)
433 const struct passwd
*pw
= su
->pwd
;
435 if (su
->simulate_login
) {
436 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
437 Unset all other environment variables. */
438 char *term
= getenv("TERM");
440 term
= xstrdup(term
);
441 environ
= xmalloc((6 + ! !term
) * sizeof(char *));
444 xsetenv("TERM", term
, 1);
447 xsetenv("HOME", pw
->pw_dir
, 1);
449 xsetenv("SHELL", shell
, 1);
450 xsetenv("USER", pw
->pw_name
, 1);
451 xsetenv("LOGNAME", pw
->pw_name
, 1);
454 /* Set HOME, SHELL, and (if not becoming a superuser)
456 if (su
->change_environment
) {
457 xsetenv("HOME", pw
->pw_dir
, 1);
459 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);
470 supam_export_environment(su
);
473 /* Become the user and group(s) specified by PW. */
476 init_groups(struct su_context
*su
, gid_t
* groups
, size_t num_groups
)
483 retval
= setgroups(num_groups
, groups
);
485 retval
= initgroups(su
->pwd
->pw_name
, su
->pwd
->pw_gid
);
488 supam_cleanup(su
, PAM_ABORT
);
489 err(EXIT_FAILURE
, _("cannot set groups"));
493 retval
= pam_setcred(su
->pamh
, PAM_ESTABLISH_CRED
);
494 if (is_pam_failure(retval
))
495 errx(EXIT_FAILURE
, "%s", pam_strerror(su
->pamh
, retval
));
497 su
->pam_has_cred
= 1;
501 change_identity (const struct passwd
* const pw
)
503 if (setgid(pw
->pw_gid
))
504 err(EXIT_FAILURE
, _("cannot set group id"));
505 if (setuid(pw
->pw_uid
))
506 err(EXIT_FAILURE
, _("cannot set user id"));
509 /* Run SHELL, if COMMAND is nonzero, pass it to the shell with the -c option.
510 * Pass ADDITIONAL_ARGS to the shell as more arguments; there are
511 * N_ADDITIONAL_ARGS extra arguments.
513 static void run_shell(
514 struct su_context
*su
,
515 char const *shell
, char const *command
, char **additional_args
,
516 size_t n_additional_args
)
518 size_t n_args
= 1 + su
->fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
519 char const **args
= xcalloc(n_args
, sizeof *args
);
523 if (su
->simulate_login
) {
525 char *shell_basename
;
527 shell_basename
= basename(shell
);
528 arg0
= xmalloc(strlen(shell_basename
) + 2);
530 strcpy(arg0
+ 1, shell_basename
);
533 args
[0] = basename(shell
);
535 if (su
->fast_startup
)
536 args
[argno
++] = "-f";
538 args
[argno
++] = "-c";
539 args
[argno
++] = command
;
542 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
543 args
[argno
+ n_additional_args
] = NULL
;
544 execv(shell
, (char **)args
);
546 rc
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
547 err(rc
, _("failed to execute %s"), shell
);
550 /* Return true if SHELL is a restricted shell (one not returned by
551 * getusershell), else false, meaning it is a standard shell.
553 static bool is_restricted_shell(const char *shell
)
558 while ((line
= getusershell()) != NULL
) {
559 if (*line
!= '#' && !strcmp(line
, shell
)) {
568 static void usage_common(void)
570 fputs(_(" -m, -p, --preserve-environment do not reset environment variables\n"), stdout
);
571 fputs(_(" -g, --group <group> specify the primary group\n"), stdout
);
572 fputs(_(" -G, --supp-group <group> specify a supplemental group\n"), stdout
);
573 fputs(USAGE_SEPARATOR
, stdout
);
575 fputs(_(" -, -l, --login make the shell a login shell\n"), stdout
);
576 fputs(_(" -c, --command <command> pass a single command to the shell with -c\n"), stdout
);
577 fputs(_(" --session-command <command> pass a single command to the shell with -c\n"
578 " and do not create a new session\n"), stdout
);
579 fputs(_(" -f, --fast pass -f to the shell (for csh or tcsh)\n"), stdout
);
580 fputs(_(" -s, --shell <shell> run <shell> if /etc/shells allows it\n"), stdout
);
582 fputs(USAGE_SEPARATOR
, stdout
);
583 printf(USAGE_HELP_OPTIONS(33));
587 static void __attribute__ ((__noreturn__
)) usage_runuser(void)
589 fputs(USAGE_HEADER
, stdout
);
591 _(" %1$s [options] -u <user> [[--] <command>]\n"
592 " %1$s [options] [-] [<user> [<argument>...]]\n"),
593 program_invocation_short_name
);
595 fputs(USAGE_SEPARATOR
, stdout
);
596 fputs(_("Run <command> with the effective user ID and group ID of <user>. If -u is\n"
597 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
598 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"), stdout
);
600 fputs(USAGE_OPTIONS
, stdout
);
601 fputs(_(" -u, --user <user> username\n"), stdout
);
603 fputs(USAGE_SEPARATOR
, stdout
);
605 fprintf(stdout
, USAGE_MAN_TAIL("runuser(1)"));
609 static void __attribute__ ((__noreturn__
)) usage_su(void)
611 fputs(USAGE_HEADER
, stdout
);
613 _(" %s [options] [-] [<user> [<argument>...]]\n"),
614 program_invocation_short_name
);
616 fputs(USAGE_SEPARATOR
, stdout
);
617 fputs(_("Change the effective user ID and group ID to that of <user>.\n"
618 "A mere - implies -l. If <user> is not given, root is assumed.\n"), stdout
);
620 fputs(USAGE_OPTIONS
, stdout
);
623 fprintf(stdout
, USAGE_MAN_TAIL("su(1)"));
627 static void usage(int mode
)
635 static void load_config(void *data
)
637 struct su_context
*su
= (struct su_context
*) data
;
639 logindefs_load_file(su
->runuser
? _PATH_LOGINDEFS_RUNUSER
: _PATH_LOGINDEFS_SU
);
640 logindefs_load_file(_PATH_LOGINDEFS
);
644 * Returns 1 if the current user is not root
649 const uid_t ruid
= getuid();
650 const uid_t euid
= geteuid();
652 /* if we're really root and aren't running setuid */
653 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
657 add_supp_group(const char *name
, gid_t
** groups
, size_t * ngroups
)
661 if (*ngroups
>= NGROUPS_MAX
)
664 ("specifying more than %d supplemental group is not possible",
665 "specifying more than %d supplemental groups is not possible",
666 NGROUPS_MAX
- 1), NGROUPS_MAX
- 1);
670 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
672 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
673 (*groups
)[*ngroups
] = gr
->gr_gid
;
680 su_main(int argc
, char **argv
, int mode
)
682 struct su_context _su
= {
683 .conv
= { supam_conv
, NULL
},
684 .runuser
= (mode
== RUNUSER_MODE
? 1 : 0),
685 .change_environment
= 1,
686 .new_user
= DEFAULT_USER
690 char *command
= NULL
;
691 int request_same_session
= 0;
694 gid_t
*groups
= NULL
;
696 bool use_supp
= false;
697 bool use_gid
= false;
700 static const struct option longopts
[] = {
701 {"command", required_argument
, NULL
, 'c'},
702 {"session-command", required_argument
, NULL
, 'C'},
703 {"fast", no_argument
, NULL
, 'f'},
704 {"login", no_argument
, NULL
, 'l'},
705 {"preserve-environment", no_argument
, NULL
, 'p'},
706 {"shell", required_argument
, NULL
, 's'},
707 {"group", required_argument
, NULL
, 'g'},
708 {"supp-group", required_argument
, NULL
, 'G'},
709 {"user", required_argument
, NULL
, 'u'}, /* runuser only */
710 {"help", no_argument
, 0, 'h'},
711 {"version", no_argument
, 0, 'V'},
715 setlocale(LC_ALL
, "");
716 bindtextdomain(PACKAGE
, LOCALEDIR
);
718 atexit(close_stdout
);
720 su
->conv
.appdata_ptr
= (void *) su
;
723 getopt_long(argc
, argv
, "c:fg:G:lmps:u:hV", longopts
,
732 request_same_session
= 1;
736 su
->fast_startup
= true;
741 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
746 add_supp_group(optarg
, &groups
, &ngroups
);
750 su
->simulate_login
= true;
755 su
->change_environment
= false;
764 errtryhelp(EXIT_FAILURE
);
765 su
->runuser_uopt
= 1;
766 su
->new_user
= optarg
;
773 printf(UTIL_LINUX_VERSION
);
777 errtryhelp(EXIT_FAILURE
);
781 su
->restricted
= evaluate_uid();
783 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
784 su
->simulate_login
= true;
788 if (su
->simulate_login
&& !su
->change_environment
) {
790 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
791 su
->change_environment
= true;
796 /* runuser -u <user> <command> */
797 if (su
->runuser_uopt
) {
798 if (shell
|| su
->fast_startup
|| command
|| su
->simulate_login
)
800 _("options --{shell,fast,command,session-command,login} and "
801 "--user are mutually exclusive"));
803 errx(EXIT_FAILURE
, _("no command was specified"));
806 /* fallthrough if -u <user> is not specified, then follow
807 * traditional su(1) behavior
811 su
->new_user
= argv
[optind
++];
815 if ((use_supp
|| use_gid
) && su
->restricted
)
817 _("only root can specify alternative groups"));
819 logindefs_set_loader(load_config
, (void *) su
);
822 su
->pwd
= xgetpwnam(su
->new_user
, &su
->pwdbuf
);
824 || !su
->pwd
->pw_passwd
825 || !su
->pwd
->pw_name
|| !*su
->pwd
->pw_name
826 || !su
->pwd
->pw_dir
|| !*su
->pwd
->pw_dir
)
827 errx(EXIT_FAILURE
, _("user %s does not exist"), su
->new_user
);
829 su
->new_user
= su
->pwd
->pw_name
;
830 su
->old_user
= xgetlogin();
832 if (!su
->pwd
->pw_shell
|| !*su
->pwd
->pw_shell
)
833 su
->pwd
->pw_shell
= DEFAULT_SHELL
;
835 if (use_supp
&& !use_gid
)
836 su
->pwd
->pw_gid
= groups
[0];
838 su
->pwd
->pw_gid
= gid
;
840 supam_authenticate(su
);
842 if (request_same_session
|| !command
|| !su
->pwd
->pw_uid
)
843 su
->same_session
= 1;
845 /* initialize shell variable only if "-u <user>" not specified */
846 if (su
->runuser_uopt
) {
849 if (!shell
&& !su
->change_environment
)
850 shell
= getenv("SHELL");
854 && is_restricted_shell(su
->pwd
->pw_shell
)) {
855 /* The user being su'd to has a nonstandard shell, and
856 * so is probably a uucp account or has restricted
857 * access. Don't compromise the account by allowing
858 * access with a standard shell.
860 warnx(_("using restricted shell %s"), su
->pwd
->pw_shell
);
863 shell
= xstrdup(shell
? shell
: su
->pwd
->pw_shell
);
866 init_groups(su
, groups
, ngroups
);
868 if (!su
->simulate_login
|| command
)
869 su
->suppress_pam_info
= 1; /* don't print PAM info messages */
871 create_watching_parent(su
);
872 /* Now we're in the child. */
874 change_identity(su
->pwd
);
875 if (!su
->same_session
)
878 /* Set environment after pam_open_session, which may put KRB5CCNAME
879 into the pam_env, etc. */
881 modify_environment(su
, shell
);
883 if (su
->simulate_login
&& chdir(su
->pwd
->pw_dir
) != 0)
884 warn(_("warning: cannot change directory to %s"), su
->pwd
->pw_dir
);
887 run_shell(su
, shell
, command
, argv
+ optind
, max(0, argc
- optind
));
889 execvp(argv
[optind
], &argv
[optind
]);
890 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);