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