]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/su.c
su: use ENV_PATH resp ENV_SUPATH to be consistent with login
[thirdparty/util-linux.git] / login-utils / su.c
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
4
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)
8 any later version.
9
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.
14
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. */
18
19 /* Run a shell with the real and effective UID and GID and groups
20 of USER, default `root'.
21
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.
25
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.
30
31 If one or more ARGs are given, they are passed as additional
32 arguments to the subshell.
33
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.
37
38 Based on an implemenation by David MacKenzie <djm@gnu.ai.mit.edu>. */
39
40 enum
41 {
42 EXIT_CANNOT_INVOKE = 126,
43 EXIT_ENOENT = 127
44 };
45
46 #include <config.h>
47 #include <stdio.h>
48 #include <getopt.h>
49 #include <sys/types.h>
50 #include <pwd.h>
51 #include <grp.h>
52 #include <security/pam_appl.h>
53 #include <security/pam_misc.h>
54 #include <signal.h>
55 #include <sys/wait.h>
56 #include <syslog.h>
57
58 #include "error.h"
59
60 #include <stdbool.h>
61 #include "c.h"
62 #include "xalloc.h"
63 #include "nls.h"
64 #include "pathnames.h"
65
66 /* name of the pam configuration files. separate configs for su and su - */
67 #define PAM_SERVICE_NAME "su"
68 #define PAM_SERVICE_NAME_L "su-l"
69
70 #include "logindefs.h"
71
72 /* The shell to run if none is given in the user's passwd entry. */
73 #define DEFAULT_SHELL "/bin/sh"
74
75 /* The user to become if none is specified. */
76 #define DEFAULT_USER "root"
77
78 extern char **environ;
79
80 static void run_shell (char const *, char const *, char **, size_t)
81 __attribute__ ((__noreturn__));
82
83 /* If true, pass the `-f' option to the subshell. */
84 static bool fast_startup;
85
86 /* If true, simulate a login instead of just starting a shell. */
87 static bool simulate_login;
88
89 /* If true, change some environment vars to indicate the user su'd to. */
90 static bool change_environment;
91
92 /* If true, then don't call setsid() with a command. */
93 int same_session = 0;
94
95 static bool _pam_session_opened;
96 static bool _pam_cred_established;
97 static sig_atomic_t volatile caught_signal = false;
98 static pam_handle_t *pamh = NULL;
99
100
101 static struct option const longopts[] =
102 {
103 {"command", required_argument, NULL, 'c'},
104 {"session-command", required_argument, NULL, 'C'},
105 {"fast", no_argument, NULL, 'f'},
106 {"login", no_argument, NULL, 'l'},
107 {"preserve-environment", no_argument, NULL, 'p'},
108 {"shell", required_argument, NULL, 's'},
109 {"help", no_argument, 0, 'u'},
110 {"version", no_argument, 0, 'v'},
111 {NULL, 0, NULL, 0}
112 };
113
114 /* Add NAME=VAL to the environment, checking for out of memory errors. */
115
116 static void
117 xsetenv (char const *name, char const *val)
118 {
119 size_t namelen = strlen (name);
120 size_t vallen = strlen (val);
121 char *string = xmalloc (namelen + 1 + vallen + 1);
122 strcpy (string, name);
123 string[namelen] = '=';
124 strcpy (string + namelen + 1, val);
125 if (putenv (string) != 0)
126 error (EXIT_FAILURE, 0, _("out of memory"));
127 }
128
129 /* Log the fact that someone has run su to the user given by PW;
130 if SUCCESSFUL is true, they gave the correct password, etc. */
131
132 static void
133 log_su (struct passwd const *pw, bool successful)
134 {
135 const char *new_user, *old_user, *tty;
136
137 new_user = pw->pw_name;
138 /* The utmp entry (via getlogin) is probably the best way to identify
139 the user, especially if someone su's from a su-shell. */
140 old_user = getlogin ();
141 if (!old_user)
142 {
143 /* getlogin can fail -- usually due to lack of utmp entry.
144 Resort to getpwuid. */
145 struct passwd *pwd = getpwuid (getuid ());
146 old_user = (pwd ? pwd->pw_name : "");
147 }
148 tty = ttyname (STDERR_FILENO);
149 if (!tty)
150 tty = "none";
151
152 openlog (program_invocation_short_name, 0 , LOG_AUTH);
153 syslog (LOG_NOTICE, "%s(to %s) %s on %s",
154 successful ? "" : "FAILED SU ",
155 new_user, old_user, tty);
156 closelog ();
157 }
158
159 static struct pam_conv conv =
160 {
161 misc_conv,
162 NULL
163 };
164
165 # define PAM_BAIL_P(a) \
166 if (retval) \
167 { \
168 pam_end (pamh, retval); \
169 a; \
170 }
171
172 static void
173 cleanup_pam (int retcode)
174 {
175 if (_pam_session_opened)
176 pam_close_session (pamh, 0);
177
178 if (_pam_cred_established)
179 pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
180
181 pam_end(pamh, retcode);
182 }
183
184 /* Signal handler for parent process. */
185 static void
186 su_catch_sig (int sig __attribute__((__unused__)))
187 {
188 caught_signal = true;
189 }
190
191 /* Export env variables declared by PAM modules. */
192 static void
193 export_pamenv (void)
194 {
195 char **env;
196
197 /* This is a copy but don't care to free as we exec later anyways. */
198 env = pam_getenvlist (pamh);
199 while (env && *env)
200 {
201 if (putenv (*env) != 0)
202 error (EXIT_FAILURE, 0, _("out of memory"));
203 env++;
204 }
205 }
206
207 static void
208 create_watching_parent (void)
209 {
210 pid_t child;
211 sigset_t ourset;
212 int status = 0;
213 int retval;
214
215 retval = pam_open_session (pamh, 0);
216 if (retval != PAM_SUCCESS)
217 {
218 cleanup_pam (retval);
219 error (EXIT_FAILURE, 0, _("cannot not open session: %s"),
220 pam_strerror (pamh, retval));
221 }
222 else
223 _pam_session_opened = 1;
224
225 child = fork ();
226 if (child == (pid_t) -1)
227 {
228 cleanup_pam (PAM_ABORT);
229 error (EXIT_FAILURE, errno, _("cannot create child process"));
230 }
231
232 /* the child proceeds to run the shell */
233 if (child == 0)
234 return;
235
236 /* In the parent watch the child. */
237
238 /* su without pam support does not have a helper that keeps
239 sitting on any directory so let's go to /. */
240 if (chdir ("/") != 0)
241 error (0, errno, _("warning: cannot change directory to %s"), "/");
242
243 sigfillset (&ourset);
244 if (sigprocmask (SIG_BLOCK, &ourset, NULL))
245 {
246 error (0, errno, _("cannot block signals"));
247 caught_signal = true;
248 }
249 if (!caught_signal)
250 {
251 struct sigaction action;
252 action.sa_handler = su_catch_sig;
253 sigemptyset (&action.sa_mask);
254 action.sa_flags = 0;
255 sigemptyset (&ourset);
256 if (!same_session)
257 {
258 if (sigaddset(&ourset, SIGINT) || sigaddset(&ourset, SIGQUIT))
259 {
260 error (0, errno, _("cannot set signal handler"));
261 caught_signal = true;
262 }
263 }
264 if (!caught_signal && (sigaddset(&ourset, SIGTERM)
265 || sigaddset(&ourset, SIGALRM)
266 || sigaction(SIGTERM, &action, NULL)
267 || sigprocmask(SIG_UNBLOCK, &ourset, NULL))) {
268 error (0, errno, _("cannot set signal handler"));
269 caught_signal = true;
270 }
271 if (!caught_signal && !same_session && (sigaction(SIGINT, &action, NULL)
272 || sigaction(SIGQUIT, &action, NULL)))
273 {
274 error (0, errno, _("cannot set signal handler"));
275 caught_signal = true;
276 }
277 }
278 if (!caught_signal)
279 {
280 pid_t pid;
281 for (;;)
282 {
283 pid = waitpid (child, &status, WUNTRACED);
284
285 if (pid != (pid_t)-1 && WIFSTOPPED (status))
286 {
287 kill (getpid (), SIGSTOP);
288 /* once we get here, we must have resumed */
289 kill (pid, SIGCONT);
290 }
291 else
292 break;
293 }
294 if (pid != (pid_t)-1)
295 if (WIFSIGNALED (status))
296 status = WTERMSIG (status) + 128;
297 else
298 status = WEXITSTATUS (status);
299 else
300 status = 1;
301 }
302 else
303 status = 1;
304
305 if (caught_signal)
306 {
307 fprintf (stderr, _("\nSession terminated, killing shell..."));
308 kill (child, SIGTERM);
309 }
310
311 cleanup_pam (PAM_SUCCESS);
312
313 if (caught_signal)
314 {
315 sleep (2);
316 kill (child, SIGKILL);
317 fprintf (stderr, _(" ...killed.\n"));
318 }
319 exit (status);
320 }
321
322 static bool
323 correct_password (const struct passwd *pw)
324 {
325 const struct passwd *lpw;
326 const char *cp;
327 int retval;
328
329 retval = pam_start (simulate_login ? PAM_SERVICE_NAME_L : PAM_SERVICE_NAME,
330 pw->pw_name, &conv, &pamh);
331 PAM_BAIL_P (return false);
332
333 if (isatty (0) && (cp = ttyname (0)) != NULL)
334 {
335 const char *tty;
336
337 if (strncmp (cp, "/dev/", 5) == 0)
338 tty = cp + 5;
339 else
340 tty = cp;
341 retval = pam_set_item (pamh, PAM_TTY, tty);
342 PAM_BAIL_P (return false);
343 }
344 # if 0 /* Manpage discourages use of getlogin. */
345 cp = getlogin ();
346 if (!(cp && *cp && (lpw = getpwnam (cp)) != NULL && lpw->pw_uid == getuid ()))
347 # endif
348 lpw = getpwuid (getuid ());
349 if (lpw && lpw->pw_name)
350 {
351 retval = pam_set_item (pamh, PAM_RUSER, (const void *) lpw->pw_name);
352 PAM_BAIL_P (return false);
353 }
354 retval = pam_authenticate (pamh, 0);
355 PAM_BAIL_P (return false);
356 retval = pam_acct_mgmt (pamh, 0);
357 if (retval == PAM_NEW_AUTHTOK_REQD)
358 {
359 /* Password has expired. Offer option to change it. */
360 retval = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
361 PAM_BAIL_P (return false);
362 }
363 PAM_BAIL_P (return false);
364 /* Must be authenticated if this point was reached. */
365 return true;
366 }
367
368 /* Add or clear /sbin and /usr/sbin for the su command
369 used without `-'. */
370
371 /* Set if /sbin is found in path. */
372 #define SBIN_MASK 0x01
373 /* Set if /usr/sbin is found in path. */
374 #define USBIN_MASK 0x02
375
376 static char *
377 addsbin (const char *const path)
378 {
379 unsigned char smask = 0;
380 char *ptr, *tmp, *cur, *ret = NULL;
381 size_t len;
382
383 if (!path || *path == 0)
384 return NULL;
385
386 tmp = xstrdup (path);
387 cur = tmp;
388 for (ptr = strsep (&cur, ":"); ptr != NULL; ptr = strsep (&cur, ":"))
389 {
390 if (!strcmp (ptr, "/sbin"))
391 smask |= SBIN_MASK;
392 if (!strcmp (ptr, "/usr/sbin"))
393 smask |= USBIN_MASK;
394 }
395
396 if ((smask & (USBIN_MASK|SBIN_MASK)) == (USBIN_MASK|SBIN_MASK))
397 {
398 free (tmp);
399 return NULL;
400 }
401
402 len = strlen (path);
403 if (!(smask & USBIN_MASK))
404 len += strlen ("/usr/sbin:");
405
406 if (!(smask & SBIN_MASK))
407 len += strlen (":/sbin");
408
409 ret = xmalloc (len + 1);
410 strcpy (tmp, path);
411
412 *ret = 0;
413 cur = tmp;
414 for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
415 {
416 if (!strcmp (ptr, "."))
417 continue;
418 if (*ret)
419 strcat (ret, ":");
420 if (!(smask & USBIN_MASK) && !strcmp (ptr, "/bin"))
421 {
422 strcat (ret, "/usr/sbin:");
423 strcat (ret, ptr);
424 smask |= USBIN_MASK;
425 continue;
426 }
427 if (!(smask & SBIN_MASK) && !strcmp (ptr, "/usr/bin"))
428 {
429 strcat (ret, ptr);
430 strcat (ret, ":/sbin");
431 smask |= SBIN_MASK;
432 continue;
433 }
434 strcat (ret, ptr);
435 }
436 free (tmp);
437
438 if (!(smask & USBIN_MASK))
439 strcat (ret, ":/usr/sbin");
440
441 if (!(smask & SBIN_MASK))
442 strcat (ret, ":/sbin");
443
444 return ret;
445 }
446
447 static char *
448 clearsbin (const char *const path)
449 {
450 char *ptr, *tmp, *cur, *ret = NULL;
451
452 if (!path || *path == 0)
453 return NULL;
454
455 tmp = strdup (path);
456 if (!tmp)
457 return NULL;
458
459 ret = xmalloc (strlen (path) + 1);
460 *ret = 0;
461 cur = tmp;
462 for (ptr = strsep (&cur, ":"); ptr; ptr = strsep (&cur, ":"))
463 {
464 if (!strcmp (ptr, "/sbin"))
465 continue;
466 if (!strcmp (ptr, "/usr/sbin"))
467 continue;
468 if (!strcmp (ptr, "/usr/local/sbin"))
469 continue;
470 if (*ret)
471 strcat (ret, ":");
472 strcat (ret, ptr);
473 }
474 free (tmp);
475
476 return ret;
477 }
478
479 static void
480 set_path(const struct passwd* pw)
481 {
482 int r;
483 if (pw->pw_uid)
484 r = logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH);
485
486 else if ((r = logindefs_setenv("PATH", "ENV_ROOTPATH", NULL)) != 0)
487 r = logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT);
488
489 if (r != 0)
490 err (EXIT_FAILURE, _("failed to set PATH"));
491 }
492
493 /* Update `environ' for the new shell based on PW, with SHELL being
494 the value for the SHELL environment variable. */
495
496 static void
497 modify_environment (const struct passwd *pw, const char *shell)
498 {
499 if (simulate_login)
500 {
501 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
502 Unset all other environment variables. */
503 char const *term = getenv ("TERM");
504 if (term)
505 term = xstrdup (term);
506 environ = xmalloc ((6 + !!term) * sizeof (char *));
507 environ[0] = NULL;
508 if (term)
509 xsetenv ("TERM", term);
510 xsetenv ("HOME", pw->pw_dir);
511 xsetenv ("SHELL", shell);
512 xsetenv ("USER", pw->pw_name);
513 xsetenv ("LOGNAME", pw->pw_name);
514 set_path(pw);
515 }
516 else
517 {
518 /* Set HOME, SHELL, and if not becoming a super-user,
519 USER and LOGNAME. */
520 if (change_environment)
521 {
522 xsetenv ("HOME", pw->pw_dir);
523 xsetenv ("SHELL", shell);
524 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
525 set_path(pw);
526 else
527 {
528 char const *path = getenv ("PATH");
529 char *new = NULL;
530
531 if (pw->pw_uid)
532 new = clearsbin (path);
533 else
534 new = addsbin (path);
535
536 if (new)
537 {
538 xsetenv ("PATH", new);
539 free (new);
540 }
541 }
542 if (pw->pw_uid)
543 {
544 xsetenv ("USER", pw->pw_name);
545 xsetenv ("LOGNAME", pw->pw_name);
546 }
547 }
548 }
549
550 export_pamenv ();
551 }
552
553 /* Become the user and group(s) specified by PW. */
554
555 static void
556 init_groups (const struct passwd *pw)
557 {
558 int retval;
559 errno = 0;
560 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
561 {
562 cleanup_pam (PAM_ABORT);
563 error (EXIT_FAILURE, errno, _("cannot set groups"));
564 }
565 endgrent ();
566
567 retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
568 if (retval != PAM_SUCCESS)
569 error (EXIT_FAILURE, 0, "%s", pam_strerror (pamh, retval));
570 else
571 _pam_cred_established = 1;
572 }
573
574 static void
575 change_identity (const struct passwd *pw)
576 {
577 if (setgid (pw->pw_gid))
578 error (EXIT_FAILURE, errno, _("cannot set group id"));
579 if (setuid (pw->pw_uid))
580 error (EXIT_FAILURE, errno, _("cannot set user id"));
581 }
582
583 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
584 If COMMAND is nonzero, pass it to the shell with the -c option.
585 Pass ADDITIONAL_ARGS to the shell as more arguments; there
586 are N_ADDITIONAL_ARGS extra arguments. */
587
588 static void
589 run_shell (char const *shell, char const *command, char **additional_args,
590 size_t n_additional_args)
591 {
592 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
593 char const **args = xcalloc (n_args, sizeof *args);
594 size_t argno = 1;
595
596 if (simulate_login)
597 {
598 char *arg0;
599 char *shell_basename;
600
601 shell_basename = basename (shell);
602 arg0 = xmalloc (strlen (shell_basename) + 2);
603 arg0[0] = '-';
604 strcpy (arg0 + 1, shell_basename);
605 args[0] = arg0;
606 }
607 else
608 args[0] = basename (shell);
609 if (fast_startup)
610 args[argno++] = "-f";
611 if (command)
612 {
613 args[argno++] = "-c";
614 args[argno++] = command;
615 }
616 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
617 args[argno + n_additional_args] = NULL;
618 execv (shell, (char **) args);
619
620 {
621 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
622 error (0, errno, "%s", shell);
623 exit (exit_status);
624 }
625 }
626
627 /* Return true if SHELL is a restricted shell (one not returned by
628 getusershell), else false, meaning it is a standard shell. */
629
630 static bool
631 restricted_shell (const char *shell)
632 {
633 char *line;
634
635 setusershell ();
636 while ((line = getusershell ()) != NULL)
637 {
638 if (*line != '#' && !strcmp (line, shell))
639 {
640 endusershell ();
641 return false;
642 }
643 }
644 endusershell ();
645 return true;
646 }
647
648 void
649 usage (int status)
650 {
651 if (status != EXIT_SUCCESS)
652 fprintf (stderr, _("Try `%s --help' for more information.\n"),
653 program_invocation_short_name);
654 else
655 {
656 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_invocation_short_name);
657 fputs (_("\
658 Change the effective user id and group id to that of USER.\n\
659 \n\
660 -, -l, --login make the shell a login shell\n\
661 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
662 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
663 and do not create a new session\n\
664 -f, --fast pass -f to the shell (for csh or tcsh)\n\
665 -m, --preserve-environment do not reset environment variables\n\
666 -p same as -m\n\
667 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
668 "), stdout);
669 fputs (_(" -u, --help display this help and exit\n"), stdout);
670 fputs (_(" -v, --version output version information and exit\n"), stdout);
671 fputs (_("\
672 \n\
673 A mere - implies -l. If USER not given, assume root.\n\
674 "), stdout);
675 }
676 exit (status);
677 }
678
679 void load_config(void)
680 {
681 logindefs_load_file("/etc/default/su");
682 logindefs_load_file(_PATH_LOGINDEFS);
683 }
684
685 int
686 main (int argc, char **argv)
687 {
688 int optc;
689 const char *new_user = DEFAULT_USER;
690 char *command = NULL;
691 int request_same_session = 0;
692 char *shell = NULL;
693 struct passwd *pw;
694 struct passwd pw_copy;
695
696 setlocale (LC_ALL, "");
697 bindtextdomain (PACKAGE, LOCALEDIR);
698 textdomain (PACKAGE);
699
700 fast_startup = false;
701 simulate_login = false;
702 change_environment = true;
703
704 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
705 {
706 switch (optc)
707 {
708 case 'c':
709 command = optarg;
710 break;
711
712 case 'C':
713 command = optarg;
714 request_same_session = 1;
715 break;
716
717 case 'f':
718 fast_startup = true;
719 break;
720
721 case 'l':
722 simulate_login = true;
723 break;
724
725 case 'm':
726 case 'p':
727 change_environment = false;
728 break;
729
730 case 's':
731 shell = optarg;
732 break;
733
734 case 'u':
735 usage(0);
736
737 case 'v':
738 printf(UTIL_LINUX_VERSION);
739 exit(EXIT_SUCCESS);
740
741 default:
742 usage (EXIT_FAILURE);
743 }
744 }
745
746 if (optind < argc && !strcmp (argv[optind], "-"))
747 {
748 simulate_login = true;
749 ++optind;
750 }
751 if (optind < argc)
752 new_user = argv[optind++];
753
754 logindefs_load_defaults = load_config;
755
756 pw = getpwnam (new_user);
757 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
758 && pw->pw_passwd))
759 error (EXIT_FAILURE, 0, _("user %s does not exist"), new_user);
760
761 /* Make a copy of the password information and point pw at the local
762 copy instead. Otherwise, some systems (e.g. Linux) would clobber
763 the static data through the getlogin call from log_su.
764 Also, make sure pw->pw_shell is a nonempty string.
765 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
766 but that doesn't have a default shell listed. */
767 pw_copy = *pw;
768 pw = &pw_copy;
769 pw->pw_name = xstrdup (pw->pw_name);
770 pw->pw_passwd = xstrdup (pw->pw_passwd);
771 pw->pw_dir = xstrdup (pw->pw_dir);
772 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
773 ? pw->pw_shell
774 : DEFAULT_SHELL);
775 endpwent ();
776
777 if (!correct_password (pw))
778 {
779 log_su (pw, false);
780 sleep (getlogindefs_num ("FAIL_DELAY", 1));
781 error (EXIT_FAILURE, 0, _("incorrect password"));
782 }
783 else
784 {
785 log_su (pw, true);
786 }
787
788 if (request_same_session || !command || !pw->pw_uid)
789 same_session = 1;
790
791 if (!shell && !change_environment)
792 shell = getenv ("SHELL");
793 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
794 {
795 /* The user being su'd to has a nonstandard shell, and so is
796 probably a uucp account or has restricted access. Don't
797 compromise the account by allowing access with a standard
798 shell. */
799 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
800 shell = NULL;
801 }
802 shell = xstrdup (shell ? shell : pw->pw_shell);
803
804 init_groups (pw);
805
806 create_watching_parent ();
807 /* Now we're in the child. */
808
809 change_identity (pw);
810 if (!same_session)
811 setsid ();
812
813 /* Set environment after pam_open_session, which may put KRB5CCNAME
814 into the pam_env, etc. */
815
816 modify_environment (pw, shell);
817
818 if (simulate_login && chdir (pw->pw_dir) != 0)
819 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
820
821 run_shell (shell, command, argv + optind, max (0, argc - optind));
822 }
823
824 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1