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