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