]> granicus.if.org Git - shadow/blob - src/su.c
[svn-upgrade] Integrating new upstream version, shadow (4.0.1)
[shadow] / src / su.c
1 /*
2  * Copyright 1989 - 1994, Julianne Frances Haugh
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
14  *    may be used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <config.h>
31
32 #include "rcsid.h"
33 RCSID (PKG_VER "$Id: su.c,v 1.21 2002/01/05 15:41:44 kloczek Exp $")
34 #include <sys/types.h>
35 #include <stdio.h>
36 #ifdef USE_PAM
37 #include "pam_defs.h"
38 static const struct pam_conv conv = {
39         misc_conv,
40         NULL
41 };
42
43 static pam_handle_t *pamh = NULL;
44 #endif
45
46 #include "prototypes.h"
47 #include "defines.h"
48
49 #include <grp.h>
50 #include <signal.h>
51 #include <pwd.h>
52 #include "pwauth.h"
53 #include "getdef.h"
54
55 /*
56  * Assorted #defines to control su's behavior
57  */
58
59 /*
60  * Global variables
61  */
62
63 /* not needed by sulog.c anymore */
64 static char name[BUFSIZ];
65 static char oldname[BUFSIZ];
66
67 static char *Prog;
68
69 struct passwd pwent;
70
71 /*
72  * External identifiers
73  */
74
75 extern char **newenvp;
76 extern size_t newenvc;
77
78 extern char **environ;
79
80 /* local function prototypes */
81
82 #ifndef USE_PAM
83
84 static RETSIGTYPE die (int);
85 static int iswheel (const char *);
86
87 /*
88  * die - set or reset termio modes.
89  *
90  *      die() is called before processing begins. signal() is then called
91  *      with die() as the signal handler. If signal later calls die() with a
92  *      signal number, the terminal modes are then reset.
93  */
94
95 static RETSIGTYPE die (int killed)
96 {
97         static TERMIO sgtty;
98
99         if (killed)
100                 STTY (0, &sgtty);
101         else
102                 GTTY (0, &sgtty);
103
104         if (killed) {
105                 closelog ();
106                 exit (killed);
107         }
108 }
109
110 static int iswheel (const char *username)
111 {
112         struct group *grp;
113
114         grp = getgrgid (0);
115         if (!grp || !grp->gr_mem)
116                 return 0;
117         return is_on_list (grp->gr_mem, username);
118 }
119 #endif                          /* !USE_PAM */
120
121
122 static void su_failure (const char *tty)
123 {
124         sulog (tty, 0, oldname, name);  /* log failed attempt */
125 #ifdef USE_SYSLOG
126         if (getdef_bool ("SYSLOG_SU_ENAB"))
127                 SYSLOG ((pwent.pw_uid ? LOG_INFO : LOG_NOTICE,
128                          "- %s %s-%s", tty,
129                          oldname[0] ? oldname : "???",
130                          name[0] ? name : "???"));
131         closelog ();
132 #endif
133         puts (_("Sorry."));
134         exit (1);
135 }
136
137
138 /*
139  * su - switch user id
140  *
141  *      su changes the user's ids to the values for the specified user.  if
142  *      no new user name is specified, "root" is used by default.
143  *
144  *      The only valid option is a "-" character, which is interpreted as
145  *      requiring a new login session to be simulated.
146  *
147  *      Any additional arguments are passed to the user's shell. In
148  *      particular, the argument "-c" will cause the next argument to be
149  *      interpreted as a command by the common shell programs.
150  */
151
152 int main (int argc, char **argv)
153 {
154         char *cp;
155         const char *tty = 0;    /* Name of tty SU is run from        */
156         int doshell = 0;
157         int fakelogin = 0;
158         int amroot = 0;
159         uid_t my_uid;
160         struct passwd *pw = 0;
161         char **envp = environ;
162
163 #ifdef USE_PAM
164         int ret;
165 #else                           /* !USE_PAM */
166         RETSIGTYPE (*oldsig) ();
167         int is_console = 0;
168
169 #ifdef  SHADOWPWD
170         struct spwd *spwd = 0;
171 #endif
172 #ifdef SU_ACCESS
173         char *oldpass;
174 #endif
175 #endif                          /* !USE_PAM */
176
177         sanitize_env ();
178
179         setlocale (LC_ALL, "");
180         bindtextdomain (PACKAGE, LOCALEDIR);
181         textdomain (PACKAGE);
182
183         /*
184          * Get the program name. The program name is used as a prefix to
185          * most error messages.
186          */
187
188         Prog = Basename (argv[0]);
189
190         OPENLOG ("su");
191
192         initenv ();
193
194         my_uid = getuid ();
195         amroot = (my_uid == 0);
196
197         /*
198          * Get the tty name. Entries will be logged indicating that the user
199          * tried to change to the named new user from the current terminal.
200          */
201
202         if (isatty (0) && (cp = ttyname (0))) {
203                 if (strncmp (cp, "/dev/", 5) == 0)
204                         tty = cp + 5;
205                 else
206                         tty = cp;
207 #ifndef USE_PAM
208                 is_console = console (tty);
209 #endif
210         } else {
211                 /*
212                  * Be more paranoid, like su from SimplePAMApps.  --marekm
213                  */
214                 if (!amroot) {
215                         fprintf (stderr,
216                                  _("%s: must be run from a terminal\n"),
217                                  Prog);
218                         exit (1);
219                 }
220                 tty = "???";
221         }
222
223         /*
224          * Process the command line arguments. 
225          */
226
227         argc--;
228         argv++;                 /* shift out command name */
229
230         if (argc > 0 && strcmp (argv[0], "-") == 0) {
231                 fakelogin = 1;
232                 argc--;
233                 argv++;         /* shift ... */
234         }
235
236         /*
237          * If a new login is being set up, the old environment will be
238          * ignored and a new one created later on.
239          */
240
241         if (fakelogin) {
242                 if ((cp = getdef_str ("ENV_TZ")))
243                         addenv (*cp == '/' ? tz (cp) : cp, NULL);
244                 /*
245                  * The clock frequency will be reset to the login value if required
246                  */
247                 if ((cp = getdef_str ("ENV_HZ")))
248                         addenv (cp, NULL);      /* set the default $HZ, if one */
249                 /*
250                  * The terminal type will be left alone if it is present in
251                  * the environment already.
252                  */
253                 if ((cp = getenv ("TERM")))
254                         addenv ("TERM", cp);
255         } else {
256                 while (*envp)
257                         addenv (*envp++, NULL);
258         }
259
260         /*
261          * The next argument must be either a user ID, or some flag to a
262          * subshell. Pretty sticky since you can't have an argument which
263          * doesn't start with a "-" unless you specify the new user name.
264          * Any remaining arguments will be passed to the user's login shell.
265          */
266
267         if (argc > 0 && argv[0][0] != '-') {
268                 STRFCPY (name, argv[0]);        /* use this login id */
269                 argc--;
270                 argv++;         /* shift ... */
271         }
272         if (!name[0])           /* use default user ID */
273                 (void) strcpy (name, "root");
274
275         doshell = argc == 0;    /* any arguments remaining? */
276
277         /*
278          * Get the user's real name. The current UID is used to determine
279          * who has executed su. That user ID must exist.
280          */
281
282         pw = get_my_pwent ();
283         if (!pw) {
284                 SYSLOG ((LOG_CRIT, "Unknown UID: %u", my_uid));
285                 su_failure (tty);
286         }
287         STRFCPY (oldname, pw->pw_name);
288
289 #ifndef USE_PAM
290 #ifdef SU_ACCESS
291         /*
292          * Sort out the password of user calling su, in case needed later
293          * -- chris
294          */
295 #ifdef SHADOWPWD
296         if ((spwd = getspnam (oldname)))
297                 pw->pw_passwd = spwd->sp_pwdp;
298 #endif
299         oldpass = xstrdup (pw->pw_passwd);
300 #endif                          /* SU_ACCESS */
301
302 #else                           /* USE_PAM */
303         ret = pam_start ("su", name, &conv, &pamh);
304         if (ret != PAM_SUCCESS) {
305                 SYSLOG ((LOG_ERR, "pam_start: error %d", ret);
306                         fprintf (stderr, _("%s: pam_start: error %d\n"),
307                                  Prog, ret));
308                 exit (1);
309         }
310
311         ret = pam_set_item (pamh, PAM_TTY, (const void *) tty);
312         if (ret == PAM_SUCCESS)
313                 ret =
314                     pam_set_item (pamh, PAM_RUSER, (const void *) oldname);
315         if (ret != PAM_SUCCESS) {
316                 SYSLOG ((LOG_ERR, "pam_set_item: %s",
317                          pam_strerror (pamh, ret)));
318                 fprintf (stderr, "%s: %s\n", Prog,
319                          pam_strerror (pamh, ret));
320                 pam_end (pamh, ret);
321                 exit (1);
322         }
323 #endif                          /* USE_PAM */
324
325       top:
326         /*
327          * This is the common point for validating a user whose name is
328          * known. It will be reached either by normal processing, or if the
329          * user is to be logged into a subsystem root.
330          *
331          * The password file entries for the user is gotten and the account
332          * validated.
333          */
334
335         if (!(pw = getpwnam (name))) {
336                 (void) fprintf (stderr, _("Unknown id: %s\n"), name);
337                 closelog ();
338                 exit (1);
339         }
340 #ifndef USE_PAM
341 #ifdef SHADOWPWD
342         spwd = NULL;
343         if (strcmp (pw->pw_passwd, SHADOW_PASSWD_STRING) == 0
344             && (spwd = getspnam (name)))
345                 pw->pw_passwd = spwd->sp_pwdp;
346 #endif
347 #endif                          /* !USE_PAM */
348         pwent = *pw;
349
350 #ifndef USE_PAM
351         /*
352          * BSD systems only allow "wheel" to SU to root. USG systems don't,
353          * so we make this a configurable option.
354          */
355
356         /* The original Shadow 3.3.2 did this differently. Do it like BSD:
357          *
358          * - check for uid 0 instead of name "root" - there are systems with
359          *   several root accounts under different names,
360          *
361          * - check the contents of /etc/group instead of the current group
362          *   set (you must be listed as a member, GID 0 is not sufficient).
363          *
364          * In addition to this traditional feature, we now have complete su
365          * access control (allow, deny, no password, own password).  Thanks
366          * to Chris Evans <lady0110@sable.ox.ac.uk>.
367          */
368
369         if (!amroot) {
370                 if (pwent.pw_uid == 0 && getdef_bool ("SU_WHEEL_ONLY")
371                     && !iswheel (oldname)) {
372                         fprintf (stderr,
373                                  _("You are not authorized to su %s\n"),
374                                  name);
375                         exit (1);
376                 }
377 #ifdef SU_ACCESS
378                 switch (check_su_auth (oldname, name)) {
379                 case 0: /* normal su, require target user's password */
380                         break;
381                 case 1: /* require no password */
382                         pwent.pw_passwd = "";   /* XXX warning: const */
383                         break;
384                 case 2: /* require own password */
385                         puts (_("(Enter your own password.)"));
386                         pwent.pw_passwd = oldpass;
387                         break;
388                 default:        /* access denied (-1) or unexpected value */
389                         fprintf (stderr,
390                                  _("You are not authorized to su %s\n"),
391                                  name);
392                         exit (1);
393                 }
394 #endif                          /* SU_ACCESS */
395         }
396 #endif                          /* !USE_PAM */
397
398         /*
399          * Set the default shell.
400          */
401
402         if (pwent.pw_shell[0] == '\0')
403                 pwent.pw_shell = "/bin/sh";     /* XXX warning: const */
404
405 #ifdef USE_PAM
406         ret = pam_authenticate (pamh, 0);
407         if (ret != PAM_SUCCESS) {
408                 SYSLOG ((LOG_ERR, "pam_authenticate: %s",
409                          pam_strerror (pamh, ret)));
410                 fprintf (stderr, "%s: %s\n", Prog,
411                          pam_strerror (pamh, ret));
412                 pam_end (pamh, ret);
413                 su_failure (tty);
414         }
415
416         ret = pam_acct_mgmt (pamh, 0);
417         if (ret != PAM_SUCCESS) {
418                 if (amroot) {
419                         fprintf (stderr, _("%s: %s\n(Ignored)\n"), Prog,
420                                  pam_strerror (pamh, ret));
421                 } else {
422                         SYSLOG ((LOG_ERR, "pam_acct_mgmt: %s",
423                                  pam_strerror (pamh, ret)));
424                         fprintf (stderr, "%s: %s\n", Prog,
425                                  pam_strerror (pamh, ret));
426                         pam_end (pamh, ret);
427                         su_failure (tty);
428                 }
429         }
430 #else                           /* !USE_PAM */
431         /*
432          * Set up a signal handler in case the user types QUIT.
433          */
434
435         die (0);
436         oldsig = signal (SIGQUIT, die);
437
438         /*
439          * See if the system defined authentication method is being used. 
440          * The first character of an administrator defined method is an '@'
441          * character.
442          */
443
444         if (!amroot && pw_auth (pwent.pw_passwd, name, PW_SU, (char *) 0)) {
445                 SYSLOG ((pwent.pw_uid ? LOG_NOTICE : LOG_WARN,
446                          "Authentication failed for %s", name));
447                 su_failure (tty);
448         }
449         signal (SIGQUIT, oldsig);
450
451         /*
452          * Check to see if the account is expired. root gets to ignore any
453          * expired accounts, but normal users can't become a user with an
454          * expired password.
455          */
456
457         if (!amroot) {
458 #ifdef  SHADOWPWD
459                 if (!spwd)
460                         spwd = pwd_to_spwd (&pwent);
461
462                 if (isexpired (&pwent, spwd)) {
463                         SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
464                                  "Expired account %s", name));
465                         su_failure (tty);
466                 }
467 #else
468 #if defined(ATT_AGE)
469                 if (pwent.pw_age[0] && isexpired (&pwent)) {
470                         SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
471                                  "Expired account %s", name));
472                         su_failure (tty);
473                 }
474 #endif                          /* ATT_AGE */
475 #endif
476         }
477
478         /*
479          * Check to see if the account permits "su". root gets to ignore any
480          * restricted accounts, but normal users can't become a user if
481          * there is a "SU" entry in the /etc/porttime file denying access to
482          * the account.
483          */
484
485         if (!amroot) {
486                 if (!isttytime (pwent.pw_name, "SU", time ((time_t *) 0))) {
487                         SYSLOG ((pwent.pw_uid ? LOG_WARN : LOG_CRIT,
488                                  "SU by %s to restricted account %s",
489                                  oldname, name));
490                         su_failure (tty);
491                 }
492         }
493 #endif                          /* !USE_PAM */
494
495         signal (SIGINT, SIG_DFL);
496         cp = getdef_str ((pwent.pw_uid == 0) ? "ENV_SUPATH" : "ENV_PATH");
497 #if 0
498         addenv (cp ? cp : "PATH=/bin:/usr/bin", NULL);
499 #else
500         /* XXX very similar code duplicated in libmisc/setupenv.c */
501         if (!cp) {
502                 addenv ("PATH=/bin:/usr/bin", NULL);
503         } else if (strchr (cp, '=')) {
504                 addenv (cp, NULL);
505         } else {
506                 addenv ("PATH", cp);
507         }
508 #endif
509
510         environ = newenvp;      /* make new environment active */
511
512         if (getenv ("IFS"))     /* don't export user IFS ... */
513                 addenv ("IFS= \t\n", NULL);     /* ... instead, set a safe IFS */
514
515         if (pwent.pw_shell[0] == '*') { /* subsystem root required */
516                 pwent.pw_shell++;       /* skip the '*' */
517                 subsystem (&pwent);     /* figure out what to execute */
518                 endpwent ();
519 #ifdef SHADOWPWD
520                 endspent ();
521 #endif
522                 goto top;
523         }
524
525         sulog (tty, 1, oldname, name);  /* save SU information */
526         endpwent ();
527 #ifdef SHADOWPWD
528         endspent ();
529 #endif
530 #ifdef USE_SYSLOG
531         if (getdef_bool ("SYSLOG_SU_ENAB"))
532                 SYSLOG ((LOG_INFO, "+ %s %s-%s", tty,
533                          oldname[0] ? oldname : "???",
534                          name[0] ? name : "???"));
535 #endif
536
537 #ifdef USE_PAM
538         /* set primary group id and supplementary groups */
539         if (setup_groups (&pwent)) {
540                 pam_end (pamh, PAM_ABORT);
541                 exit (1);
542         }
543
544         /*
545          * pam_setcred() may do things like resource limits, console groups,
546          * and much more, depending on the configured modules
547          */
548         ret = pam_setcred (pamh, PAM_ESTABLISH_CRED);
549         if (ret != PAM_SUCCESS) {
550                 SYSLOG ((LOG_ERR, "pam_setcred: %s",
551                          pam_strerror (pamh, ret)));
552                 fprintf (stderr, "%s: %s\n", Prog,
553                          pam_strerror (pamh, ret));
554                 pam_end (pamh, ret);
555                 exit (1);
556         }
557
558         /* become the new user */
559         if (change_uid (&pwent)) {
560                 pam_setcred (pamh, PAM_DELETE_CRED);
561                 pam_end (pamh, PAM_ABORT);
562                 exit (1);
563         }
564
565         /* now we are done using PAM */
566         pam_end (pamh, PAM_SUCCESS);
567
568 #else                           /* !USE_PAM */
569         if (!amroot)            /* no limits if su from root */
570                 setup_limits (&pwent);
571
572         if (setup_uid_gid (&pwent, is_console))
573                 exit (1);
574 #endif                          /* !USE_PAM */
575
576         if (fakelogin)
577                 setup_env (&pwent);
578 #if 1                           /* Suggested by Joey Hess. XXX - is this right?  */
579         else
580                 addenv ("HOME", pwent.pw_dir);
581 #endif
582
583         /*
584          * This is a workaround for Linux libc bug/feature (?) - the
585          * /dev/log file descriptor is open without the close-on-exec flag
586          * and used to be passed to the new shell. There is "fcntl(LogFile,
587          * F_SETFD, 1)" in libc/misc/syslog.c, but it is commented out (at
588          * least in 5.4.33). Why?  --marekm
589          */
590         closelog ();
591
592         /*
593          * See if the user has extra arguments on the command line. In that
594          * case they will be provided to the new user's shell as arguments.
595          */
596
597         if (fakelogin) {
598                 char *arg0;
599
600 #if 0                           /* XXX - GNU su doesn't do this.  --marekm */
601                 if (!hushed (&pwent)) {
602                         motd ();
603                         mailcheck ();
604                 }
605 #endif
606                 cp = getdef_str ("SU_NAME");
607                 if (!cp)
608                         cp = Basename (pwent.pw_shell);
609
610                 arg0 = xmalloc (strlen (cp) + 2);
611                 arg0[0] = '-';
612                 strcpy (arg0 + 1, cp);
613                 cp = arg0;
614         } else
615                 cp = Basename (pwent.pw_shell);
616
617         if (!doshell) {
618
619                 /*
620                  * Use new user's shell from /etc/passwd and create an argv
621                  * with the rest of the command line included.
622                  */
623
624                 argv[-1] = pwent.pw_shell;
625                 (void) execv (pwent.pw_shell, &argv[-1]);
626                 (void) fprintf (stderr, _("No shell\n"));
627                 SYSLOG ((LOG_WARN, "Cannot execute %s", pwent.pw_shell));
628                 closelog ();
629                 exit (1);
630         }
631
632         shell (pwent.pw_shell, cp);
633          /*NOTREACHED*/ exit (1);
634 }