.
[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 void error ();
154
155 static char *concat ();
156 static int correct_password ();
157 static int elements ();
158 static int restricted_shell ();
159 static void change_identity ();
160 static void modify_environment ();
161 static void run_shell ();
162 static void usage ();
163 static void xputenv ();
164
165 extern char **environ;
166
167 /* The name this program was run with.  */
168 char *program_name;
169
170 /* If non-zero, display usage information and exit.  */
171 static int show_help;
172
173 /* If non-zero, print the version on standard output and exit.  */
174 static int show_version;
175
176 /* If nonzero, pass the `-f' option to the subshell.  */
177 static int fast_startup;
178
179 /* If nonzero, simulate a login instead of just starting a shell.  */
180 static int simulate_login;
181
182 /* If nonzero, change some environment vars to indicate the user su'd to.  */
183 static int change_environment;
184
185 static struct option const longopts[] =
186 {
187   {"command", required_argument, 0, 'c'},
188   {"fast", no_argument, &fast_startup, 1},
189   {"help", no_argument, &show_help, 1},
190   {"login", no_argument, &simulate_login, 1},
191   {"preserve-environment", no_argument, &change_environment, 0},
192   {"shell", required_argument, 0, 's'},
193   {"version", no_argument, &show_version, 1},
194   {0, 0, 0, 0}
195 };
196
197 void
198 main (argc, argv)
199      int argc;
200      char **argv;
201 {
202   int optc;
203   char *new_user = DEFAULT_USER;
204   char *command = 0;
205   char **additional_args = 0;
206   char *shell = 0;
207   struct passwd *pw;
208
209   program_name = argv[0];
210   fast_startup = 0;
211   simulate_login = 0;
212   change_environment = 1;
213
214   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
215          != EOF)
216     {
217       switch (optc)
218         {
219         case 0:
220           break;
221
222         case 'c':
223           command = optarg;
224           break;
225
226         case 'f':
227           fast_startup = 1;
228           break;
229
230         case 'l':
231           simulate_login = 1;
232           break;
233
234         case 'm':
235         case 'p':
236           change_environment = 0;
237           break;
238
239         case 's':
240           shell = optarg;
241           break;
242
243         default:
244           usage (1);
245         }
246     }
247
248   if (show_version)
249     {
250       printf ("su - %s\n", version_string);
251       exit (0);
252     }
253
254   if (show_help)
255     usage (0);
256
257   if (optind < argc && !strcmp (argv[optind], "-"))
258     {
259       simulate_login = 1;
260       ++optind;
261     }
262   if (optind < argc)
263     new_user = argv[optind++];
264   if (optind < argc)
265     additional_args = argv + optind;
266
267   pw = getpwnam (new_user);
268   if (pw == 0)
269     error (1, 0, "user %s does not exist", new_user);
270   endpwent ();
271   if (!correct_password (pw))
272     {
273 #ifdef SYSLOG_FAILURE
274       log_su (pw, 0);
275 #endif
276       error (1, 0, "incorrect password");
277     }
278 #ifdef SYSLOG_SUCCESS
279   else
280     {
281       log_su (pw, 1);
282     }
283 #endif
284
285   if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
286     pw->pw_shell = DEFAULT_SHELL;
287   if (shell == 0 && change_environment == 0)
288     shell = getenv ("SHELL");
289   if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
290     {
291       /* The user being su'd to has a nonstandard shell, and so is
292          probably a uucp account or has restricted access.  Don't
293          compromise the account by allowing access with a standard
294          shell.  */
295       error (0, 0, "using restricted shell %s", pw->pw_shell);
296       shell = 0;
297     }
298   if (shell == 0)
299     shell = pw->pw_shell;
300   shell = strcpy (xmalloc (strlen (shell) + 1), shell);
301   modify_environment (pw, shell);
302
303   change_identity (pw);
304   if (simulate_login && chdir (pw->pw_dir))
305     error (0, errno, "warning: cannot change directory to %s", pw->pw_dir);
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   bzero (unencrypted, 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 NGROUPS_MAX
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   char **args;
417   int argno = 1;
418
419   if (additional_args)
420     args = (char **) xmalloc (sizeof (char *)
421                               * (10 + elements (additional_args)));
422   else
423     args = (char **) xmalloc (sizeof (char *) * 10);
424   if (simulate_login)
425     {
426       args[0] = xmalloc (strlen (shell) + 2);
427       args[0][0] = '-';
428       strcpy (args[0] + 1, basename (shell));
429     }
430   else
431     args[0] = basename (shell);
432   if (fast_startup)
433     args[argno++] = "-f";
434   if (command)
435     {
436       args[argno++] = "-c";
437       args[argno++] = command;
438     }
439   if (additional_args)
440     for (; *additional_args; ++additional_args)
441       args[argno++] = *additional_args;
442   args[argno] = 0;
443   execv (shell, args);
444   error (1, errno, "cannot run %s", shell);
445 }
446
447 #if defined (SYSLOG_SUCCESS) || defined (SYSLOG_FAILURE)
448 /* Log the fact that someone has run su to the user given by PW;
449    if SUCCESSFUL is nonzero, they gave the correct password, etc.  */
450
451 static void
452 log_su (pw, successful)
453      struct passwd *pw;
454      int successful;
455 {
456   char *new_user, *old_user, *tty;
457
458 #ifndef SYSLOG_NON_ROOT
459   if (pw->pw_uid)
460     return;
461 #endif
462   new_user = pw->pw_name;
463   /* The utmp entry (via getlogin) is probably the best way to identify
464      the user, especially if someone su's from a su-shell.  */
465   old_user = getlogin ();
466   if (old_user == 0)
467     old_user = "";
468   tty = ttyname (2);
469   if (tty == 0)
470     tty = "";
471   /* 4.2BSD openlog doesn't have the third parameter.  */
472   openlog (basename (program_name), 0
473 #ifdef LOG_AUTH
474            , LOG_AUTH
475 #endif
476            );
477   syslog (LOG_NOTICE,
478 #ifdef SYSLOG_NON_ROOT
479           "%s(to %s) %s on %s",
480 #else
481           "%s%s on %s",
482 #endif
483           successful ? "" : "FAILED SU ",
484 #ifdef SYSLOG_NON_ROOT
485           new_user,
486 #endif
487           old_user, tty);
488   closelog ();
489 }
490 #endif
491
492 /* Return 1 if SHELL is a restricted shell (one not returned by
493    getusershell), else 0, meaning it is a standard shell.  */
494
495 static int
496 restricted_shell (shell)
497      char *shell;
498 {
499   char *line;
500
501   setusershell ();
502   while ((line = getusershell ()) != NULL)
503     {
504       if (*line != '#' && strcmp (line, shell) == 0)
505         {
506           endusershell ();
507           return 0;
508         }
509     }
510   endusershell ();
511   return 1;
512 }
513
514 /* Return the number of elements in ARR, a null-terminated array.  */
515
516 static int
517 elements (arr)
518      char **arr;
519 {
520   int n = 0;
521
522   for (n = 0; *arr; ++arr)
523     ++n;
524   return n;
525 }
526
527 /* Add VAL to the environment, checking for out of memory errors.  */
528
529 static void
530 xputenv (val)
531      char *val;
532 {
533   if (putenv (val))
534     error (1, 0, "virtual memory exhausted");
535 }
536
537 /* Return a newly-allocated string whose contents concatenate
538    those of S1, S2, S3.  */
539
540 static char *
541 concat (s1, s2, s3)
542      char *s1, *s2, *s3;
543 {
544   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
545   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
546
547   strcpy (result, s1);
548   strcpy (result + len1, s2);
549   strcpy (result + len1 + len2, s3);
550   result[len1 + len2 + len3] = 0;
551
552   return result;
553 }
554
555 static void
556 usage (status)
557      int status;
558 {
559   if (status != 0)
560     fprintf (stderr, "Try `%s --help' for more information.\n",
561              program_name);
562   else
563     {
564       printf ("Usage: %s [OPTION]... [-] [USER [ARG]...]\n", program_name);
565       printf ("\
566 \n\
567   -l, --login                  make the shell a login shell\n\
568   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
569   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
570   -m, --preserve-environment   do not reset environment variables\n\
571   -p                           same as -m\n\
572   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
573       --help                   display this help and exit\n\
574       --version                output version information and exit\n\
575 \n\
576 A mere - implies -l.   If USER not given, assume root.\n\
577 ");
578     }
579   exit (status);
580 }