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