.
[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 #ifndef HAVE_ENDGRENT
122 #define endgrent()
123 #endif
124
125 #ifndef HAVE_ENDPWENT
126 #define endpwent()
127 #endif
128
129 #ifdef HAVE_SHADOW_H
130 #include <shadow.h>
131 #endif
132
133 #include "version.h"
134
135 /* The default PATH for simulated logins to non-superuser accounts.  */
136 #define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
137
138 /* The default PATH for simulated logins to superuser accounts.  */
139 #define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
140
141 /* The shell to run if none is given in the user's passwd entry.  */
142 #define DEFAULT_SHELL "/bin/sh"
143
144 /* The user to become if none is specified.  */
145 #define DEFAULT_USER "root"
146
147 char *crypt ();
148 char *getpass ();
149 char *getusershell ();
150 void endusershell ();
151 void setusershell ();
152
153 char *basename ();
154 char *xmalloc ();
155 char *xrealloc ();
156 char *xstrdup ();
157 void error ();
158
159 static char *concat ();
160 static int correct_password ();
161 static int elements ();
162 static int restricted_shell ();
163 static void change_identity ();
164 static void modify_environment ();
165 static void run_shell ();
166 static void usage ();
167 static void xputenv ();
168
169 extern char **environ;
170
171 /* The name this program was run with.  */
172 char *program_name;
173
174 /* If non-zero, display usage information and exit.  */
175 static int show_help;
176
177 /* If non-zero, print the version on standard output and exit.  */
178 static int show_version;
179
180 /* If nonzero, pass the `-f' option to the subshell.  */
181 static int fast_startup;
182
183 /* If nonzero, simulate a login instead of just starting a shell.  */
184 static int simulate_login;
185
186 /* If nonzero, change some environment vars to indicate the user su'd to.  */
187 static int change_environment;
188
189 static struct option const longopts[] =
190 {
191   {"command", required_argument, 0, 'c'},
192   {"fast", no_argument, &fast_startup, 1},
193   {"help", no_argument, &show_help, 1},
194   {"login", no_argument, &simulate_login, 1},
195   {"preserve-environment", no_argument, &change_environment, 0},
196   {"shell", required_argument, 0, 's'},
197   {"version", no_argument, &show_version, 1},
198   {0, 0, 0, 0}
199 };
200
201 void
202 main (argc, argv)
203      int argc;
204      char **argv;
205 {
206   int optc;
207   char *new_user = DEFAULT_USER;
208   char *command = 0;
209   char **additional_args = 0;
210   char *shell = 0;
211   struct passwd *pw;
212   struct passwd pw_copy;
213
214   program_name = argv[0];
215   fast_startup = 0;
216   simulate_login = 0;
217   change_environment = 1;
218
219   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
220          != EOF)
221     {
222       switch (optc)
223         {
224         case 0:
225           break;
226
227         case 'c':
228           command = optarg;
229           break;
230
231         case 'f':
232           fast_startup = 1;
233           break;
234
235         case 'l':
236           simulate_login = 1;
237           break;
238
239         case 'm':
240         case 'p':
241           change_environment = 0;
242           break;
243
244         case 's':
245           shell = optarg;
246           break;
247
248         default:
249           usage (1);
250         }
251     }
252
253   if (show_version)
254     {
255       printf ("su - %s\n", version_string);
256       exit (0);
257     }
258
259   if (show_help)
260     usage (0);
261
262   if (optind < argc && !strcmp (argv[optind], "-"))
263     {
264       simulate_login = 1;
265       ++optind;
266     }
267   if (optind < argc)
268     new_user = argv[optind++];
269   if (optind < argc)
270     additional_args = argv + optind;
271
272   pw = getpwnam (new_user);
273   if (pw == 0)
274     error (1, 0, "user %s does not exist", new_user);
275   endpwent ();
276
277   /* Make a copy of the password information and point pw at the local
278      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
279      the static data through the getlogin call from log_su.  */
280   pw_copy = *pw;
281   pw = &pw_copy;
282   pw->pw_name = xstrdup (pw->pw_name);
283   pw->pw_dir = xstrdup (pw->pw_dir);
284   pw->pw_shell = xstrdup (pw->pw_shell);
285
286   if (!correct_password (pw))
287     {
288 #ifdef SYSLOG_FAILURE
289       log_su (pw, 0);
290 #endif
291       error (1, 0, "incorrect password");
292     }
293 #ifdef SYSLOG_SUCCESS
294   else
295     {
296       log_su (pw, 1);
297     }
298 #endif
299
300   if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
301     pw->pw_shell = DEFAULT_SHELL;
302   if (shell == 0 && change_environment == 0)
303     shell = getenv ("SHELL");
304   if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
305     {
306       /* The user being su'd to has a nonstandard shell, and so is
307          probably a uucp account or has restricted access.  Don't
308          compromise the account by allowing access with a standard
309          shell.  */
310       error (0, 0, "using restricted shell %s", pw->pw_shell);
311       shell = 0;
312     }
313   if (shell == 0)
314     {
315       shell = xstrdup (pw->pw_shell);
316     }
317   modify_environment (pw, shell);
318
319   change_identity (pw);
320   if (simulate_login && chdir (pw->pw_dir))
321     error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
322
323   run_shell (shell, command, additional_args);
324 }
325
326 /* Ask the user for a password.
327    Return 1 if the user gives the correct password for entry PW,
328    0 if not.  Return 1 without asking for a password if run by UID 0
329    or if PW has an empty password.  */
330
331 static int
332 correct_password (pw)
333      struct passwd *pw;
334 {
335   char *unencrypted, *encrypted, *correct;
336 #ifdef HAVE_SHADOW_H
337   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
338   struct spwd *sp = getspnam (pw->pw_name);
339
340   endspent ();
341   if (sp)
342     correct = sp->sp_pwdp;
343   else
344 #endif
345   correct = pw->pw_passwd;
346
347   if (getuid () == 0 || correct == 0 || correct[0] == '\0')
348     return 1;
349
350   unencrypted = getpass ("Password:");
351   if (unencrypted == NULL)
352     {
353       error (0, 0, "getpass: cannot open /dev/tty");
354       return 0;
355     }
356   encrypted = crypt (unencrypted, correct);
357   bzero (unencrypted, strlen (unencrypted));
358   return strcmp (encrypted, correct) == 0;
359 }
360
361 /* Update `environ' for the new shell based on PW, with SHELL being
362    the value for the SHELL environment variable.  */
363
364 static void
365 modify_environment (pw, shell)
366      struct passwd *pw;
367      char *shell;
368 {
369   char *term;
370
371   if (simulate_login)
372     {
373       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
374          Unset all other environment variables.  */
375       term = getenv ("TERM");
376       environ = (char **) xmalloc (2 * sizeof (char *));
377       environ[0] = 0;
378       if (term)
379         xputenv (concat ("TERM", "=", term));
380       xputenv (concat ("HOME", "=", pw->pw_dir));
381       xputenv (concat ("SHELL", "=", shell));
382       xputenv (concat ("USER", "=", pw->pw_name));
383       xputenv (concat ("LOGNAME", "=", pw->pw_name));
384       xputenv (concat ("PATH", "=", pw->pw_uid
385                        ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH));
386     }
387   else
388     {
389       /* Set HOME, SHELL, and if not becoming a super-user,
390          USER and LOGNAME.  */
391       if (change_environment)
392         {
393           xputenv (concat ("HOME", "=", pw->pw_dir));
394           xputenv (concat ("SHELL", "=", shell));
395           if (pw->pw_uid)
396             {
397               xputenv (concat ("USER", "=", pw->pw_name));
398               xputenv (concat ("LOGNAME", "=", pw->pw_name));
399             }
400         }
401     }
402 }
403
404 /* Become the user and group(s) specified by PW.  */
405
406 static void
407 change_identity (pw)
408      struct passwd *pw;
409 {
410 #ifdef NGROUPS_MAX
411   errno = 0;
412   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
413     error (1, errno, "cannot set groups");
414   endgrent ();
415 #endif
416   if (setgid (pw->pw_gid))
417     error (1, errno, "cannot set group id");
418   if (setuid (pw->pw_uid))
419     error (1, errno, "cannot set user id");
420 }
421
422 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
423    If COMMAND is nonzero, pass it to the shell with the -c option.
424    If ADDITIONAL_ARGS is nonzero, pass it to the shell as more
425    arguments.  */
426
427 static void
428 run_shell (shell, command, additional_args)
429      char *shell;
430      char *command;
431      char **additional_args;
432 {
433   char **args;
434   int argno = 1;
435
436   if (additional_args)
437     args = (char **) xmalloc (sizeof (char *)
438                               * (10 + elements (additional_args)));
439   else
440     args = (char **) xmalloc (sizeof (char *) * 10);
441   if (simulate_login)
442     {
443       args[0] = xmalloc (strlen (shell) + 2);
444       args[0][0] = '-';
445       strcpy (args[0] + 1, basename (shell));
446     }
447   else
448     args[0] = basename (shell);
449   if (fast_startup)
450     args[argno++] = "-f";
451   if (command)
452     {
453       args[argno++] = "-c";
454       args[argno++] = command;
455     }
456   if (additional_args)
457     for (; *additional_args; ++additional_args)
458       args[argno++] = *additional_args;
459   args[argno] = NULL;
460   execv (shell, args);
461   error (1, errno, "cannot run %s", shell);
462 }
463
464 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
465 /* Log the fact that someone has run su to the user given by PW;
466    if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
467
468 static void
469 log_su (pw, successful)
470      struct passwd *pw;
471      int successful;
472 {
473   char *new_user, *old_user, *tty;
474
475 #ifndef SYSLOG_NON_ROOT
476   if (pw->pw_uid)
477     return;
478 #endif
479   new_user = pw->pw_name;
480   /* The utmp entry (via getlogin) is probably the best way to identify
481      the user, especially if someone su's from a su-shell.  */
482   old_user = getlogin ();
483   if (old_user == NULL)
484     old_user = "";
485   tty = ttyname (2);
486   if (tty == NULL)
487     tty = "";
488   /* 4.2BSD openlog doesn't have the third parameter.  */
489   openlog (basename (program_name), 0
490 #ifdef LOG_AUTH
491            , LOG_AUTH
492 #endif
493            );
494   syslog (LOG_NOTICE,
495 #ifdef SYSLOG_NON_ROOT
496           "%s(to %s) %s on %s",
497 #else
498           "%s%s on %s",
499 #endif
500           successful ? "" : "FAILED SU ",
501 #ifdef SYSLOG_NON_ROOT
502           new_user,
503 #endif
504           old_user, tty);
505   closelog ();
506 }
507 #endif
508
509 /* Return 1 if SHELL is a restricted shell (one not returned by
510    getusershell), else 0, meaning it is a standard shell.  */
511
512 static int
513 restricted_shell (shell)
514      char *shell;
515 {
516   char *line;
517
518   setusershell ();
519   while ((line = getusershell ()) != NULL)
520     {
521       if (*line != '#' && strcmp (line, shell) == 0)
522         {
523           endusershell ();
524           return 0;
525         }
526     }
527   endusershell ();
528   return 1;
529 }
530
531 /* Return the number of elements in ARR, a null-terminated array.  */
532
533 static int
534 elements (arr)
535      char **arr;
536 {
537   int n = 0;
538
539   for (n = 0; *arr; ++arr)
540     ++n;
541   return n;
542 }
543
544 /* Add VAL to the environment, checking for out of memory errors.  */
545
546 static void
547 xputenv (val)
548      char *val;
549 {
550   if (putenv (val))
551     error (1, 0, "virtual memory exhausted");
552 }
553
554 /* Return a newly-allocated string whose contents concatenate
555    those of S1, S2, S3.  */
556
557 static char *
558 concat (s1, s2, s3)
559      char *s1, *s2, *s3;
560 {
561   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
562   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
563
564   strcpy (result, s1);
565   strcpy (result + len1, s2);
566   strcpy (result + len1 + len2, s3);
567   result[len1 + len2 + len3] = 0;
568
569   return result;
570 }
571
572 static void
573 usage (status)
574      int status;
575 {
576   if (status != 0)
577     fprintf (stderr, "Try `%s --help' for more information.\n",
578              program_name);
579   else
580     {
581       printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
582       printf ("\
583 \n\
584   -l, --login                  make the shell a login shell\n\
585   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
586   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
587   -m, --preserve-environment   do not reset environment variables\n\
588   -p                           same as -m\n\
589   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
590       --help                   display this help and exit\n\
591       --version                output version information and exit\n\
592 \n\
593 A mere - implies -l.   If USER not given, assume root.\n\
594 ");
595     }
596   exit (status);
597 }