]> granicus.if.org Git - shadow/blob - src/su.c
0a4aac3e8120a15af4f2af35c9d6b37200a46738
[shadow] / src / su.c
1 /*
2  * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2000 - 2006, Tomasz Kłoczko
5  * Copyright (c) 2007 - 2008, Nicolas François
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the copyright holders or contributors may not be used to
17  *    endorse or promote products derived from this software without
18  *    specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 /* Some parts substantially derived from an ancestor of:
34    su for GNU.  Run a shell with substitute user and group IDs.
35
36    Copyright (C) 1992-2003 Free Software Foundation, Inc.
37
38    This program is free software; you can redistribute it and/or modify
39    it under the terms of the GNU General Public License as published by
40    the Free Software Foundation; either version 2, or (at your option)
41    any later version.
42
43    This program is distributed in the hope that it will be useful,
44    but WITHOUT ANY WARRANTY; without even the implied warranty of
45    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46    GNU General Public License for more details.
47
48    You should have received a copy of the GNU General Public License
49    along with this program; if not, write to the Free Software
50    Foundation, Inc., 51 Franklin Street, Fifth Floor,
51    Boston, MA 02110-1301, USA.  */
52
53
54 #include <config.h>
55
56 #ident "$Id$"
57
58 #include <getopt.h>
59 #include <grp.h>
60 #include <pwd.h>
61 #include <signal.h>
62 #include <stdio.h>
63 #include <sys/types.h>
64 #include "prototypes.h"
65 #include "defines.h"
66 #include "exitcodes.h"
67 #include "pwauth.h"
68 #include "getdef.h"
69 #ifdef USE_PAM
70 #include "pam_defs.h"
71 #endif
72 /*
73  * Assorted #defines to control su's behavior
74  */
75 /*
76  * Global variables
77  */
78 char *Prog;
79
80 /* not needed by sulog.c anymore */
81 static char name[BUFSIZ];
82 static char oldname[BUFSIZ];
83
84 /* If nonzero, change some environment vars to indicate the user su'd to. */
85 static bool change_environment;
86
87 #ifdef USE_PAM
88 static pam_handle_t *pamh = NULL;
89 static bool caught = false;
90 #endif
91
92 extern struct passwd pwent;
93
94 /*
95  * External identifiers
96  */
97
98 extern char **newenvp;
99 extern char **environ;
100 extern size_t newenvc;
101
102 /* local function prototypes */
103
104 #ifndef USE_PAM
105
106 static RETSIGTYPE die (int);
107 static int iswheel (const char *);
108
109 /*
110  * die - set or reset termio modes.
111  *
112  *      die() is called before processing begins. signal() is then called
113  *      with die() as the signal handler. If signal later calls die() with a
114  *      signal number, the terminal modes are then reset.
115  */
116 static RETSIGTYPE die (int killed)
117 {
118         static TERMIO sgtty;
119
120         if (killed)
121                 STTY (0, &sgtty);
122         else
123                 GTTY (0, &sgtty);
124
125         if (killed) {
126                 closelog ();
127                 exit (killed);
128         }
129 }
130
131 static int iswheel (const char *username)
132 {
133         struct group *grp;
134
135         grp = getgrnam ("wheel"); /* !USE_PAM, no need for xgetgrnam */
136         if (   (NULL ==grp)
137             || (NULL == grp->gr_mem)) {
138                 return 0;
139         }
140         return is_on_list (grp->gr_mem, username);
141 }
142 #endif                          /* !USE_PAM */
143
144 /* borrowed from GNU sh-utils' "su.c" */
145 static bool restricted_shell (const char *shellstr)
146 {
147         char *line;
148
149         setusershell ();
150         while ((line = getusershell ()) != NULL) {
151                 if (('#' != *line) && (strcmp (line, shellstr) == 0)) {
152                         endusershell ();
153                         return false;
154                 }
155         }
156         endusershell ();
157         return true;
158 }
159
160 static void su_failure (const char *tty)
161 {
162         sulog (tty, 0, oldname, name);  /* log failed attempt */
163 #ifdef USE_SYSLOG
164         if (getdef_bool ("SYSLOG_SU_ENAB")) {
165                 SYSLOG (((0 != pwent.pw_uid) ? LOG_INFO : LOG_NOTICE,
166                          "- %s %s:%s", tty,
167                          ('\0' != oldname[0]) ? oldname : "???",
168                          ('\0' != name[0]) ? name : "???"));
169         }
170         closelog ();
171 #endif
172         exit (1);
173 }
174
175
176 #ifdef USE_PAM
177 /* Signal handler for parent process later */
178 static void catch_signals (unused int sig)
179 {
180         caught = true;
181 }
182
183 /* This I ripped out of su.c from sh-utils after the Mandrake pam patch
184  * have been applied.  Some work was needed to get it integrated into
185  * su.c from shadow.
186  */
187 static void run_shell (const char *shellstr, char *args[], bool doshell,
188                        char *const envp[])
189 {
190         pid_t child;
191         sigset_t ourset;
192         int status;
193         int ret;
194
195         child = fork ();
196         if (child == 0) {       /* child shell */
197                 /*
198                  * PAM_DATA_SILENT is not supported by some modules, and
199                  * there is no strong need to clean up the process space's
200                  * memory since we will either call exec or exit.
201                 pam_end (pamh, PAM_SUCCESS | PAM_DATA_SILENT);
202                  */
203
204                 if (doshell) {
205                         (void) shell (shellstr, (char *) args[0], envp);
206                 } else {
207                         (void) execve (shellstr, (char **) args, envp);
208                 }
209                 exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
210         } else if ((pid_t)-1 == child) {
211                 (void) fprintf (stderr, "%s: Cannot fork user shell\n", Prog);
212                 SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
213                 closelog ();
214                 exit (1);
215         }
216         /* parent only */
217         sigfillset (&ourset);
218         if (sigprocmask (SIG_BLOCK, &ourset, NULL) != 0) {
219                 (void) fprintf (stderr, "%s: signal malfunction\n", Prog);
220                 caught = true;
221         }
222         if (!caught) {
223                 struct sigaction action;
224
225                 action.sa_handler = catch_signals;
226                 sigemptyset (&action.sa_mask);
227                 action.sa_flags = 0;
228                 sigemptyset (&ourset);
229
230                 if (   (sigaddset (&ourset, SIGTERM) != 0)
231                     || (sigaddset (&ourset, SIGALRM) != 0)
232                     || (sigaction (SIGTERM, &action, NULL) != 0)
233                     || (sigprocmask (SIG_UNBLOCK, &ourset, NULL) != 0)
234                     ) {
235                         fprintf (stderr,
236                                  "%s: signal masking malfunction\n", Prog);
237                         caught = true;
238                 }
239         }
240
241         if (!caught) {
242                 do {
243                         pid_t pid;
244
245                         pid = waitpid (-1, &status, WUNTRACED);
246
247                         if (((pid_t)-1 != pid) && (0 != WIFSTOPPED (status))) {
248                                 /* The child (shell) was suspended.
249                                  * Suspend su. */
250                                 kill (getpid (), WSTOPSIG(status));
251                                 /* wake child when resumed */
252                                 kill (pid, SIGCONT);
253                         }
254                 } while (0 != WIFSTOPPED (status));
255         }
256
257         if (caught) {
258                 fprintf (stderr, "\nSession terminated, killing shell...");
259                 kill (child, SIGTERM);
260         }
261
262         ret = pam_close_session (pamh, 0);
263         if (PAM_SUCCESS != ret) {
264                 SYSLOG ((LOG_ERR, "pam_close_session: %s",
265                          pam_strerror (pamh, ret)));
266                 fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
267                 (void) pam_end (pamh, ret);
268                 exit (1);
269         }
270
271         ret = pam_end (pamh, PAM_SUCCESS);
272
273         if (caught) {
274                 sleep (2);
275                 kill (child, SIGKILL);
276                 fprintf (stderr, " ...killed.\n");
277                 exit (-1);
278         }
279
280         exit ((0 != WIFEXITED (status)) ? WEXITSTATUS (status)
281                                         : WTERMSIG (status) + 128);
282 }
283 #endif
284
285 /*
286  * usage - print command line syntax and exit
287   */
288 static void usage (void)
289 {
290         fputs (_("Usage: su [options] [LOGIN]\n"
291                  "\n"
292                  "Options:\n"
293                  "  -c, --command COMMAND         pass COMMAND to the invoked shell\n"
294                  "  -h, --help                    display this help message and exit\n"
295                  "  -, -l, --login                make the shell a login shell\n"
296                  "  -m, -p,\n"
297                  "  --preserve-environment        do not reset environment variables, and\n"
298                  "                                keep the same shell\n"
299                  "  -s, --shell SHELL             use SHELL instead of the default in passwd\n"
300                  "\n"), stderr);
301         exit (E_USAGE);
302 }
303
304 /*
305  * su - switch user id
306  *
307  *      su changes the user's ids to the values for the specified user.  if
308  *      no new user name is specified, "root" is used by default.
309  *
310  *      Any additional arguments are passed to the user's shell. In
311  *      particular, the argument "-c" will cause the next argument to be
312  *      interpreted as a command by the common shell programs.
313  */
314 int main (int argc, char **argv)
315 {
316         char *cp;
317         const char *tty = NULL; /* Name of tty SU is run from        */
318         bool doshell = false;
319         bool fakelogin = false;
320         bool amroot = false;
321         uid_t my_uid;
322         struct passwd *pw = NULL;
323         char **envp = environ;
324         char *shellstr = NULL;
325         char *command = NULL;
326
327 #ifdef USE_PAM
328         char **envcp;
329         int ret;
330 #else                           /* !USE_PAM */
331         int err = 0;
332
333         RETSIGTYPE (*oldsig) (int);
334         int is_console = 0;
335
336         struct spwd *spwd = 0;
337
338 #ifdef SU_ACCESS
339         char *oldpass;
340 #endif
341 #endif                          /* !USE_PAM */
342
343         sanitize_env ();
344
345         (void) setlocale (LC_ALL, "");
346         (void) bindtextdomain (PACKAGE, LOCALEDIR);
347         (void) textdomain (PACKAGE);
348
349         change_environment = true;
350
351         /*
352          * Get the program name. The program name is used as a prefix to
353          * most error messages.
354          */
355         Prog = Basename (argv[0]);
356
357         OPENLOG ("su");
358
359         /*
360          * Process the command line arguments. 
361          */
362
363         {
364                 /*
365                  * Parse the command line options.
366                  */
367                 int option_index = 0;
368                 int c;
369                 static struct option long_options[] = {
370                         {"command", required_argument, NULL, 'c'},
371                         {"help", no_argument, NULL, 'h'},
372                         {"login", no_argument, NULL, 'l'},
373                         {"preserve-environment", no_argument, NULL, 'p'},
374                         {"shell", required_argument, NULL, 's'},
375                         {NULL, 0, NULL, '\0'}
376                 };
377
378                 while ((c =
379                         getopt_long (argc, argv, "c:hlmps:", long_options,
380                                      &option_index)) != -1) {
381                         switch (c) {
382                         case 'c':
383                                 command = optarg;
384                                 break;
385                         case 'h':
386                                 usage ();
387                                 break;
388                         case 'l':
389                                 fakelogin = true;
390                                 break;
391                         case 'm':
392                         case 'p':
393                                 /* This will only have an effect if the target
394                                  * user do not have a restricted shell, or if
395                                  * su is called by root.
396                                  */
397                                 change_environment = false;
398                                 break;
399                         case 's':
400                                 shellstr = optarg;
401                                 break;
402                         default:
403                                 usage ();       /* NOT REACHED */
404                         }
405                 }
406
407                 if ((optind < argc) && (strcmp (argv[optind], "-") == 0)) {
408                         fakelogin = true;
409                         optind++;
410                         if (   (optind < argc)
411                             && (strcmp (argv[optind], "--") == 0)) {
412                                 optind++;
413                         }
414                 }
415         }
416
417         initenv ();
418
419         my_uid = getuid ();
420         amroot = (my_uid == 0);
421
422         /*
423          * Get the tty name. Entries will be logged indicating that the user
424          * tried to change to the named new user from the current terminal.
425          */
426         cp = ttyname (0);
427         if ((isatty (0) != 0) && (NULL != cp)) {
428                 if (strncmp (cp, "/dev/", 5) == 0) {
429                         tty = cp + 5;
430                 } else {
431                         tty = cp;
432                 }
433 #ifndef USE_PAM
434                 is_console = console (tty);
435 #endif
436         } else {
437                 /*
438                  * Be more paranoid, like su from SimplePAMApps.  --marekm
439                  */
440                 if (!amroot) {
441                         fprintf (stderr,
442                                  _("%s: must be run from a terminal\n"), Prog);
443                         exit (1);
444                 }
445                 tty = "???";
446         }
447
448         /*
449          * The next argument must be either a user ID, or some flag to a
450          * subshell. Pretty sticky since you can't have an argument which
451          * doesn't start with a "-" unless you specify the new user name.
452          * Any remaining arguments will be passed to the user's login shell.
453          */
454         if ((optind < argc) && ('-' != argv[optind][0])) {
455                 STRFCPY (name, argv[optind++]); /* use this login id */
456                 if ((optind < argc) && (strcmp (argv[optind], "--") == 0)) {
457                         optind++;
458                 }
459         }
460         if ('\0' == name[0]) {          /* use default user ID */
461                 (void) strcpy (name, "root");
462         }
463
464         doshell = (argc == optind);     /* any arguments remaining? */
465         if (NULL != command) {
466                 doshell = false;
467         }
468
469         /*
470          * Get the user's real name. The current UID is used to determine
471          * who has executed su. That user ID must exist.
472          */
473         pw = get_my_pwent ();
474         if (NULL == pw) {
475                 fprintf (stderr, _("%s: Cannot determine your user name.\n"),
476                          Prog);
477                 SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
478                          (unsigned long) my_uid));
479                 su_failure (tty);
480         }
481         STRFCPY (oldname, pw->pw_name);
482
483 #ifndef USE_PAM
484 #ifdef SU_ACCESS
485         /*
486          * Sort out the password of user calling su, in case needed later
487          * -- chris
488          */
489         spwd = getspnam (oldname); /* !USE_PAM, no need for xgetspnam */
490         if (NULL != spwd) {
491                 pw->pw_passwd = spwd->sp_pwdp;
492         }
493         oldpass = xstrdup (pw->pw_passwd);
494 #endif                          /* SU_ACCESS */
495
496 #else                           /* USE_PAM */
497         ret = pam_start ("su", name, &conv, &pamh);
498         if (PAM_SUCCESS != ret) {
499                 SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
500                         fprintf (stderr, _("%s: pam_start: error %d\n"),
501                                  Prog, ret));
502                 exit (1);
503         }
504
505         ret = pam_set_item (pamh, PAM_TTY, (const void *) tty);
506         if (PAM_SUCCESS == ret) {
507                 ret = pam_set_item (pamh, PAM_RUSER, (const void *) oldname);
508         }
509         if (PAM_SUCCESS != ret) {
510                 SYSLOG ((LOG_ERR, "pam_set_item: %s",
511                          pam_strerror (pamh, ret)));
512                 fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
513                 pam_end (pamh, ret);
514                 exit (1);
515         }
516 #endif                          /* USE_PAM */
517
518       top:
519         /*
520          * This is the common point for validating a user whose name is
521          * known. It will be reached either by normal processing, or if the
522          * user is to be logged into a subsystem root.
523          *
524          * The password file entries for the user is gotten and the account
525          * validated.
526          */
527         pw = xgetpwnam (name);
528         if (NULL == pw) {
529                 (void) fprintf (stderr, _("Unknown id: %s\n"), name);
530                 closelog ();
531                 exit (1);
532         }
533 #ifndef USE_PAM
534         spwd = NULL;
535         if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0) {
536                 spwd = getspnam (name); /* !USE_PAM, no need for xgetspnam */
537                 if (NULL != spwd) {
538                         pw->pw_passwd = spwd->sp_pwdp;
539                 }
540         }
541 #endif                          /* !USE_PAM */
542         pwent = *pw;
543
544         /* If su is not called by root, and the target user has a restricted
545          * shell, the environment must be changed.
546          */
547         change_environment |= (restricted_shell (pwent.pw_shell) && !amroot);
548
549         /*
550          * If a new login is being set up, the old environment will be
551          * ignored and a new one created later on.
552          * (note: in the case of a subsystem, the shell will be restricted,
553          *        and this won't be executed on the first pass)
554          */
555         if (fakelogin && change_environment) {
556                 /*
557                  * The terminal type will be left alone if it is present in
558                  * the environment already.
559                  */
560                 cp = getenv ("TERM");
561                 if (NULL != cp) {
562                         addenv ("TERM", cp);
563                 }
564 #ifndef USE_PAM
565                 cp = getdef_str ("ENV_TZ");
566                 if (NULL != cp) {
567                         addenv (('/' == *cp) ? tz (cp) : cp, NULL);
568                 }
569
570                 /*
571                  * The clock frequency will be reset to the login value if required
572                  */
573                 cp = getdef_str ("ENV_HZ");
574                 if (NULL != cp) {
575                         addenv (cp, NULL);      /* set the default $HZ, if one */
576                 }
577
578                 /*
579                  * Also leave DISPLAY and XAUTHORITY if present, else
580                  * pam_xauth will not work.
581                  */
582                 cp = getenv ("DISPLAY");
583                 if (NULL != cp) {
584                         addenv ("DISPLAY", cp);
585                 }
586                 cp = getenv ("XAUTHORITY");
587                 if (NULL != cp) {
588                         addenv ("XAUTHORITY", cp);
589                 }
590 #endif                          /* !USE_PAM */
591         } else {
592                 while (NULL != *envp) {
593                         addenv (*envp, NULL);
594                         envp++;
595                 }
596         }
597
598 #ifndef USE_PAM
599         /*
600          * BSD systems only allow "wheel" to SU to root. USG systems don't,
601          * so we make this a configurable option.
602          */
603
604         /* The original Shadow 3.3.2 did this differently. Do it like BSD:
605          *
606          * - check for UID 0 instead of name "root" - there are systems with
607          *   several root accounts under different names,
608          *
609          * - check the contents of /etc/group instead of the current group
610          *   set (you must be listed as a member, GID 0 is not sufficient).
611          *
612          * In addition to this traditional feature, we now have complete su
613          * access control (allow, deny, no password, own password).  Thanks
614          * to Chris Evans <lady0110@sable.ox.ac.uk>.
615          */
616
617         if (!amroot) {
618                 if (   (0 == pwent.pw_uid)
619                     && getdef_bool ("SU_WHEEL_ONLY")
620                     && !iswheel (oldname)) {
621                         fprintf (stderr,
622                                  _("You are not authorized to su %s\n"), name);
623                         exit (1);
624                 }
625 #ifdef SU_ACCESS
626                 switch (check_su_auth (oldname, name)) {
627                 case 0: /* normal su, require target user's password */
628                         break;
629                 case 1: /* require no password */
630                         pwent.pw_passwd = "";   /* XXX warning: const */
631                         break;
632                 case 2: /* require own password */
633                         puts (_("(Enter your own password)"));
634                         pwent.pw_passwd = oldpass;
635                         break;
636                 default:        /* access denied (-1) or unexpected value */
637                         fprintf (stderr,
638                                  _("You are not authorized to su %s\n"), name);
639                         exit (1);
640                 }
641 #endif                          /* SU_ACCESS */
642         }
643 #endif                          /* !USE_PAM */
644
645         /* If the user do not want to change the environment,
646          * use the current SHELL.
647          * (unless another shell is required by the command line)
648          */
649         if ((NULL == shellstr) && !change_environment) {
650                 shellstr = getenv ("SHELL");
651         }
652         /* For users with non null UID, if this user has a restricted
653          * shell, the shell must be the one specified in /etc/passwd
654          */
655         if (   (NULL != shellstr)
656             && !amroot
657             && restricted_shell (pwent.pw_shell)) {
658                 shellstr = NULL;
659         }
660         /* If the shell is not set at this time, use the shell specified
661          * in /etc/passwd.
662          */
663         if (NULL == shellstr) {
664                 shellstr = (char *) strdup (pwent.pw_shell);
665         }
666
667         /*
668          * Set the default shell.
669          */
670         if ((NULL == shellstr) || ('\0' == shellstr[0])) {
671                 shellstr = "/bin/sh";
672         }
673
674         (void) signal (SIGINT, SIG_IGN);
675         (void) signal (SIGQUIT, SIG_IGN);
676 #ifdef USE_PAM
677         ret = pam_authenticate (pamh, 0);
678         if (PAM_SUCCESS != ret) {
679                 SYSLOG ((LOG_ERR, "pam_authenticate: %s",
680                          pam_strerror (pamh, ret)));
681                 fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
682                 (void) pam_end (pamh, ret);
683                 su_failure (tty);
684         }
685
686         ret = pam_acct_mgmt (pamh, 0);
687         if (PAM_SUCCESS != ret) {
688                 if (amroot) {
689                         fprintf (stderr, _("%s: %s\n(Ignored)\n"), Prog,
690                                  pam_strerror (pamh, ret));
691                 } else if (PAM_NEW_AUTHTOK_REQD == ret) {
692                         ret = pam_chauthtok (pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
693                         if (PAM_SUCCESS != ret) {
694                                 SYSLOG ((LOG_ERR, "pam_chauthtok: %s",
695                                          pam_strerror (pamh, ret)));
696                                 fprintf (stderr, _("%s: %s\n"), Prog,
697                                          pam_strerror (pamh, ret));
698                                 (void) pam_end (pamh, ret);
699                                 su_failure (tty);
700                         }
701                 } else {
702                         SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
703                                  pam_strerror (pamh, ret)));
704                         fprintf (stderr, _("%s: %s\n"), Prog,
705                                  pam_strerror (pamh, ret));
706                         (void) pam_end (pamh, ret);
707                         su_failure (tty);
708                 }
709         }
710 #else                           /* !USE_PAM */
711         /*
712          * Set up a signal handler in case the user types QUIT.
713          */
714         die (0);
715         oldsig = signal (SIGQUIT, die);
716
717         /*
718          * See if the system defined authentication method is being used. 
719          * The first character of an administrator defined method is an '@'
720          * character.
721          */
722         if (!amroot && pw_auth (pwent.pw_passwd, name, PW_SU, (char *) 0)) {
723                 SYSLOG ((pwent.pw_uid ? LOG_NOTICE : LOG_WARN,
724                          "Authentication failed for %s", name));
725                 fprintf(stderr, _("%s: Authentication failure\n"), Prog);
726                 su_failure (tty);
727         }
728         (void) signal (SIGQUIT, oldsig);
729
730         /*
731          * Check to see if the account is expired. root gets to ignore any
732          * expired accounts, but normal users can't become a user with an
733          * expired password.
734          */
735         if (!amroot) {
736                 if (NULL == spwd) {
737                         spwd = pwd_to_spwd (&pwent);
738                 }
739
740                 if (expire (&pwent, spwd)) {
741                         /* !USE_PAM, no need for xgetpwnam */
742                         struct passwd *pwd = getpwnam (name);
743
744                         /* !USE_PAM, no need for xgetspnam */
745                         spwd = getspnam (name);
746                         if (NULL != pwd) {
747                                 pwent = *pwd;
748                         }
749                 }
750         }
751
752         /*
753          * Check to see if the account permits "su". root gets to ignore any
754          * restricted accounts, but normal users can't become a user if
755          * there is a "SU" entry in the /etc/porttime file denying access to
756          * the account.
757          */
758         if (!amroot) {
759                 if (!isttytime (pwent.pw_name, "SU", time ((time_t *) 0))) {
760                         SYSLOG (((0 != pwent.pw_uid) ? LOG_WARN : LOG_CRIT,
761                                  "SU by %s to restricted account %s",
762                                  oldname, name));
763                         fprintf(stderr,
764                                 _("%s: You are not authorized to su at that time\n"), Prog);
765                         su_failure (tty);
766                 }
767         }
768 #endif                          /* !USE_PAM */
769
770         (void) signal (SIGINT, SIG_DFL);
771         (void) signal (SIGQUIT, SIG_DFL);
772
773         cp = getdef_str ((pwent.pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
774         if (NULL == cp) {
775                 addenv ("PATH=/bin:/usr/bin", NULL);
776         } else if (strchr (cp, '=') != NULL) {
777                 addenv (cp, NULL);
778         } else {
779                 addenv ("PATH", cp);
780         }
781
782         if (getenv ("IFS") != NULL) {   /* don't export user IFS ... */
783                 addenv ("IFS= \t\n", NULL);     /* ... instead, set a safe IFS */
784         }
785
786         /*
787          * Even if --shell is specified, the subsystem login test is based on
788          * the shell specified in /etc/passwd (not the one specified with
789          * --shell, which will be the one executed in the chroot later).
790          */
791         if ('*' == pwent.pw_shell[0]) { /* subsystem root required */
792                 pwent.pw_shell++;       /* skip the '*' */
793                 subsystem (&pwent);     /* figure out what to execute */
794                 endpwent ();
795                 endspent ();
796                 goto top;
797         }
798
799         sulog (tty, true, oldname, name);       /* save SU information */
800         endpwent ();
801         endspent ();
802 #ifdef USE_SYSLOG
803         if (getdef_bool ("SYSLOG_SU_ENAB")) {
804                 SYSLOG ((LOG_INFO, "+ %s %s:%s", tty,
805                          ('\0' != oldname[0]) ? oldname : "???",
806                          ('\0' != name[0]) ? name : "???"));
807         }
808 #endif
809
810 #ifdef USE_PAM
811         /* set primary group id and supplementary groups */
812         if (setup_groups (&pwent) != 0) {
813                 pam_end (pamh, PAM_ABORT);
814                 exit (1);
815         }
816
817         /*
818          * pam_setcred() may do things like resource limits, console groups,
819          * and much more, depending on the configured modules
820          */
821         ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
822         if (PAM_SUCCESS != ret) {
823                 SYSLOG ((LOG_ERR, "pam_setcred: %s", pam_strerror (pamh, ret)));
824                 fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
825                 (void) pam_end (pamh, ret);
826                 exit (1);
827         }
828
829         ret = pam_open_session (pamh, 0);
830         if (PAM_SUCCESS != ret) {
831                 SYSLOG ((LOG_ERR, "pam_open_session: %s",
832                          pam_strerror (pamh, ret)));
833                 fprintf (stderr, _("%s: %s\n"), Prog, pam_strerror (pamh, ret));
834                 pam_setcred (pamh, PAM_DELETE_CRED);
835                 (void) pam_end (pamh, ret);
836                 exit (1);
837         }
838
839         if (change_environment) {
840                 /* we need to setup the environment *after* pam_open_session(),
841                  * else the UID is changed before stuff like pam_xauth could
842                  * run, and we cannot access /etc/shadow and co
843                  */
844                 environ = newenvp;      /* make new environment active */
845
846                 /* update environment with all pam set variables */
847                 envcp = pam_getenvlist (pamh);
848                 if (NULL != envcp) {
849                         while (NULL != *envcp) {
850                                 addenv (*envcp, NULL);
851                                 envcp++;
852                         }
853                 }
854         }
855
856         /* become the new user */
857         if (change_uid (&pwent) != 0) {
858                 pam_close_session (pamh, 0);
859                 pam_setcred (pamh, PAM_DELETE_CRED);
860                 (void) pam_end (pamh, PAM_ABORT);
861                 exit (1);
862         }
863 #else                           /* !USE_PAM */
864         environ = newenvp;      /* make new environment active */
865
866         /* no limits if su from root (unless su must fake login's behavior) */
867         if (!amroot || fakelogin) {
868                 setup_limits (&pwent);
869         }
870
871         if (setup_uid_gid (&pwent, is_console) != 0) {
872                 exit (1);
873         }
874 #endif                          /* !USE_PAM */
875
876         if (change_environment) {
877                 if (fakelogin) {
878                         pwent.pw_shell = shellstr;
879                         setup_env (&pwent);
880                 } else {
881                         addenv ("HOME", pwent.pw_dir);
882                         addenv ("USER", pwent.pw_name);
883                         addenv ("LOGNAME", pwent.pw_name);
884                         addenv ("SHELL", shellstr);
885                 }
886         }
887
888         /*
889          * This is a workaround for Linux libc bug/feature (?) - the
890          * /dev/log file descriptor is open without the close-on-exec flag
891          * and used to be passed to the new shell. There is "fcntl(LogFile,
892          * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
893          * least in 5.4.33). Why?  --marekm
894          */
895         closelog ();
896
897         /*
898          * See if the user has extra arguments on the command line. In that
899          * case they will be provided to the new user's shell as arguments.
900          */
901         if (fakelogin) {
902                 char *arg0;
903
904                 cp = getdef_str ("SU_NAME");
905                 if (NULL == cp) {
906                         cp = Basename (shellstr);
907                 }
908
909                 arg0 = xmalloc (strlen (cp) + 2);
910                 arg0[0] = '-';
911                 strcpy (arg0 + 1, cp);
912                 cp = arg0;
913         } else {
914                 cp = Basename (shellstr);
915         }
916
917         if (!doshell) {
918                 /* Position argv to the remaining arguments */
919                 argv += optind;
920                 if (NULL != command) {
921                         argv -= 2;
922                         argv[0] = "-c";
923                         argv[1] = command;
924                 }
925                 /*
926                  * Use the shell and create an argv
927                  * with the rest of the command line included.
928                  */
929                 argv[-1] = shellstr;
930 #ifndef USE_PAM
931                 (void) execve (shellstr, &argv[-1], environ);
932                 err = errno;
933                 (void) fputs (_("No shell\n"), stderr);
934                 SYSLOG ((LOG_WARN, "Cannot execute %s", shellstr));
935                 closelog ();
936                 exit ((ENOENT == err) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
937 #else
938                 run_shell (shellstr, &argv[-1], false, environ); /* no return */
939 #endif
940         }
941 #ifndef USE_PAM
942         err = shell (shellstr, cp, environ);
943         exit ((ENOENT == err) ? E_CMD_NOTFOUND : E_CMD_NOEXEC);
944 #else
945         run_shell (shellstr, &cp, true, environ);
946 #endif
947         /* NOT REACHED */
948         exit (1);
949 }
950