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