.
[platform/upstream/coreutils.git] / src / su.c
1 /* su for GNU.  Run a shell with substitute user and group IDs.
2    Copyright (C) 92, 93, 1994 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* Run a shell with the real and effective UID and GID and groups
19    of USER, default `root'.
20
21    The shell run is taken from USER's password entry, /bin/sh if
22    none is specified there.  If the account has a password, su
23    prompts for a password unless run by a user with real UID 0.
24
25    Does not change the current directory.
26    Sets `HOME' and `SHELL' from the password entry for USER, and if
27    USER is not root, sets `USER' and `LOGNAME' to USER.
28    The subshell is not a login shell.
29
30    If one or more ARGs are given, they are passed as additional
31    arguments to the subshell.
32
33    Does not handle /bin/sh or other shells specially
34    (setting argv[0] to "-su", passing -c only to certain shells, etc.).
35    I don't see the point in doing that, and it's ugly.
36
37    This program intentionally does not support a "wheel group" that
38    restricts who can su to UID 0 accounts.  RMS considers that to
39    be fascist.
40
41    Options:
42    -, -l, --login       Make the subshell a login shell.
43                         Unset all environment variables except
44                         TERM, HOME and SHELL (set as above), and USER
45                         and LOGNAME (set unconditionally as above), and
46                         set PATH to a default value.
47                         Change to USER's home directory.
48                         Prepend "-" to the shell's name.
49    -c, --commmand=COMMAND
50                         Pass COMMAND to the subshell with a -c option
51                         instead of starting an interactive shell.
52    -f, --fast           Pass the -f option to the subshell.
53    -m, -p, --preserve-environment
54                         Do not change HOME, USER, LOGNAME, SHELL.
55                         Run $SHELL instead of USER's shell from /etc/passwd
56                         unless not the superuser and USER's shell is
57                         restricted.
58                         Overridden by --login and --shell.
59    -s, --shell=shell    Run SHELL instead of USER's shell from /etc/passwd
60                         unless not the superuser and USER's shell is
61                         restricted.
62
63    Compile-time options:
64    -DSYSLOG_SUCCESS     Log successful su's (by default, to root) with syslog.
65    -DSYSLOG_FAILURE     Log failed su's (by default, to root) with syslog.
66
67    -DSYSLOG_NON_ROOT    Log all su's, not just those to root (UID 0).
68    Never logs attempted su's to nonexistent accounts.
69
70    Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
71
72 #ifdef HAVE_CONFIG_H
73 #if defined (CONFIG_BROKETS)
74 /* We use <config.h> instead of "config.h" so that a compilation
75    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
76    (which it would do because it found this file in $srcdir).  */
77 #include <config.h>
78 #else
79 #include "config.h"
80 #endif
81 #endif
82
83 #include <stdio.h>
84 #include <getopt.h>
85 #include <sys/types.h>
86 #include <pwd.h>
87 #include <grp.h>
88 #include "system.h"
89
90 #ifdef HAVE_SYSLOG_H
91 #include <syslog.h>
92 static void log_su ();
93 #else /* !HAVE_SYSLOG_H */
94 #ifdef SYSLOG_SUCCESS
95 #undef SYSLOG_SUCCESS
96 #endif
97 #ifdef SYSLOG_FAILURE
98 #undef SYSLOG_FAILURE
99 #endif
100 #ifdef SYSLOG_NON_ROOT
101 #undef SYSLOG_NON_ROOT
102 #endif
103 #endif /* !HAVE_SYSLOG_H */
104
105 #ifdef _POSIX_VERSION
106 #include <limits.h>
107 #ifdef NGROUPS_MAX
108 #undef NGROUPS_MAX
109 #endif
110 #define NGROUPS_MAX sysconf (_SC_NGROUPS_MAX)
111 #else /* not _POSIX_VERSION */
112 struct passwd *getpwuid ();
113 struct group *getgrgid ();
114 uid_t getuid ();
115 #include <sys/param.h>
116 #if !defined(NGROUPS_MAX) && defined(NGROUPS)
117 #define NGROUPS_MAX NGROUPS
118 #endif
119 #endif /* not _POSIX_VERSION */
120
121 #ifdef _POSIX_SOURCE
122 #define endgrent()
123 #define endpwent()
124 #endif
125
126 #ifdef HAVE_SHADOW_H
127 #include <shadow.h>
128 #endif
129
130 #include "version.h"
131
132 /* The default PATH for simulated logins to non-superuser accounts.  */
133 #define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
134
135 /* The default PATH for simulated logins to superuser accounts.  */
136 #define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
137
138 /* The shell to run if none is given in the user's passwd entry.  */
139 #define DEFAULT_SHELL "/bin/sh"
140
141 /* The user to become if none is specified.  */
142 #define DEFAULT_USER "root"
143
144 char *crypt ();
145 char *getpass ();
146 char *getusershell ();
147 void endusershell ();
148 void setusershell ();
149
150 char *basename ();
151 char *xmalloc ();
152 char *xrealloc ();
153 char *xstrdup ();
154 void error ();
155
156 static char *concat ();
157 static int correct_password ();
158 static int elements ();
159 static int restricted_shell ();
160 static void change_identity ();
161 static void modify_environment ();
162 static void run_shell ();
163 static void usage ();
164 static void xputenv ();
165
166 extern char **environ;
167
168 /* The name this program was run with.  */
169 char *program_name;
170
171 /* If non-zero, display usage information and exit.  */
172 static int show_help;
173
174 /* If non-zero, print the version on standard output and exit.  */
175 static int show_version;
176
177 /* If nonzero, pass the `-f' option to the subshell.  */
178 static int fast_startup;
179
180 /* If nonzero, simulate a login instead of just starting a shell.  */
181 static int simulate_login;
182
183 /* If nonzero, change some environment vars to indicate the user su'd to.  */
184 static int change_environment;
185
186 static struct option const longopts[] =
187 {
188   {"command", required_argument, 0, 'c'},
189   {"fast", no_argument, &fast_startup, 1},
190   {"help", no_argument, &show_help, 1},
191   {"login", no_argument, &simulate_login, 1},
192   {"preserve-environment", no_argument, &change_environment, 0},
193   {"shell", required_argument, 0, 's'},
194   {"version", no_argument, &show_version, 1},
195   {0, 0, 0, 0}
196 };
197
198 void
199 main (argc, argv)
200      int argc;
201      char **argv;
202 {
203   int optc;
204   char *new_user = DEFAULT_USER;
205   char *command = 0;
206   char **additional_args = 0;
207   char *shell = 0;
208   struct passwd *pw;
209   struct passwd pw_copy;
210
211   program_name = argv[0];
212   fast_startup = 0;
213   simulate_login = 0;
214   change_environment = 1;
215
216   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
217          != EOF)
218     {
219       switch (optc)
220         {
221         case 0:
222           break;
223
224         case 'c':
225           command = optarg;
226           break;
227
228         case 'f':
229           fast_startup = 1;
230           break;
231
232         case 'l':
233           simulate_login = 1;
234           break;
235
236         case 'm':
237         case 'p':
238           change_environment = 0;
239           break;
240
241         case 's':
242           shell = optarg;
243           break;
244
245         default:
246           usage (1);
247         }
248     }
249
250   if (show_version)
251     {
252       printf ("su - %s\n", version_string);
253       exit (0);
254     }
255
256   if (show_help)
257     usage (0);
258
259   if (optind < argc && !strcmp (argv[optind], "-"))
260     {
261       simulate_login = 1;
262       ++optind;
263     }
264   if (optind < argc)
265     new_user = argv[optind++];
266   if (optind < argc)
267     additional_args = argv + optind;
268
269   pw = getpwnam (new_user);
270   if (pw == 0)
271     error (1, 0, "user %s does not exist", new_user);
272   endpwent ();
273
274   /* Make a copy of the password information and point pw at the local
275      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
276      the static data through the getlogin call from log_su.  */
277   pw_copy = *pw;
278   pw = &pw_copy;
279   pw->pw_name = xstrdup (pw->pw_name);
280   pw->pw_dir = xstrdup (pw->pw_dir);
281   pw->pw_shell = xstrdup (pw->pw_shell);
282
283   if (!correct_password (pw))
284     {
285 #ifdef SYSLOG_FAILURE
286       log_su (pw, 0);
287 #endif
288       error (1, 0, "incorrect password");
289     }
290 #ifdef SYSLOG_SUCCESS
291   else
292     {
293       log_su (pw, 1);
294     }
295 #endif
296
297   if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
298     pw->pw_shell = DEFAULT_SHELL;
299   if (shell == 0 && change_environment == 0)
300     shell = getenv ("SHELL");
301   if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
302     {
303       /* The user being su'd to has a nonstandard shell, and so is
304          probably a uucp account or has restricted access.  Don't
305          compromise the account by allowing access with a standard
306          shell.  */
307       error (0, 0, "using restricted shell %s", pw->pw_shell);
308       shell = 0;
309     }
310   if (shell == 0)
311     {
312       shell = xstrdup (pw->pw_shell);
313     }
314   modify_environment (pw, shell);
315
316   change_identity (pw);
317   if (simulate_login && chdir (pw->pw_dir))
318     error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
319
320   free (pw->pw_name);
321   free (pw->pw_dir);
322   free (pw->pw_shell);
323
324   run_shell (shell, command, additional_args);
325 }
326
327 /* Ask the user for a password.
328    Return 1 if the user gives the correct password for entry PW,
329    0 if not.  Return 1 without asking for a password if run by UID 0
330    or if PW has an empty password.  */
331
332 static int
333 correct_password (pw)
334      struct passwd *pw;
335 {
336   char *unencrypted, *encrypted, *correct;
337 #ifdef HAVE_SHADOW_H
338   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
339   struct spwd *sp = getspnam (pw->pw_name);
340
341   endspent ();
342   if (sp)
343     correct = sp->sp_pwdp;
344   else
345 #endif
346   correct = pw->pw_passwd;
347
348   if (getuid () == 0 || correct == 0 || correct[0] == '\0')
349     return 1;
350
351   unencrypted = getpass ("Password:");
352   if (unencrypted == NULL)
353     {
354       error (0, 0, "getpass: cannot open /dev/tty");
355       return 0;
356     }
357   encrypted = crypt (unencrypted, correct);
358   bzero (unencrypted, strlen (unencrypted));
359   return strcmp (encrypted, correct) == 0;
360 }
361
362 /* Update `environ' for the new shell based on PW, with SHELL being
363    the value for the SHELL environment variable.  */
364
365 static void
366 modify_environment (pw, shell)
367      struct passwd *pw;
368      char *shell;
369 {
370   char *term;
371
372   if (simulate_login)
373     {
374       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
375          Unset all other environment variables.  */
376       term = getenv ("TERM");
377       environ = (char **) xmalloc (2 * sizeof (char *));
378       environ[0] = 0;
379       if (term)
380         xputenv (concat ("TERM", "=", term));
381       xputenv (concat ("HOME", "=", pw->pw_dir));
382       xputenv (concat ("SHELL", "=", shell));
383       xputenv (concat ("USER", "=", pw->pw_name));
384       xputenv (concat ("LOGNAME", "=", pw->pw_name));
385       xputenv (concat ("PATH", "=", pw->pw_uid
386                        ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
387     }
388   else
389     {
390       /* Set HOME, SHELL, and if not becoming a super-user,
391          USER and LOGNAME.  */
392       if (change_environment)
393         {
394           xputenv (concat ("HOME", "=", pw->pw_dir));
395           xputenv (concat ("SHELL", "=", shell));
396           if (pw->pw_uid)
397             {
398               xputenv (concat ("USER", "=", pw->pw_name));
399               xputenv (concat ("LOGNAME", "=", pw->pw_name));
400             }
401         }
402     }
403 }
404
405 /* Become the user and group(s) specified by PW.  */
406
407 static void
408 change_identity (pw)
409      struct passwd *pw;
410 {
411 #ifdef NGROUPS_MAX
412   errno = 0;
413   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
414     error (1, errno, "cannot set groups");
415   endgrent ();
416 #endif
417   if (setgid (pw->pw_gid))
418     error (1, errno, "cannot set group id");
419   if (setuid (pw->pw_uid))
420     error (1, errno, "cannot set user id");
421 }
422
423 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
424    If COMMAND is nonzero, pass it to the shell with the -c option.
425    If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
426    arguments.  */
427
428 static void
429 run_shell (shell, command, additional_args)
430      char *shell;
431      char *command;
432      char **additional_args;
433 {
434   char **args;
435   int argno = 1;
436
437   if (additional_args)
438     args = (char **) xmalloc (sizeof (char *)
439                               * (10 + elements (additional_args)));
440   else
441     args = (char **) xmalloc (sizeof (char *) * 10);
442   if (simulate_login)
443     {
444       args[0] = xmalloc (strlen (shell) + 2);
445       args[0][0] = '-';
446       strcpy (args[0] + 1, basename (shell));
447     }
448   else
449     args[0] = basename (shell);
450   if (fast_startup)
451     args[argno++] = "-f";
452   if (command)
453     {
454       args[argno++] = "-c";
455       args[argno++] = command;
456     }
457   if (additional_args)
458     for (; *additional_args; ++additional_args)
459       args[argno++] = *additional_args;
460   args[argno] = NULL;
461   execv (shell, args);
462   error (1, errno, "cannot run %s", shell);
463 }
464
465 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
466 /* Log the fact that someone has run su to the user given by PW;
467    if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
468
469 static void
470 log_su (pw, successful)
471      struct passwd *pw;
472      int successful;
473 {
474   char *new_user, *old_user, *tty;
475
476 #ifndef SYSLOG_NON_ROOT
477   if (pw->pw_uid)
478     return;
479 #endif
480   new_user = pw->pw_name;
481   /* The utmp entry (via getlogin) is probably the best way to identify
482      the user, especially if someone su's from a su-shell.  */
483   old_user = getlogin ();
484   if (old_user == NULL)
485     old_user = "";
486   tty = ttyname (2);
487   if (tty == NULL)
488     tty = "";
489   /* 4.2BSD openlog doesn't have the third parameter.  */
490   openlog (basename (program_name), 0
491 #ifdef LOG_AUTH
492            , LOG_AUTH
493 #endif
494            );
495   syslog (LOG_NOTICE,
496 #ifdef SYSLOG_NON_ROOT
497           "%s(to %s) %s on %s",
498 #else
499           "%s%s on %s",
500 #endif
501           successful ? "" : "FAILED SU ",
502 #ifdef SYSLOG_NON_ROOT
503           new_user,
504 #endif
505           old_user, tty);
506   closelog ();
507 }
508 #endif
509
510 /* Return 1 if SHELL is a restricted shell (one not returned by
511    getusershell), else 0, meaning it is a standard shell.  */
512
513 static int
514 restricted_shell (shell)
515      char *shell;
516 {
517   char *line;
518
519   setusershell ();
520   while ((line = getusershell ()) != NULL)
521     {
522       if (*line != '#' && strcmp (line, shell) == 0)
523         {
524           endusershell ();
525           return 0;
526         }
527     }
528   endusershell ();
529   return 1;
530 }
531
532 /* Return the number of elements in ARR, a null-terminated array.  */
533
534 static int
535 elements (arr)
536      char **arr;
537 {
538   int n = 0;
539
540   for (n = 0; *arr; ++arr)
541     ++n;
542   return n;
543 }
544
545 /* Add VAL to the environment, checking for out of memory errors.  */
546
547 static void
548 xputenv (val)
549      char *val;
550 {
551   if (putenv (val))
552     error (1, 0, "virtual memory exhausted");
553 }
554
555 /* Return a newly-allocated string whose contents concatenate
556    those of S1, S2, S3.  */
557
558 static char *
559 concat (s1, s2, s3)
560      char *s1, *s2, *s3;
561 {
562   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
563   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
564
565   strcpy (result, s1);
566   strcpy (result + len1, s2);
567   strcpy (result + len1 + len2, s3);
568   result[len1 + len2 + len3] = 0;
569
570   return result;
571 }
572
573 static void
574 usage (status)
575      int status;
576 {
577   if (status != 0)
578     fprintf (stderr, "Try `%s --help' for more information.\n",
579              program_name);
580   else
581     {
582       printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
583       printf ("\
584 \n\
585   -l, --login                  make the shell a login shell\n\
586   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
587   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
588   -m, --preserve-environment   do not reset environment variables\n\
589   -p                           same as -m\n\
590   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
591       --help                   display this help and exit\n\
592       --version                output version information and exit\n\
593 \n\
594 A mere - implies -l.   If USER not given, assume root.\n\
595 ");
596     }
597   exit (status);
598 }