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