1abae322f26e4510fcbaa1549649ec61c0c22855
[platform/upstream/coreutils.git] / src / su.c
1 /* su for GNU.  Run a shell with substitute user and group IDs.
2    Copyright (C) 1992-2005 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 Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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
79 /* Hide any system prototype for getusershell.
80    This is necessary because some Cray systems have a conflicting
81    prototype (returning `int') in <unistd.h>.  */
82 #define getusershell _getusershell_sys_proto_
83
84 #include "system.h"
85 #include "dirname.h"
86
87 #undef getusershell
88
89 #if HAVE_SYSLOG_H && HAVE_SYSLOG
90 # include <syslog.h>
91 #else
92 # undef SYSLOG_SUCCESS
93 # undef SYSLOG_FAILURE
94 # undef SYSLOG_NON_ROOT
95 #endif
96
97 #if HAVE_SYS_PARAM_H
98 # include <sys/param.h>
99 #endif
100
101 #ifndef HAVE_ENDGRENT
102 # define endgrent() ((void) 0)
103 #endif
104
105 #ifndef HAVE_ENDPWENT
106 # define endpwent() ((void) 0)
107 #endif
108
109 #if HAVE_SHADOW_H
110 # include <shadow.h>
111 #endif
112
113 #include "error.h"
114
115 /* The official name of this program (e.g., no `g' prefix).  */
116 #define PROGRAM_NAME "su"
117
118 #define AUTHORS "David MacKenzie"
119
120 #if HAVE_PATHS_H
121 # include <paths.h>
122 #endif
123
124 /* The default PATH for simulated logins to non-superuser accounts.  */
125 #ifdef _PATH_DEFPATH
126 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
127 #else
128 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
129 #endif
130
131 /* The default PATH for simulated logins to superuser accounts.  */
132 #ifdef _PATH_DEFPATH_ROOT
133 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
134 #else
135 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
136 #endif
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 extern char **environ;
151
152 static void run_shell (char const *, char const *, char **, size_t)
153      ATTRIBUTE_NORETURN;
154
155 /* The name this program was run with.  */
156 char *program_name;
157
158 /* If true, pass the `-f' option to the subshell.  */
159 static bool fast_startup;
160
161 /* If true, simulate a login instead of just starting a shell.  */
162 static bool simulate_login;
163
164 /* If true, change some environment vars to indicate the user su'd to.  */
165 static bool change_environment;
166
167 static struct option const longopts[] =
168 {
169   {"command", required_argument, NULL, 'c'},
170   {"fast", no_argument, NULL, 'f'},
171   {"login", no_argument, NULL, 'l'},
172   {"preserve-environment", no_argument, NULL, 'p'},
173   {"shell", required_argument, NULL, 's'},
174   {GETOPT_HELP_OPTION_DECL},
175   {GETOPT_VERSION_OPTION_DECL},
176   {NULL, 0, NULL, 0}
177 };
178
179 /* Add NAME=VAL to the environment, checking for out of memory errors.  */
180
181 static void
182 xsetenv (char const *name, char const *val)
183 {
184   size_t namelen = strlen (name);
185   size_t vallen = strlen (val);
186   char *string = xmalloc (namelen + 1 + vallen + 1);
187   strcpy (string, name);
188   string[namelen] = '=';
189   strcpy (string + namelen + 1, val);
190   if (putenv (string) != 0)
191     xalloc_die ();
192 }
193
194 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
195 /* Log the fact that someone has run su to the user given by PW;
196    if SUCCESSFUL is true, they gave the correct password, etc.  */
197
198 static void
199 log_su (struct passwd const *pw, bool successful)
200 {
201   const char *new_user, *old_user, *tty;
202
203 # ifndef SYSLOG_NON_ROOT
204   if (pw->pw_uid)
205     return;
206 # endif
207   new_user = pw->pw_name;
208   /* The utmp entry (via getlogin) is probably the best way to identify
209      the user, especially if someone su's from a su-shell.  */
210   old_user = getlogin ();
211   if (!old_user)
212     {
213       /* getlogin can fail -- usually due to lack of utmp entry.
214          Resort to getpwuid.  */
215       struct passwd *pwd = getpwuid (getuid ());
216       old_user = (pwd ? pwd->pw_name : "");
217     }
218   tty = ttyname (STDERR_FILENO);
219   if (!tty)
220     tty = "none";
221   /* 4.2BSD openlog doesn't have the third parameter.  */
222   openlog (base_name (program_name), 0
223 # ifdef LOG_AUTH
224            , LOG_AUTH
225 # endif
226            );
227   syslog (LOG_NOTICE,
228 # ifdef SYSLOG_NON_ROOT
229           "%s(to %s) %s on %s",
230 # else
231           "%s%s on %s",
232 # endif
233           successful ? "" : "FAILED SU ",
234 # ifdef SYSLOG_NON_ROOT
235           new_user,
236 # endif
237           old_user, tty);
238   closelog ();
239 }
240 #endif
241
242 /* Ask the user for a password.
243    Return true if the user gives the correct password for entry PW,
244    false if not.  Return true without asking for a password if run by UID 0
245    or if PW has an empty password.  */
246
247 static bool
248 correct_password (const struct passwd *pw)
249 {
250   char *unencrypted, *encrypted, *correct;
251 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
252   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
253   struct spwd *sp = getspnam (pw->pw_name);
254
255   endspent ();
256   if (sp)
257     correct = sp->sp_pwdp;
258   else
259 #endif
260     correct = pw->pw_passwd;
261
262   if (getuid () == 0 || !correct || correct[0] == '\0')
263     return true;
264
265   unencrypted = getpass (_("Password:"));
266   if (!unencrypted)
267     {
268       error (0, 0, _("getpass: cannot open /dev/tty"));
269       return false;
270     }
271   encrypted = crypt (unencrypted, correct);
272   memset (unencrypted, 0, strlen (unencrypted));
273   return STREQ (encrypted, correct);
274 }
275
276 /* Update `environ' for the new shell based on PW, with SHELL being
277    the value for the SHELL environment variable.  */
278
279 static void
280 modify_environment (const struct passwd *pw, const char *shell)
281 {
282   if (simulate_login)
283     {
284       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
285          Unset all other environment variables.  */
286       char const *term = getenv ("TERM");
287       if (term)
288         term = xstrdup (term);
289       environ = xmalloc ((6 + !!term) * sizeof (char *));
290       environ[0] = NULL;
291       if (term)
292         xsetenv ("TERM", term);
293       xsetenv ("HOME", pw->pw_dir);
294       xsetenv ("SHELL", shell);
295       xsetenv ("USER", pw->pw_name);
296       xsetenv ("LOGNAME", pw->pw_name);
297       xsetenv ("PATH", (pw->pw_uid
298                         ? DEFAULT_LOGIN_PATH
299                         : DEFAULT_ROOT_LOGIN_PATH));
300     }
301   else
302     {
303       /* Set HOME, SHELL, and if not becoming a super-user,
304          USER and LOGNAME.  */
305       if (change_environment)
306         {
307           xsetenv ("HOME", pw->pw_dir);
308           xsetenv ("SHELL", shell);
309           if (pw->pw_uid)
310             {
311               xsetenv ("USER", pw->pw_name);
312               xsetenv ("LOGNAME", pw->pw_name);
313             }
314         }
315     }
316 }
317
318 /* Become the user and group(s) specified by PW.  */
319
320 static void
321 change_identity (const struct passwd *pw)
322 {
323 #ifdef HAVE_INITGROUPS
324   errno = 0;
325   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
326     error (EXIT_FAIL, errno, _("cannot set groups"));
327   endgrent ();
328 #endif
329   if (setgid (pw->pw_gid))
330     error (EXIT_FAIL, errno, _("cannot set group id"));
331   if (setuid (pw->pw_uid))
332     error (EXIT_FAIL, errno, _("cannot set user id"));
333 }
334
335 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
336    If COMMAND is nonzero, pass it to the shell with the -c option.
337    Pass ADDITIONAL_ARGS to the shell as more arguments; there
338    are N_ADDITIONAL_ARGS extra arguments.  */
339
340 static void
341 run_shell (char const *shell, char const *command, char **additional_args,
342            size_t n_additional_args)
343 {
344   size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
345   char const **args = xnmalloc (n_args, sizeof *args);
346   size_t argno = 1;
347
348   if (simulate_login)
349     {
350       char *arg0;
351       char *shell_basename;
352
353       shell_basename = base_name (shell);
354       arg0 = xmalloc (strlen (shell_basename) + 2);
355       arg0[0] = '-';
356       strcpy (arg0 + 1, shell_basename);
357       args[0] = arg0;
358     }
359   else
360     args[0] = base_name (shell);
361   if (fast_startup)
362     args[argno++] = "-f";
363   if (command)
364     {
365       args[argno++] = "-c";
366       args[argno++] = command;
367     }
368   memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
369   args[argno + n_additional_args] = NULL;
370   execv (shell, (char **) args);
371
372   {
373     int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
374     error (0, errno, "%s", shell);
375     exit (exit_status);
376   }
377 }
378
379 /* Return true if SHELL is a restricted shell (one not returned by
380    getusershell), else false, meaning it is a standard shell.  */
381
382 static bool
383 restricted_shell (const char *shell)
384 {
385   char *line;
386
387   setusershell ();
388   while ((line = getusershell ()) != NULL)
389     {
390       if (*line != '#' && STREQ (line, shell))
391         {
392           endusershell ();
393           return false;
394         }
395     }
396   endusershell ();
397   return true;
398 }
399
400 void
401 usage (int status)
402 {
403   if (status != EXIT_SUCCESS)
404     fprintf (stderr, _("Try `%s --help' for more information.\n"),
405              program_name);
406   else
407     {
408       printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
409       fputs (_("\
410 Change the effective user id and group id to that of USER.\n\
411 \n\
412   -, -l, --login               make the shell a login shell\n\
413   -c, --commmand=COMMAND       pass a single COMMAND to the shell with -c\n\
414   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
415   -m, --preserve-environment   do not reset environment variables\n\
416   -p                           same as -m\n\
417   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
418 "), stdout);
419       fputs (HELP_OPTION_DESCRIPTION, stdout);
420       fputs (VERSION_OPTION_DESCRIPTION, stdout);
421       fputs (_("\
422 \n\
423 A mere - implies -l.   If USER not given, assume root.\n\
424 "), stdout);
425       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
426     }
427   exit (status);
428 }
429
430 int
431 main (int argc, char **argv)
432 {
433   int optc;
434   const char *new_user = DEFAULT_USER;
435   char *command = NULL;
436   char *shell = NULL;
437   struct passwd *pw;
438   struct passwd pw_copy;
439
440   initialize_main (&argc, &argv);
441   program_name = argv[0];
442   setlocale (LC_ALL, "");
443   bindtextdomain (PACKAGE, LOCALEDIR);
444   textdomain (PACKAGE);
445
446   initialize_exit_failure (EXIT_FAIL);
447   atexit (close_stdout);
448
449   fast_startup = false;
450   simulate_login = false;
451   change_environment = true;
452
453   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
454     {
455       switch (optc)
456         {
457         case 'c':
458           command = optarg;
459           break;
460
461         case 'f':
462           fast_startup = true;
463           break;
464
465         case 'l':
466           simulate_login = true;
467           break;
468
469         case 'm':
470         case 'p':
471           change_environment = false;
472           break;
473
474         case 's':
475           shell = optarg;
476           break;
477
478         case_GETOPT_HELP_CHAR;
479
480         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
481
482         default:
483           usage (EXIT_FAIL);
484         }
485     }
486
487   if (optind < argc && STREQ (argv[optind], "-"))
488     {
489       simulate_login = true;
490       ++optind;
491     }
492   if (optind < argc)
493     new_user = argv[optind++];
494
495   pw = getpwnam (new_user);
496   if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
497          && pw->pw_passwd))
498     error (EXIT_FAIL, 0, _("user %s does not exist"), new_user);
499
500   /* Make a copy of the password information and point pw at the local
501      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
502      the static data through the getlogin call from log_su.
503      Also, make sure pw->pw_shell is a nonempty string.
504      It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
505      but that doesn't have a default shell listed.  */
506   pw_copy = *pw;
507   pw = &pw_copy;
508   pw->pw_name = xstrdup (pw->pw_name);
509   pw->pw_passwd = xstrdup (pw->pw_passwd);
510   pw->pw_dir = xstrdup (pw->pw_dir);
511   pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
512                           ? pw->pw_shell
513                           : DEFAULT_SHELL);
514   endpwent ();
515
516   if (!correct_password (pw))
517     {
518 #ifdef SYSLOG_FAILURE
519       log_su (pw, false);
520 #endif
521       error (EXIT_FAIL, 0, _("incorrect password"));
522     }
523 #ifdef SYSLOG_SUCCESS
524   else
525     {
526       log_su (pw, true);
527     }
528 #endif
529
530   if (!shell && !change_environment)
531     shell = getenv ("SHELL");
532   if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
533     {
534       /* The user being su'd to has a nonstandard shell, and so is
535          probably a uucp account or has restricted access.  Don't
536          compromise the account by allowing access with a standard
537          shell.  */
538       error (0, 0, _("using restricted shell %s"), pw->pw_shell);
539       shell = NULL;
540     }
541   shell = xstrdup (shell ? shell : pw->pw_shell);
542   modify_environment (pw, shell);
543
544   change_identity (pw);
545   if (simulate_login && chdir (pw->pw_dir) != 0)
546     error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
547
548   run_shell (shell, command, argv + optind, MAX (0, argc - optind));
549 }