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