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