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