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