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