(main): update --version output to conform to coding standard
[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, 95, 1996 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       puts (_("\nReport bugs to bug-gnu-utils@gnu.ai.mit.edu"));
434     }
435   exit (status);
436 }
437
438 int
439 main (int argc, char **argv)
440 {
441   int optc;
442   const char *new_user = DEFAULT_USER;
443   char *command = 0;
444   char **additional_args = 0;
445   char *shell = 0;
446   struct passwd *pw;
447   struct passwd pw_copy;
448
449   program_name = argv[0];
450   setlocale (LC_ALL, "");
451   bindtextdomain (PACKAGE, LOCALEDIR);
452   textdomain (PACKAGE);
453
454   fast_startup = 0;
455   simulate_login = 0;
456   change_environment = 1;
457
458   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, (int *) 0))
459          != EOF)
460     {
461       switch (optc)
462         {
463         case 0:
464           break;
465
466         case 'c':
467           command = optarg;
468           break;
469
470         case 'f':
471           fast_startup = 1;
472           break;
473
474         case 'l':
475           simulate_login = 1;
476           break;
477
478         case 'm':
479         case 'p':
480           change_environment = 0;
481           break;
482
483         case 's':
484           shell = optarg;
485           break;
486
487         default:
488           usage (1);
489         }
490     }
491
492   if (show_version)
493     {
494       printf ("su (%s) %s\n", GNU_PACKAGE, VERSION);
495       exit (0);
496     }
497
498   if (show_help)
499     usage (0);
500
501   if (optind < argc && !strcmp (argv[optind], "-"))
502     {
503       simulate_login = 1;
504       ++optind;
505     }
506   if (optind < argc)
507     new_user = argv[optind++];
508   if (optind < argc)
509     additional_args = argv + optind;
510
511   pw = getpwnam (new_user);
512   if (pw == 0)
513     error (1, 0, _("user %s does not exist"), new_user);
514   endpwent ();
515
516   /* Make a copy of the password information and point pw at the local
517      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
518      the static data through the getlogin call from log_su.  */
519   pw_copy = *pw;
520   pw = &pw_copy;
521   pw->pw_name = xstrdup (pw->pw_name);
522   pw->pw_dir = xstrdup (pw->pw_dir);
523   pw->pw_shell = xstrdup (pw->pw_shell);
524
525   if (!correct_password (pw))
526     {
527 #ifdef SYSLOG_FAILURE
528       log_su (pw, 0);
529 #endif
530       error (1, 0, _("incorrect password"));
531     }
532 #ifdef SYSLOG_SUCCESS
533   else
534     {
535       log_su (pw, 1);
536     }
537 #endif
538
539   if (pw->pw_shell == 0 || pw->pw_shell[0] == 0)
540     pw->pw_shell = (char *) DEFAULT_SHELL;
541   if (shell == 0 && change_environment == 0)
542     shell = getenv ("SHELL");
543   if (shell != 0 && getuid () && restricted_shell (pw->pw_shell))
544     {
545       /* The user being su'd to has a nonstandard shell, and so is
546          probably a uucp account or has restricted access.  Don't
547          compromise the account by allowing access with a standard
548          shell.  */
549       error (0, 0, _("using restricted shell %s"), pw->pw_shell);
550       shell = 0;
551     }
552   if (shell == 0)
553     {
554       shell = xstrdup (pw->pw_shell);
555     }
556   modify_environment (pw, shell);
557
558   change_identity (pw);
559   if (simulate_login && chdir (pw->pw_dir))
560     error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
561
562   run_shell (shell, command, additional_args);
563 }