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