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