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