]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su.c
set sane default path
[thirdparty/util-linux.git] / login-utils / su.c
1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006 Free Software Foundation, Inc.
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
17
18 /* Run a shell with the real and effective UID and GID and groups
19 of USER, default `root'.
20
21 The shell run is taken from USER's password entry, /bin/sh if
22 none is specified there. If the account has a password, su
23 prompts for a password unless run by a user with real UID 0.
24
25 Does not change the current directory.
26 Sets `HOME' and `SHELL' from the password entry for USER, and if
27 USER is not root, sets `USER' and `LOGNAME' to USER.
28 The subshell is not a login shell.
29
30 If one or more ARGs are given, they are passed as additional
31 arguments to the subshell.
32
33 Does not handle /bin/sh or other shells specially
34 (setting argv[0] to "-su", passing -c only to certain shells, etc.).
35 I don't see the point in doing that, and it's ugly.
36
37 This program intentionally does not support a "wheel group" that
38 restricts who can su to UID 0 accounts. RMS considers that to
39 be fascist.
40
41 #ifdef USE_PAM
42
43 Actually, with PAM, su has nothing to do with whether or not a
44 wheel group is enforced by su. RMS tries to restrict your access
45 to a su which implements the wheel group, but PAM considers that
46 to be fascist, and gives the user/sysadmin the opportunity to
47 enforce a wheel group by proper editing of /etc/pam.d/su
48
49 #endif
50
51 Compile-time options:
52 -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog.
53 -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog.
54
55 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
56 Never logs attempted su's to nonexistent accounts.
57
58 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
59
60 #include <config.h>
61 #include <stdio.h>
62 #include <getopt.h>
63 #include <sys/types.h>
64 #include <pwd.h>
65 #include <grp.h>
66 #ifdef USE_PAM
67 # include <security/pam_appl.h>
68 # include <security/pam_misc.h>
69 # include <signal.h>
70 # include <sys/wait.h>
71 # include <sys/fsuid.h>
72 #endif
73
74 /* Hide any system prototype for getusershell.
75 This is necessary because some Cray systems have a conflicting
76 prototype (returning `int') in <unistd.h>. */
77 #define getusershell _getusershell_sys_proto_
78
79 #include "system.h"
80 #include "getpass.h"
81
82 #undef getusershell
83
84 #if HAVE_SYSLOG_H && HAVE_SYSLOG
85 # include <syslog.h>
86 # define SYSLOG_SUCCESS 1
87 # define SYSLOG_FAILURE 1
88 # define SYSLOG_NON_ROOT 1
89 #else
90 # undef SYSLOG_SUCCESS
91 # undef SYSLOG_FAILURE
92 # undef SYSLOG_NON_ROOT
93 #endif
94
95 #if HAVE_SYS_PARAM_H
96 # include <sys/param.h>
97 #endif
98
99 #ifndef HAVE_ENDGRENT
100 # define endgrent() ((void) 0)
101 #endif
102
103 #ifndef HAVE_ENDPWENT
104 # define endpwent() ((void) 0)
105 #endif
106
107 #if HAVE_SHADOW_H
108 # include <shadow.h>
109 #endif
110
111 #include "error.h"
112
113 /* The official name of this program (e.g., no `g' prefix). */
114 #define PROGRAM_NAME "su"
115
116 #define AUTHORS "David MacKenzie"
117
118 #if HAVE_PATHS_H
119 # include <paths.h>
120 #endif
121
122 /* The default PATH for simulated logins to non-superuser accounts. */
123 #define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin"
124
125 /* The default PATH for simulated logins to superuser accounts. */
126 #define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin"
127
128 /* The shell to run if none is given in the user's passwd entry. */
129 #define DEFAULT_SHELL "/bin/sh"
130
131 /* The user to become if none is specified. */
132 #define DEFAULT_USER "root"
133
134 #ifndef USE_PAM
135 char *crypt ();
136 #endif
137 char *getusershell ();
138 void endusershell ();
139 void setusershell ();
140
141 extern char **environ;
142
143 static void run_shell (char const *, char const *, char **, size_t)
144 ATTRIBUTE_NORETURN;
145
146 /* The name this program was run with. */
147 char *program_name;
148
149 /* If true, pass the `-f' option to the subshell. */
150 static bool fast_startup;
151
152 /* If true, simulate a login instead of just starting a shell. */
153 static bool simulate_login;
154
155 /* If true, change some environment vars to indicate the user su'd to. */
156 static bool change_environment;
157
158 #ifdef USE_PAM
159 static bool _pam_session_opened;
160 static bool _pam_cred_established;
161 #endif
162
163 static struct option const longopts[] =
164 {
165 {"command", required_argument, NULL, 'c'},
166 {"fast", no_argument, NULL, 'f'},
167 {"login", no_argument, NULL, 'l'},
168 {"preserve-environment", no_argument, NULL, 'p'},
169 {"shell", required_argument, NULL, 's'},
170 {GETOPT_HELP_OPTION_DECL},
171 {GETOPT_VERSION_OPTION_DECL},
172 {NULL, 0, NULL, 0}
173 };
174
175 /* Add NAME=VAL to the environment, checking for out of memory errors. */
176
177 static void
178 xsetenv (char const *name, char const *val)
179 {
180 size_t namelen = strlen (name);
181 size_t vallen = strlen (val);
182 char *string = xmalloc (namelen + 1 + vallen + 1);
183 strcpy (string, name);
184 string[namelen] = '=';
185 strcpy (string + namelen + 1, val);
186 if (putenv (string) != 0)
187 xalloc_die ();
188 }
189
190 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
191 /* Log the fact that someone has run su to the user given by PW;
192 if SUCCESSFUL is true, they gave the correct password, etc. */
193
194 static void
195 log_su (struct passwd const *pw, bool successful)
196 {
197 const char *new_user, *old_user, *tty;
198
199 # ifndef SYSLOG_NON_ROOT
200 if (pw->pw_uid)
201 return;
202 # endif
203 new_user = pw->pw_name;
204 /* The utmp entry (via getlogin) is probably the best way to identify
205 the user, especially if someone su's from a su-shell. */
206 old_user = getlogin ();
207 if (!old_user)
208 {
209 /* getlogin can fail -- usually due to lack of utmp entry.
210 Resort to getpwuid. */
211 struct passwd *pwd = getpwuid (getuid ());
212 old_user = (pwd ? pwd->pw_name : "");
213 }
214 tty = ttyname (STDERR_FILENO);
215 if (!tty)
216 tty = "none";
217 /* 4.2BSD openlog doesn't have the third parameter. */
218 openlog (last_component (program_name), 0
219 # ifdef LOG_AUTH
220 , LOG_AUTH
221 # endif
222 );
223 syslog (LOG_NOTICE,
224 # ifdef SYSLOG_NON_ROOT
225 "%s(to %s) %s on %s",
226 # else
227 "%s%s on %s",
228 # endif
229 successful ? "" : "FAILED SU ",
230 # ifdef SYSLOG_NON_ROOT
231 new_user,
232 # endif
233 old_user, tty);
234 closelog ();
235 }
236 #endif
237
238 #ifdef USE_PAM
239 # define PAM_SERVICE_NAME PROGRAM_NAME
240 # define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
241 static sig_atomic_t volatile caught_signal = false;
242 static pam_handle_t *pamh = NULL;
243 static int retval;
244 static struct pam_conv conv =
245 {
246 misc_conv,
247 NULL
248 };
249
250 # define PAM_BAIL_P(a) \
251 if (retval) \
252 { \
253 pam_end (pamh, retval); \
254 a; \
255 }
256
257 static void
258 cleanup_pam (int retcode)
259 {
260 if (_pam_session_opened)
261 pam_close_session (pamh, 0);
262
263 if (_pam_cred_established)
264 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
265
266 pam_end(pamh, retcode);
267 }
268
269 /* Signal handler for parent process. */
270 static void
271 su_catch_sig (int sig)
272 {
273 caught_signal = true;
274 }
275
276 /* Export env variables declared by PAM modules. */
277 static void
278 export_pamenv (void)
279 {
280 char **env;
281
282 /* This is a copy but don't care to free as we exec later anyways. */
283 env = pam_getenvlist (pamh);
284 while (env && *env)
285 {
286 if (putenv (*env) != 0)
287 xalloc_die ();
288 env++;
289 }
290 }
291
292 static void
293 create_watching_parent (void)
294 {
295 pid_t child;
296 sigset_t ourset;
297 int status = 0;
298
299 retval = pam_open_session (pamh, 0);
300 if (retval != PAM_SUCCESS)
301 {
302 cleanup_pam (retval);
303 error (EXIT_FAILURE, 0, _("cannot not open session: %s"),
304 pam_strerror (pamh, retval));
305 }
306 else
307 _pam_session_opened = 1;
308
309 child = fork ();
310 if (child == (pid_t) -1)
311 {
312 cleanup_pam (PAM_ABORT);
313 error (EXIT_FAILURE, errno, _("cannot create child process"));
314 }
315
316 /* the child proceeds to run the shell */
317 if (child == 0)
318 return;
319
320 /* In the parent watch the child. */
321
322 /* su without pam support does not have a helper that keeps
323 sitting on any directory so let's go to /. */
324 if (chdir ("/") != 0)
325 error (0, errno, _("warning: cannot change directory to %s"), "/");
326
327 sigfillset (&ourset);
328 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
329 {
330 error (0, errno, _("cannot block signals"));
331 caught_signal = true;
332 }
333 if (!caught_signal)
334 {
335 struct sigaction action;
336 action.sa_handler = su_catch_sig;
337 sigemptyset (&action.sa_mask);
338 action.sa_flags = 0;
339 sigemptyset (&ourset);
340 if (sigaddset (&ourset, SIGTERM)
341 || sigaddset (&ourset, SIGALRM)
342 || sigaction (SIGTERM, &action, NULL)
343 || sigprocmask (SIG_UNBLOCK, &ourset, NULL))
344 {
345 error (0, errno, _("cannot set signal handler"));
346 caught_signal = true;
347 }
348 }
349 if (!caught_signal)
350 {
351 pid_t pid;
352 for (;;)
353 {
354 pid = waitpid (child, &status, WUNTRACED);
355
356 if (pid != (pid_t)-1 && WIFSTOPPED (status))
357 {
358 kill (getpid (), SIGSTOP);
359 /* once we get here, we must have resumed */
360 kill (pid, SIGCONT);
361 }
362 else
363 break;
364 }
365 if (pid != (pid_t)-1)
366 if (WIFSIGNALED (status))
367 status = WTERMSIG (status) + 128;
368 else
369 status = WEXITSTATUS (status);
370 else
371 status = 1;
372 }
373 else
374 status = 1;
375
376 if (caught_signal)
377 {
378 fprintf (stderr, _("\nSession terminated, killing shell..."));
379 kill (child, SIGTERM);
380 }
381
382 cleanup_pam (PAM_SUCCESS);
383
384 if (caught_signal)
385 {
386 sleep (2);
387 kill (child, SIGKILL);
388 fprintf (stderr, _(" ...killed.\n"));
389 }
390 exit (status);
391 }
392 #endif
393
394 /* Ask the user for a password.
395 If PAM is in use, let PAM ask for the password if necessary.
396 Return true if the user gives the correct password for entry PW,
397 false if not. Return true without asking for a password if run by UID 0
398 or if PW has an empty password. */
399
400 static bool
401 correct_password (const struct passwd *pw)
402 {
403 #ifdef USE_PAM
404 const struct passwd *lpw;
405 const char *cp;
406
407 retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
408 pw->pw_name, &conv, &pamh);
409 PAM_BAIL_P (return false);
410
411 if (isatty (0) && (cp = ttyname (0)) != NULL)
412 {
413 const char *tty;
414
415 if (strncmp (cp, "/dev/", 5) == 0)
416 tty = cp + 5;
417 else
418 tty = cp;
419 retval = pam_set_item (pamh, PAM_TTY, tty);
420 PAM_BAIL_P (return false);
421 }
422 # if 0 /* Manpage discourages use of getlogin. */
423 cp = getlogin ();
424 if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ()))
425 # endif
426 lpw = getpwuid (getuid ());
427 if (lpw && lpw->pw_name)
428 {
429 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
430 PAM_BAIL_P (return false);
431 }
432 retval = pam_authenticate (pamh, 0);
433 PAM_BAIL_P (return false);
434 retval = pam_acct_mgmt (pamh, 0);
435 if (retval == PAM_NEW_AUTHTOK_REQD)
436 {
437 /* Password has expired. Offer option to change it. */
438 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
439 PAM_BAIL_P (return false);
440 }
441 PAM_BAIL_P (return false);
442 /* Must be authenticated if this point was reached. */
443 return true;
444 #else /* !USE_PAM */
445 char *unencrypted, *encrypted, *correct;
446 # if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
447 /* Shadow passwd stuff for SVR3 and maybe other systems. */
448 const struct spwd *sp = getspnam (pw->pw_name);
449
450 endspent ();
451 if (sp)
452 correct = sp->sp_pwdp;
453 else
454 # endif
455 correct = pw->pw_passwd;
456
457 if (getuid () == 0 || !correct || correct[0] == '\0')
458 return true;
459
460 unencrypted = getpass (_("Password:"));
461 if (!unencrypted)
462 {
463 error (0, 0, _("getpass: cannot open /dev/tty"));
464 return false;
465 }
466 encrypted = crypt (unencrypted, correct);
467 memset (unencrypted, 0, strlen (unencrypted));
468 return STREQ (encrypted, correct);
469 #endif /* !USE_PAM */
470 }
471
472 /* Update `environ' for the new shell based on PW, with SHELL being
473 the value for the SHELL environment variable. */
474
475 static void
476 modify_environment (const struct passwd *pw, const char *shell)
477 {
478 if (simulate_login)
479 {
480 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
481 Unset all other environment variables. */
482 char const *term = getenv ("TERM");
483 if (term)
484 term = xstrdup (term);
485 environ = xmalloc ((6 + !!term) * sizeof (char *));
486 environ[0] = NULL;
487 if (term)
488 xsetenv ("TERM", term);
489 xsetenv ("HOME", pw->pw_dir);
490 xsetenv ("SHELL", shell);
491 xsetenv ("USER", pw->pw_name);
492 xsetenv ("LOGNAME", pw->pw_name);
493 xsetenv ("PATH", (pw->pw_uid
494 ? DEFAULT_LOGIN_PATH
495 : DEFAULT_ROOT_LOGIN_PATH));
496 }
497 else
498 {
499 /* Set HOME, SHELL, and if not becoming a super-user,
500 USER and LOGNAME. */
501 if (change_environment)
502 {
503 xsetenv ("HOME", pw->pw_dir);
504 xsetenv ("SHELL", shell);
505 if (pw->pw_uid)
506 {
507 xsetenv ("USER", pw->pw_name);
508 xsetenv ("LOGNAME", pw->pw_name);
509 }
510 }
511 }
512
513 #ifdef USE_PAM
514 export_pamenv ();
515 #endif
516 }
517
518 /* Become the user and group(s) specified by PW. */
519
520 static void
521 init_groups (const struct passwd *pw)
522 {
523 #ifdef HAVE_INITGROUPS
524 errno = 0;
525 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
526 {
527 # ifdef USE_PAM
528 cleanup_pam (PAM_ABORT);
529 # endif
530 error (EXIT_FAIL, errno, _("cannot set groups"));
531 }
532 endgrent ();
533 #endif
534
535 #ifdef USE_PAM
536 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
537 if (retval != PAM_SUCCESS)
538 error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval));
539 else
540 _pam_cred_established = 1;
541 #endif
542 }
543
544 static void
545 change_identity (const struct passwd *pw)
546 {
547 if (setgid (pw->pw_gid))
548 error (EXIT_FAIL, errno, _("cannot set group id"));
549 if (setuid (pw->pw_uid))
550 error (EXIT_FAIL, errno, _("cannot set user id"));
551 }
552
553 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
554 If COMMAND is nonzero, pass it to the shell with the -c option.
555 Pass ADDITIONAL_ARGS to the shell as more arguments; there
556 are N_ADDITIONAL_ARGS extra arguments. */
557
558 static void
559 run_shell (char const *shell, char const *command, char **additional_args,
560 size_t n_additional_args)
561 {
562 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
563 char const **args = xnmalloc (n_args, sizeof *args);
564 size_t argno = 1;
565
566 if (simulate_login)
567 {
568 char *arg0;
569 char *shell_basename;
570
571 shell_basename = last_component (shell);
572 arg0 = xmalloc (strlen (shell_basename) + 2);
573 arg0[0] = '-';
574 strcpy (arg0 + 1, shell_basename);
575 args[0] = arg0;
576 }
577 else
578 args[0] = last_component (shell);
579 if (fast_startup)
580 args[argno++] = "-f";
581 if (command)
582 {
583 args[argno++] = "-c";
584 args[argno++] = command;
585 }
586 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
587 args[argno + n_additional_args] = NULL;
588 execv (shell, (char **) args);
589
590 {
591 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
592 error (0, errno, "%s", shell);
593 exit (exit_status);
594 }
595 }
596
597 /* Return true if SHELL is a restricted shell (one not returned by
598 getusershell), else false, meaning it is a standard shell. */
599
600 static bool
601 restricted_shell (const char *shell)
602 {
603 char *line;
604
605 setusershell ();
606 while ((line = getusershell ()) != NULL)
607 {
608 if (*line != '#' && STREQ (line, shell))
609 {
610 endusershell ();
611 return false;
612 }
613 }
614 endusershell ();
615 return true;
616 }
617
618 void
619 usage (int status)
620 {
621 if (status != EXIT_SUCCESS)
622 fprintf (stderr, _("Try `%s --help' for more information.\n"),
623 program_name);
624 else
625 {
626 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
627 fputs (_("\
628 Change the effective user id and group id to that of USER.\n\
629 \n\
630 -, -l, --login make the shell a login shell\n\
631 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
632 -f, --fast pass -f to the shell (for csh or tcsh)\n\
633 -m, --preserve-environment do not reset environment variables\n\
634 -p same as -m\n\
635 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
636 "), stdout);
637 fputs (HELP_OPTION_DESCRIPTION, stdout);
638 fputs (VERSION_OPTION_DESCRIPTION, stdout);
639 fputs (_("\
640 \n\
641 A mere - implies -l. If USER not given, assume root.\n\
642 "), stdout);
643 emit_bug_reporting_address ();
644 }
645 exit (status);
646 }
647
648 int
649 main (int argc, char **argv)
650 {
651 int optc;
652 const char *new_user = DEFAULT_USER;
653 char *command = NULL;
654 char *shell = NULL;
655 struct passwd *pw;
656 struct passwd pw_copy;
657
658 initialize_main (&argc, &argv);
659 program_name = argv[0];
660 setlocale (LC_ALL, "");
661 bindtextdomain (PACKAGE, LOCALEDIR);
662 textdomain (PACKAGE);
663
664 initialize_exit_failure (EXIT_FAIL);
665 atexit (close_stdout);
666
667 fast_startup = false;
668 simulate_login = false;
669 change_environment = true;
670
671 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
672 {
673 switch (optc)
674 {
675 case 'c':
676 command = optarg;
677 break;
678
679 case 'f':
680 fast_startup = true;
681 break;
682
683 case 'l':
684 simulate_login = true;
685 break;
686
687 case 'm':
688 case 'p':
689 change_environment = false;
690 break;
691
692 case 's':
693 shell = optarg;
694 break;
695
696 case_GETOPT_HELP_CHAR;
697
698 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
699
700 default:
701 usage (EXIT_FAIL);
702 }
703 }
704
705 if (optind < argc && STREQ (argv[optind], "-"))
706 {
707 simulate_login = true;
708 ++optind;
709 }
710 if (optind < argc)
711 new_user = argv[optind++];
712
713 pw = getpwnam (new_user);
714 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
715 && pw->pw_passwd))
716 error (EXIT_FAIL, 0, _("user %s does not exist"), new_user);
717
718 /* Make a copy of the password information and point pw at the local
719 copy instead. Otherwise, some systems (e.g. Linux) would clobber
720 the static data through the getlogin call from log_su.
721 Also, make sure pw->pw_shell is a nonempty string.
722 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
723 but that doesn't have a default shell listed. */
724 pw_copy = *pw;
725 pw = &pw_copy;
726 pw->pw_name = xstrdup (pw->pw_name);
727 pw->pw_passwd = xstrdup (pw->pw_passwd);
728 pw->pw_dir = xstrdup (pw->pw_dir);
729 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
730 ? pw->pw_shell
731 : DEFAULT_SHELL);
732 endpwent ();
733
734 if (!correct_password (pw))
735 {
736 #ifdef SYSLOG_FAILURE
737 log_su (pw, false);
738 #endif
739 error (EXIT_FAIL, 0, _("incorrect password"));
740 }
741 #ifdef SYSLOG_SUCCESS
742 else
743 {
744 log_su (pw, true);
745 }
746 #endif
747
748 if (!shell && !change_environment)
749 shell = getenv ("SHELL");
750 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
751 {
752 /* The user being su'd to has a nonstandard shell, and so is
753 probably a uucp account or has restricted access. Don't
754 compromise the account by allowing access with a standard
755 shell. */
756 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
757 shell = NULL;
758 }
759 shell = xstrdup (shell ? shell : pw->pw_shell);
760
761 init_groups (pw);
762
763 #ifdef USE_PAM
764 create_watching_parent ();
765 /* Now we're in the child. */
766 #endif
767
768 change_identity (pw);
769
770 /* Set environment after pam_open_session, which may put KRB5CCNAME
771 into the pam_env, etc. */
772
773 modify_environment (pw, shell);
774
775 if (simulate_login && chdir (pw->pw_dir) != 0)
776 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
777
778 run_shell (shell, command, argv + optind, MAX (0, argc - optind));
779 }