.
[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       /* FIXME: Using malloc (through xstrdup) to allocate this space
313          is a minor memory leak.  Consider using alloca instead.  */
314       shell = xstrdup (pw->pw_shell);
315     }
316   modify_environment (pw, shell);
317
318   change_identity (pw);
319   if (simulate_login && chdir (pw->pw_dir))
320     error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
321
322   free (pw->pw_name);
323   free (pw->pw_dir);
324   free (pw->pw_shell);
325
326   run_shell (shell, command, additional_args);
327 }
328
329 /* Ask the user for a password.
330    Return 1 if the user gives the correct password for entry PW,
331    0 if not.  Return 1 without asking for a password if run by UID 0
332    or if PW has an empty password.  */
333
334 static int
335 correct_password (pw)
336      struct passwd *pw;
337 {
338   char *unencrypted, *encrypted, *correct;
339 #ifdef HAVE_SHADOW_H
340   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
341   struct spwd *sp = getspnam (pw->pw_name);
342
343   endspent ();
344   if (sp)
345     correct = sp->sp_pwdp;
346   else
347 #endif
348   correct = pw->pw_passwd;
349
350   if (getuid () == 0 || correct == 0 || correct[0] == '\0')
351     return 1;
352
353   unencrypted = getpass ("Password:");
354   if (unencrypted == NULL)
355     {
356       error (0, 0, "getpass: cannot open /dev/tty");
357       return 0;
358     }
359   encrypted = crypt (unencrypted, correct);
360   bzero (unencrypted, strlen (unencrypted));
361   return strcmp (encrypted, correct) == 0;
362 }
363
364 /* Update `environ' for the new shell based on PW, with SHELL being
365    the value for the SHELL environment variable.  */
366
367 static void
368 modify_environment (pw, shell)
369      struct passwd *pw;
370      char *shell;
371 {
372   char *term;
373
374   if (simulate_login)
375     {
376       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
377          Unset all other environment variables.  */
378       term = getenv ("TERM");
379       environ = (char **) xmalloc (2 * sizeof (char *));
380       environ[0] = 0;
381       if (term)
382         xputenv (concat ("TERM", "=", term));
383       xputenv (concat ("HOME", "=", pw->pw_dir));
384       xputenv (concat ("SHELL", "=", shell));
385       xputenv (concat ("USER", "=", pw->pw_name));
386       xputenv (concat ("LOGNAME", "=", pw->pw_name));
387       xputenv (concat ("PATH", "=", pw->pw_uid
388                        ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
389     }
390   else
391     {
392       /* Set HOME, SHELL, and if not becoming a super-user,
393          USER and LOGNAME.  */
394       if (change_environment)
395         {
396           xputenv (concat ("HOME", "=", pw->pw_dir));
397           xputenv (concat ("SHELL", "=", shell));
398           if (pw->pw_uid)
399             {
400               xputenv (concat ("USER", "=", pw->pw_name));
401               xputenv (concat ("LOGNAME", "=", pw->pw_name));
402             }
403         }
404     }
405 }
406
407 /* Become the user and group(s) specified by PW.  */
408
409 static void
410 change_identity (pw)
411      struct passwd *pw;
412 {
413 #ifdef NGROUPS_MAX
414   errno = 0;
415   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
416     error (1, errno, "cannot set groups");
417   endgrent ();
418 #endif
419   if (setgid (pw->pw_gid))
420     error (1, errno, "cannot set group id");
421   if (setuid (pw->pw_uid))
422     error (1, errno, "cannot set user id");
423 }
424
425 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
426    If COMMAND is nonzero, pass it to the shell with the -c option.
427    If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
428    arguments.  */
429
430 static void
431 run_shell (shell, command, additional_args)
432      char *shell;
433      char *command;
434      char **additional_args;
435 {
436   char **args;
437   int argno = 1;
438
439   if (additional_args)
440     args = (char **) xmalloc (sizeof (char *)
441                               * (10 + elements (additional_args)));
442   else
443     args = (char **) xmalloc (sizeof (char *) * 10);
444   if (simulate_login)
445     {
446       args[0] = xmalloc (strlen (shell) + 2);
447       args[0][0] = '-';
448       strcpy (args[0] + 1, basename (shell));
449     }
450   else
451     args[0] = basename (shell);
452   if (fast_startup)
453     args[argno++] = "-f";
454   if (command)
455     {
456       args[argno++] = "-c";
457       args[argno++] = command;
458     }
459   if (additional_args)
460     for (; *additional_args; ++additional_args)
461       args[argno++] = *additional_args;
462   args[argno] = NULL;
463   execv (shell, args);
464   error (1, errno, "cannot run %s", shell);
465 }
466
467 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
468 /* Log the fact that someone has run su to the user given by PW;
469    if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
470
471 static void
472 log_su (pw, successful)
473      struct passwd *pw;
474      int successful;
475 {
476   char *new_user, *old_user, *tty;
477
478 #ifndef SYSLOG_NON_ROOT
479   if (pw->pw_uid)
480     return;
481 #endif
482   new_user = pw->pw_name;
483   /* The utmp entry (via getlogin) is probably the best way to identify
484      the user, especially if someone su's from a su-shell.  */
485   old_user = getlogin ();
486   if (old_user == NULL)
487     old_user = "";
488   tty = ttyname (2);
489   if (tty == NULL)
490     tty = "";
491   /* 4.2BSD openlog doesn't have the third parameter.  */
492   openlog (basename (program_name), 0
493 #ifdef LOG_AUTH
494            , LOG_AUTH
495 #endif
496            );
497   syslog (LOG_NOTICE,
498 #ifdef SYSLOG_NON_ROOT
499           "%s(to %s) %s on %s",
500 #else
501           "%s%s on %s",
502 #endif
503           successful ? "" : "FAILED SU ",
504 #ifdef SYSLOG_NON_ROOT
505           new_user,
506 #endif
507           old_user, tty);
508   closelog ();
509 }
510 #endif
511
512 /* Return 1 if SHELL is a restricted shell (one not returned by
513    getusershell), else 0, meaning it is a standard shell.  */
514
515 static int
516 restricted_shell (shell)
517      char *shell;
518 {
519   char *line;
520
521   setusershell ();
522   while ((line = getusershell ()) != NULL)
523     {
524       if (*line != '#' && strcmp (line, shell) == 0)
525         {
526           endusershell ();
527           return 0;
528         }
529     }
530   endusershell ();
531   return 1;
532 }
533
534 /* Return the number of elements in ARR, a null-terminated array.  */
535
536 static int
537 elements (arr)
538      char **arr;
539 {
540   int n = 0;
541
542   for (n = 0; *arr; ++arr)
543     ++n;
544   return n;
545 }
546
547 /* Add VAL to the environment, checking for out of memory errors.  */
548
549 static void
550 xputenv (val)
551      char *val;
552 {
553   if (putenv (val))
554     error (1, 0, "virtual memory exhausted");
555 }
556
557 /* Return a newly-allocated string whose contents concatenate
558    those of S1, S2, S3.  */
559
560 static char *
561 concat (s1, s2, s3)
562      char *s1, *s2, *s3;
563 {
564   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
565   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
566
567   strcpy (result, s1);
568   strcpy (result + len1, s2);
569   strcpy (result + len1 + len2, s3);
570   result[len1 + len2 + len3] = 0;
571
572   return result;
573 }
574
575 static void
576 usage (status)
577      int status;
578 {
579   if (status != 0)
580     fprintf (stderr, "Try `%s --help' for more information.\n",
581              program_name);
582   else
583     {
584       printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
585       printf ("\
586 \n\
587   -l, --login                  make the shell a login shell\n\
588   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
589   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
590   -m, --preserve-environment   do not reset environment variables\n\
591   -p                           same as -m\n\
592   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
593       --help                   display this help and exit\n\
594       --version                output version information and exit\n\
595 \n\
596 A mere - implies -l.   If USER not given, assume root.\n\
597 ");
598     }
599   exit (status);
600 }