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