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