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