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