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