1 /* su for Linux. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006 Free Software Foundation, Inc.
3 Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19 /* Run a shell with the real and effective UID and GID and groups
20 of USER, default `root'.
22 The shell run is taken from USER's password entry, /bin/sh if
23 none is specified there. If the account has a password, su
24 prompts for a password unless run by a user with real UID 0.
26 Does not change the current directory.
27 Sets `HOME' and `SHELL' from the password entry for USER, and if
28 USER is not root, sets `USER' and `LOGNAME' to USER.
29 The subshell is not a login shell.
31 If one or more ARGs are given, they are passed as additional
32 arguments to the subshell.
34 Does not handle /bin/sh or other shells specially
35 (setting argv[0] to "-su", passing -c only to certain shells, etc.).
36 I don't see the point in doing that, and it's ugly.
38 Based on an implementation by David MacKenzie <djm@gnu.ai.mit.edu>. */
41 EXIT_CANNOT_INVOKE
= 126,
48 #include <sys/types.h>
51 #include <security/pam_appl.h>
52 #ifdef HAVE_SECURITY_PAM_MISC_H
53 #include <security/pam_misc.h>
54 #elif defined(HAVE_SECURITY_OPENPAM_H)
55 #include <security/openpam.h>
68 #include "pathnames.h"
70 #include "closestream.h"
74 /* name of the pam configuration files. separate configs for su and su - */
75 #define PAM_SRVNAME_SU "su"
76 #define PAM_SRVNAME_SU_L "su-l"
78 #define PAM_SRVNAME_RUNUSER "runuser"
79 #define PAM_SRVNAME_RUNUSER_L "runuser-l"
81 #define _PATH_LOGINDEFS_SU "/etc/default/su"
82 #define _PATH_LOGINDEFS_RUNUSER "/etc/default/runuser"
84 #define is_pam_failure(_rc) ((_rc) != PAM_SUCCESS)
86 #include "logindefs.h"
87 #include "su-common.h"
89 /* The shell to run if none is given in the user's passwd entry. */
90 #define DEFAULT_SHELL "/bin/sh"
92 /* The user to become if none is specified. */
93 #define DEFAULT_USER "root"
95 #ifndef HAVE_ENVIRON_DECL
96 extern char **environ
;
99 static void run_shell(char const *, char const *, char **, size_t)
100 __attribute__ ((__noreturn__
));
102 /* If true, pass the `-f' option to the subshell. */
103 static bool fast_startup
;
105 /* If true, simulate a login instead of just starting a shell. */
106 static bool simulate_login
;
108 /* If true, change some environment vars to indicate the user su'd to. */
109 static bool change_environment
;
111 /* If true, then don't call setsid() with a command. */
112 static int same_session
= 0;
114 /* SU_MODE_{RUNUSER,SU} */
117 /* Don't print PAM info messages (Last login, etc.). */
118 static int suppress_pam_info
;
120 static bool _pam_session_opened
;
121 static bool _pam_cred_established
;
122 static sig_atomic_t volatile caught_signal
= false;
123 static pam_handle_t
*pamh
= NULL
;
125 static int restricted
= 1; /* zero for root user */
127 static const struct passwd
*
128 current_getpwuid(void)
132 /* GNU Hurd implementation has an extension where a process can exist in a
133 * non-conforming environment, and thus be outside the realms of POSIX
134 * process identifiers; on this platform, getuid() fails with a status of
135 * (uid_t)(-1) and sets errno if a program is run from a non-conforming
138 * http://austingroupbugs.net/view.php?id=511
143 return errno
== 0 ? getpwuid(ruid
) : NULL
;
146 /* Log the fact that someone has run su to the user given by PW;
147 if SUCCESSFUL is true, they gave the correct password, etc. */
150 log_syslog(struct passwd
const * const pw
, const bool successful
)
152 const char *new_user
, *old_user
, *tty
;
154 new_user
= pw
->pw_name
;
155 /* The utmp entry (via getlogin) is probably the best way to identify
156 the user, especially if someone su's from a su-shell. */
157 old_user
= getlogin();
159 /* getlogin can fail -- usually due to lack of utmp entry.
160 Resort to getpwuid. */
161 const struct passwd
*pwd
= current_getpwuid();
162 old_user
= pwd
? pwd
->pw_name
: "";
165 if (get_terminal_name(NULL
, &tty
, NULL
) != 0 || !tty
)
168 openlog(program_invocation_short_name
, 0, LOG_AUTH
);
169 syslog(LOG_NOTICE
, "%s(to %s) %s on %s",
171 su_mode
== RUNUSER_MODE
? "FAILED RUNUSER " : "FAILED SU ",
172 new_user
, old_user
, tty
);
177 * Log failed login attempts in _PATH_BTMP if that exists.
179 static void log_btmp(struct passwd
const * const pw
)
183 const char *tty_name
, *tty_num
;
185 memset(&ut
, 0, sizeof(ut
));
188 pw
&& pw
->pw_name
? pw
->pw_name
: "(unknown)",
191 get_terminal_name(NULL
, &tty_name
, &tty_num
);
193 xstrncpy(ut
.ut_id
, tty_num
, sizeof(ut
.ut_id
));
195 xstrncpy(ut
.ut_line
, tty_name
, sizeof(ut
.ut_line
));
197 gettimeofday(&tv
, NULL
);
198 ut
.ut_tv
.tv_sec
= tv
.tv_sec
;
199 ut
.ut_tv
.tv_usec
= tv
.tv_usec
;
200 ut
.ut_type
= LOGIN_PROCESS
; /* XXX doesn't matter */
201 ut
.ut_pid
= getpid();
203 updwtmpx(_PATH_BTMP
, &ut
);
207 su_pam_conv(int num_msg
, const struct pam_message
**msg
,
208 struct pam_response
**resp
, void *appdata_ptr
)
210 if (suppress_pam_info
211 && num_msg
== 1 && msg
&& msg
[0]->msg_style
== PAM_TEXT_INFO
)
213 #ifdef HAVE_SECURITY_PAM_MISC_H
214 return misc_conv(num_msg
, msg
, resp
, appdata_ptr
);
215 #elif defined(HAVE_SECURITY_OPENPAM_H)
216 return openpam_ttyconv(num_msg
, msg
, resp
, appdata_ptr
);
220 static struct pam_conv conv
= {
226 cleanup_pam(const int retcode
)
228 const int saved_errno
= errno
;
230 if (_pam_session_opened
)
231 pam_close_session(pamh
, 0);
233 if (_pam_cred_established
)
234 pam_setcred(pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
236 pam_end(pamh
, retcode
);
241 /* Signal handler for parent process. */
243 su_catch_sig(int sig
)
248 /* Export env variables declared by PAM modules. */
254 /* This is a copy but don't care to free as we exec later anyways. */
255 env
= pam_getenvlist(pamh
);
256 while (env
&& *env
) {
257 if (putenv(*env
) != 0)
258 err(EXIT_FAILURE
, NULL
);
264 create_watching_parent(void)
268 struct sigaction oldact
[3];
272 retval
= pam_open_session(pamh
, 0);
273 if (is_pam_failure(retval
)) {
275 errx(EXIT_FAILURE
, _("cannot open session: %s"),
276 pam_strerror(pamh
, retval
));
278 _pam_session_opened
= 1;
280 memset(oldact
, 0, sizeof(oldact
));
283 if (child
== (pid_t
) - 1) {
284 cleanup_pam(PAM_ABORT
);
285 err(EXIT_FAILURE
, _("cannot create child process"));
288 /* the child proceeds to run the shell */
292 /* In the parent watch the child. */
294 /* su without pam support does not have a helper that keeps
295 sitting on any directory so let's go to /. */
297 warn(_("cannot change directory to %s"), "/");
300 if (sigprocmask(SIG_BLOCK
, &ourset
, NULL
)) {
301 warn(_("cannot block signals"));
302 caught_signal
= true;
304 if (!caught_signal
) {
305 struct sigaction action
;
306 action
.sa_handler
= su_catch_sig
;
307 sigemptyset(&action
.sa_mask
);
309 sigemptyset(&ourset
);
311 if (sigaddset(&ourset
, SIGINT
)
312 || sigaddset(&ourset
, SIGQUIT
)) {
313 warn(_("cannot set signal handler"));
314 caught_signal
= true;
317 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
318 || sigaddset(&ourset
, SIGALRM
)
319 || sigaction(SIGTERM
, &action
,
321 || sigprocmask(SIG_UNBLOCK
, &ourset
,
323 warn(_("cannot set signal handler"));
324 caught_signal
= true;
326 if (!caught_signal
&& !same_session
327 && (sigaction(SIGINT
, &action
, &oldact
[1])
328 || sigaction(SIGQUIT
, &action
, &oldact
[2]))) {
329 warn(_("cannot set signal handler"));
330 caught_signal
= true;
333 if (!caught_signal
) {
336 pid
= waitpid(child
, &status
, WUNTRACED
);
338 if (pid
!= (pid_t
) - 1 && WIFSTOPPED(status
)) {
339 kill(getpid(), SIGSTOP
);
340 /* once we get here, we must have resumed */
345 if (pid
!= (pid_t
) - 1) {
346 if (WIFSIGNALED(status
)) {
347 fprintf(stderr
, "%s%s\n",
348 strsignal(WTERMSIG(status
)),
349 WCOREDUMP(status
) ? _(" (core dumped)")
351 status
= WTERMSIG(status
) + 128;
353 status
= WEXITSTATUS(status
);
354 } else if (caught_signal
)
355 status
= caught_signal
+ 128;
362 fprintf(stderr
, _("\nSession terminated, killing shell..."));
363 kill(child
, SIGTERM
);
366 cleanup_pam(PAM_SUCCESS
);
370 kill(child
, SIGKILL
);
371 fprintf(stderr
, _(" ...killed.\n"));
373 /* Let's terminate itself with the received signal.
375 * It seems that shells use WIFSIGNALED() rather than our exit status
376 * value to detect situations when is necessary to cleanup (reset)
377 * terminal settings (kzak -- Jun 2013).
379 switch (caught_signal
) {
381 sigaction(SIGTERM
, &oldact
[0], NULL
);
384 sigaction(SIGINT
, &oldact
[1], NULL
);
387 sigaction(SIGQUIT
, &oldact
[2], NULL
);
390 /* just in case that signal stuff initialization failed and
391 * caught_signal = true */
392 caught_signal
= SIGKILL
;
395 kill(getpid(), caught_signal
);
401 authenticate(const struct passwd
*pw
)
403 const struct passwd
*lpw
= NULL
;
404 const char *cp
, *srvname
= NULL
;
409 srvname
= simulate_login
? PAM_SRVNAME_SU_L
: PAM_SRVNAME_SU
;
413 simulate_login
? PAM_SRVNAME_RUNUSER_L
:
421 retval
= pam_start(srvname
, pw
->pw_name
, &conv
, &pamh
);
422 if (is_pam_failure(retval
))
425 if (isatty(0) && (cp
= ttyname(0)) != NULL
) {
428 if (strncmp(cp
, "/dev/", 5) == 0)
432 retval
= pam_set_item(pamh
, PAM_TTY
, tty
);
433 if (is_pam_failure(retval
))
437 lpw
= current_getpwuid();
438 if (lpw
&& lpw
->pw_name
) {
440 pam_set_item(pamh
, PAM_RUSER
, (const void *)lpw
->pw_name
);
441 if (is_pam_failure(retval
))
445 if (su_mode
== RUNUSER_MODE
) {
447 * This is the only difference between runuser(1) and su(1). The command
448 * runuser(1) does not required authentication, because user is root.
452 _("may not be used by non-root users"));
456 retval
= pam_authenticate(pamh
, 0);
457 if (is_pam_failure(retval
))
460 retval
= pam_acct_mgmt(pamh
, 0);
461 if (retval
== PAM_NEW_AUTHTOK_REQD
) {
462 /* Password has expired. Offer option to change it. */
463 retval
= pam_chauthtok(pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
468 log_syslog(pw
, !is_pam_failure(retval
));
470 if (is_pam_failure(retval
)) {
475 msg
= pam_strerror(pamh
, retval
);
476 pam_end(pamh
, retval
);
477 sleep(getlogindefs_num("FAIL_DELAY", 1));
478 errx(EXIT_FAILURE
, "%s", msg
? msg
: _("incorrect password"));
483 set_path(const struct passwd
* const pw
)
487 r
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
489 else if ((r
= logindefs_setenv("PATH", "ENV_ROOTPATH", NULL
)) != 0)
490 r
= logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT
);
494 _("failed to set the %s environment variable"), "PATH");
497 /* Update `environ' for the new shell based on PW, with SHELL being
498 the value for the SHELL environment variable. */
501 modify_environment (const struct passwd
* const pw
, const char * const shell
)
503 if (simulate_login
) {
504 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
505 Unset all other environment variables. */
506 char *term
= getenv("TERM");
508 term
= xstrdup(term
);
509 environ
= xmalloc((6 + ! !term
) * sizeof(char *));
512 xsetenv("TERM", term
, 1);
515 xsetenv("HOME", pw
->pw_dir
, 1);
517 xsetenv("SHELL", shell
, 1);
518 xsetenv("USER", pw
->pw_name
, 1);
519 xsetenv("LOGNAME", pw
->pw_name
, 1);
522 /* Set HOME, SHELL, and (if not becoming a superuser)
524 if (change_environment
) {
525 xsetenv("HOME", pw
->pw_dir
, 1);
527 xsetenv("SHELL", shell
, 1);
528 if (getlogindefs_bool("ALWAYS_SET_PATH", 0))
532 xsetenv("USER", pw
->pw_name
, 1);
533 xsetenv("LOGNAME", pw
->pw_name
, 1);
541 /* Become the user and group(s) specified by PW. */
544 init_groups (const struct passwd
* const pw
, const gid_t
* const groups
, const size_t num_groups
)
551 retval
= setgroups(num_groups
, groups
);
553 retval
= initgroups(pw
->pw_name
, pw
->pw_gid
);
556 cleanup_pam(PAM_ABORT
);
557 err(EXIT_FAILURE
, _("cannot set groups"));
561 retval
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
562 if (is_pam_failure(retval
))
563 errx(EXIT_FAILURE
, "%s", pam_strerror(pamh
, retval
));
565 _pam_cred_established
= 1;
569 change_identity (const struct passwd
* const pw
)
571 if (setgid(pw
->pw_gid
))
572 err(EXIT_FAILURE
, _("cannot set group id"));
573 if (setuid(pw
->pw_uid
))
574 err(EXIT_FAILURE
, _("cannot set user id"));
577 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
578 If COMMAND is nonzero, pass it to the shell with the -c option.
579 Pass ADDITIONAL_ARGS to the shell as more arguments; there
580 are N_ADDITIONAL_ARGS extra arguments. */
583 run_shell (char const * const shell
, char const * const command
, char ** const additional_args
,
584 const size_t n_additional_args
)
587 1 + fast_startup
+ 2 * ! !command
+ n_additional_args
+ 1;
588 char const **args
= xcalloc(n_args
, sizeof *args
);
591 if (simulate_login
) {
593 char *shell_basename
;
595 shell_basename
= basename(shell
);
596 arg0
= xmalloc(strlen(shell_basename
) + 2);
598 strcpy(arg0
+ 1, shell_basename
);
601 args
[0] = basename(shell
);
603 args
[argno
++] = "-f";
605 args
[argno
++] = "-c";
606 args
[argno
++] = command
;
608 memcpy(args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
609 args
[argno
+ n_additional_args
] = NULL
;
610 execv(shell
, (char **)args
);
614 (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
615 warn(_("failed to execute %s"), shell
);
620 /* Return true if SHELL is a restricted shell (one not returned by
621 getusershell), else false, meaning it is a standard shell. */
624 restricted_shell (const char * const shell
)
629 while ((line
= getusershell()) != NULL
) {
630 if (*line
!= '#' && !strcmp(line
, shell
)) {
640 static void __attribute__ ((__noreturn__
)) usage(int status
)
642 if (su_mode
== RUNUSER_MODE
) {
643 fputs(USAGE_HEADER
, stdout
);
644 printf(_(" %s [options] -u <user> <command>\n"),
645 program_invocation_short_name
);
646 printf(_(" %s [options] [-] [<user> [<argument>...]]\n"),
647 program_invocation_short_name
);
650 "Run <command> with the effective user ID and group ID of <user>. If -u is\n"
651 "not given, fall back to su(1)-compatible semantics and execute standard shell.\n"
652 "The options -c, -f, -l, and -s are mutually exclusive with -u.\n"),
655 fputs(USAGE_OPTIONS
, stdout
);
657 fputs(_(" -u, --user <user> username\n"), stdout
);
660 fputs(USAGE_HEADER
, stdout
);
661 printf(_(" %s [options] [-] [<user> [<argument>...]]\n"),
662 program_invocation_short_name
);
665 "Change the effective user ID and group ID to that of <user>.\n"
666 "A mere - implies -l. If <user> is not given, root is assumed.\n"),
669 fputs(USAGE_OPTIONS
, stdout
);
673 (" -m, -p, --preserve-environment do not reset environment variables\n"),
675 fputs(_(" -g, --group <group> specify the primary group\n"),
678 (" -G, --supp-group <group> specify a supplemental group\n\n"),
682 (" -, -l, --login make the shell a login shell\n"),
685 (" -c, --command <command> pass a single command to the shell with -c\n"),
688 (" --session-command <command> pass a single command to the shell with -c\n"
689 " and do not create a new session\n"),
692 (" -f, --fast pass -f to the shell (for csh or tcsh)\n"),
695 (" -s, --shell <shell> run <shell> if /etc/shells allows it\n"),
698 fputs(USAGE_SEPARATOR
, stdout
);
699 printf(USAGE_HELP_OPTIONS(22));
700 printf(USAGE_MAN_TAIL(su_mode
== SU_MODE
? "su(1)" : "runuser(1)"));
709 logindefs_load_file(_PATH_LOGINDEFS_SU
);
712 logindefs_load_file(_PATH_LOGINDEFS_RUNUSER
);
716 logindefs_load_file(_PATH_LOGINDEFS
);
720 * Returns 1 if the current user is not root
725 const uid_t ruid
= getuid();
726 const uid_t euid
= geteuid();
728 /* if we're really root and aren't running setuid */
729 return (uid_t
) 0 == ruid
&& ruid
== euid
? 0 : 1;
733 add_supp_group(const char *name
, gid_t
** groups
, size_t * ngroups
)
737 if (*ngroups
>= NGROUPS_MAX
)
740 ("specifying more than %d supplemental group is not possible",
741 "specifying more than %d supplemental groups is not possible",
742 NGROUPS_MAX
- 1), NGROUPS_MAX
- 1);
746 errx(EXIT_FAILURE
, _("group %s does not exist"), name
);
748 *groups
= xrealloc(*groups
, sizeof(gid_t
) * (*ngroups
+ 1));
749 (*groups
)[*ngroups
] = gr
->gr_gid
;
756 su_main(int argc
, char **argv
, int mode
)
759 const char *new_user
= DEFAULT_USER
, *runuser_user
= NULL
;
760 char *command
= NULL
;
761 int request_same_session
= 0;
764 struct passwd pw_copy
;
766 gid_t
*groups
= NULL
;
768 bool use_supp
= false;
769 bool use_gid
= false;
772 static const struct option longopts
[] = {
773 {"command", required_argument
, NULL
, 'c'},
774 {"session-command", required_argument
, NULL
, 'C'},
775 {"fast", no_argument
, NULL
, 'f'},
776 {"login", no_argument
, NULL
, 'l'},
777 {"preserve-environment", no_argument
, NULL
, 'p'},
778 {"shell", required_argument
, NULL
, 's'},
779 {"group", required_argument
, NULL
, 'g'},
780 {"supp-group", required_argument
, NULL
, 'G'},
781 {"user", required_argument
, NULL
, 'u'}, /* runuser only */
782 {"help", no_argument
, 0, 'h'},
783 {"version", no_argument
, 0, 'V'},
787 setlocale(LC_ALL
, "");
788 bindtextdomain(PACKAGE
, LOCALEDIR
);
790 atexit(close_stdout
);
793 fast_startup
= false;
794 simulate_login
= false;
795 change_environment
= true;
798 getopt_long(argc
, argv
, "c:fg:G:lmps:u:hV", longopts
,
807 request_same_session
= 1;
816 gid
= add_supp_group(optarg
, &groups
, &ngroups
);
821 add_supp_group(optarg
, &groups
, &ngroups
);
825 simulate_login
= true;
830 change_environment
= false;
838 if (su_mode
!= RUNUSER_MODE
)
840 runuser_user
= optarg
;
847 printf(UTIL_LINUX_VERSION
);
855 restricted
= evaluate_uid();
857 if (optind
< argc
&& !strcmp(argv
[optind
], "-")) {
858 simulate_login
= true;
862 if (simulate_login
&& !change_environment
) {
864 ("ignoring --preserve-environment, it's mutually exclusive with --login"));
865 change_environment
= true;
871 /* runuser -u <user> <command> */
872 new_user
= runuser_user
;
873 if (shell
|| fast_startup
|| command
|| simulate_login
) {
876 ("options --{shell,fast,command,session-command,login} and "
877 "--user are mutually exclusive"));
881 _("no command was specified"));
885 /* fallthrough if -u <user> is not specified, then follow
886 * traditional su(1) behavior
890 new_user
= argv
[optind
++];
894 if ((use_supp
|| use_gid
) && restricted
)
896 _("only root can specify alternative groups"));
898 logindefs_load_defaults
= load_config
;
900 pw
= getpwnam(new_user
);
901 if (!(pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
903 errx(EXIT_FAILURE
, _("user %s does not exist"), new_user
);
905 /* Make a copy of the password information and point pw at the local
906 copy instead. Otherwise, some systems (e.g. Linux) would clobber
907 the static data through the getlogin call from log_su.
908 Also, make sure pw->pw_shell is a nonempty string.
909 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
910 but that doesn't have a default shell listed. */
913 pw
->pw_name
= xstrdup(pw
->pw_name
);
914 pw
->pw_passwd
= xstrdup(pw
->pw_passwd
);
915 pw
->pw_dir
= xstrdup(pw
->pw_dir
);
916 pw
->pw_shell
= xstrdup(pw
->pw_shell
&& pw
->pw_shell
[0]
917 ? pw
->pw_shell
: DEFAULT_SHELL
);
920 if (use_supp
&& !use_gid
)
921 pw
->pw_gid
= groups
[0];
927 if (request_same_session
|| !command
|| !pw
->pw_uid
)
930 /* initialize shell variable only if "-u <user>" not specified */
934 if (!shell
&& !change_environment
)
935 shell
= getenv("SHELL");
936 if (shell
&& getuid() != 0 && restricted_shell(pw
->pw_shell
)) {
937 /* The user being su'd to has a nonstandard shell, and so is
938 probably a uucp account or has restricted access. Don't
939 compromise the account by allowing access with a standard
941 warnx(_("using restricted shell %s"), pw
->pw_shell
);
944 shell
= xstrdup(shell
? shell
: pw
->pw_shell
);
947 init_groups(pw
, groups
, ngroups
);
949 if (!simulate_login
|| command
)
950 suppress_pam_info
= 1; /* don't print PAM info messages */
952 create_watching_parent();
953 /* Now we're in the child. */
959 /* Set environment after pam_open_session, which may put KRB5CCNAME
960 into the pam_env, etc. */
962 modify_environment(pw
, shell
);
964 if (simulate_login
&& chdir(pw
->pw_dir
) != 0)
965 warn(_("warning: cannot change directory to %s"), pw
->pw_dir
);
968 run_shell(shell
, command
, argv
+ optind
, max(0, argc
- optind
));
970 execvp(argv
[optind
], &argv
[optind
]);
971 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);