Tizen 2.0 Release
[external/tizen-coreutils.git] / src / su.c
1 /* su for GNU.  Run a shell with substitute user and group IDs.
2    Copyright (C) 1992-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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    Compile-time options:
42    -DSYSLOG_SUCCESS     Log successful su's (by default, to root) with syslog.
43    -DSYSLOG_FAILURE     Log failed su's (by default, to root) with syslog.
44
45    -DSYSLOG_NON_ROOT    Log all su's, not just those to root (UID 0).
46    Never logs attempted su's to nonexistent accounts.
47
48    Written by David MacKenzie <djm@gnu.ai.mit.edu>.  */
49
50 #include <config.h>
51 #include <stdio.h>
52 #include <getopt.h>
53 #include <sys/types.h>
54 #include <pwd.h>
55 #include <grp.h>
56
57 /* Hide any system prototype for getusershell.
58    This is necessary because some Cray systems have a conflicting
59    prototype (returning `int') in <unistd.h>.  */
60 #define getusershell _getusershell_sys_proto_
61
62 #include "system.h"
63 #include "getpass.h"
64
65 #undef getusershell
66
67 #if HAVE_SYSLOG_H && HAVE_SYSLOG
68 # include <syslog.h>
69 #else
70 # undef SYSLOG_SUCCESS
71 # undef SYSLOG_FAILURE
72 # undef SYSLOG_NON_ROOT
73 #endif
74
75 #if HAVE_SYS_PARAM_H
76 # include <sys/param.h>
77 #endif
78
79 #ifndef HAVE_ENDGRENT
80 # define endgrent() ((void) 0)
81 #endif
82
83 #ifndef HAVE_ENDPWENT
84 # define endpwent() ((void) 0)
85 #endif
86
87 #if HAVE_SHADOW_H
88 # include <shadow.h>
89 #endif
90
91 #include "error.h"
92
93 /* The official name of this program (e.g., no `g' prefix).  */
94 #define PROGRAM_NAME "su"
95
96 #define AUTHORS "David MacKenzie"
97
98 #if HAVE_PATHS_H
99 # include <paths.h>
100 #endif
101
102 /* The default PATH for simulated logins to non-superuser accounts.  */
103 #ifdef _PATH_DEFPATH
104 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
105 #else
106 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
107 #endif
108
109 /* The default PATH for simulated logins to superuser accounts.  */
110 #ifdef _PATH_DEFPATH_ROOT
111 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
112 #else
113 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
114 #endif
115
116 /* The shell to run if none is given in the user's passwd entry.  */
117 #define DEFAULT_SHELL "/bin/sh"
118
119 /* The user to become if none is specified.  */
120 #define DEFAULT_USER "root"
121
122 char *crypt ();
123 char *getusershell ();
124 void endusershell ();
125 void setusershell ();
126
127 extern char **environ;
128
129 static void run_shell (char const *, char const *, char **, size_t)
130      ATTRIBUTE_NORETURN;
131
132 /* The name this program was run with.  */
133 char *program_name;
134
135 /* If true, pass the `-f' option to the subshell.  */
136 static bool fast_startup;
137
138 /* If true, simulate a login instead of just starting a shell.  */
139 static bool simulate_login;
140
141 /* If true, change some environment vars to indicate the user su'd to.  */
142 static bool change_environment;
143
144 static struct option const longopts[] =
145 {
146   {"command", required_argument, NULL, 'c'},
147   {"fast", no_argument, NULL, 'f'},
148   {"login", no_argument, NULL, 'l'},
149   {"preserve-environment", no_argument, NULL, 'p'},
150   {"shell", required_argument, NULL, 's'},
151   {GETOPT_HELP_OPTION_DECL},
152   {GETOPT_VERSION_OPTION_DECL},
153   {NULL, 0, NULL, 0}
154 };
155
156 /* Add NAME=VAL to the environment, checking for out of memory errors.  */
157
158 static void
159 xsetenv (char const *name, char const *val)
160 {
161   size_t namelen = strlen (name);
162   size_t vallen = strlen (val);
163   char *string = xmalloc (namelen + 1 + vallen + 1);
164   strcpy (string, name);
165   string[namelen] = '=';
166   strcpy (string + namelen + 1, val);
167   if (putenv (string) != 0)
168     xalloc_die ();
169 }
170
171 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
172 /* Log the fact that someone has run su to the user given by PW;
173    if SUCCESSFUL is true, they gave the correct password, etc.  */
174
175 static void
176 log_su (struct passwd const *pw, bool successful)
177 {
178   const char *new_user, *old_user, *tty;
179
180 # ifndef SYSLOG_NON_ROOT
181   if (pw->pw_uid)
182     return;
183 # endif
184   new_user = pw->pw_name;
185   /* The utmp entry (via getlogin) is probably the best way to identify
186      the user, especially if someone su's from a su-shell.  */
187   old_user = getlogin ();
188   if (!old_user)
189     {
190       /* getlogin can fail -- usually due to lack of utmp entry.
191          Resort to getpwuid.  */
192       struct passwd *pwd = getpwuid (getuid ());
193       old_user = (pwd ? pwd->pw_name : "");
194     }
195   tty = ttyname (STDERR_FILENO);
196   if (!tty)
197     tty = "none";
198   /* 4.2BSD openlog doesn't have the third parameter.  */
199   openlog (last_component (program_name), 0
200 # ifdef LOG_AUTH
201            , LOG_AUTH
202 # endif
203            );
204   syslog (LOG_NOTICE,
205 # ifdef SYSLOG_NON_ROOT
206           "%s(to %s) %s on %s",
207 # else
208           "%s%s on %s",
209 # endif
210           successful ? "" : "FAILED SU ",
211 # ifdef SYSLOG_NON_ROOT
212           new_user,
213 # endif
214           old_user, tty);
215   closelog ();
216 }
217 #endif
218
219 /* Ask the user for a password.
220    Return true if the user gives the correct password for entry PW,
221    false if not.  Return true without asking for a password if run by UID 0
222    or if PW has an empty password.  */
223
224 static bool
225 correct_password (const struct passwd *pw)
226 {
227   char *unencrypted, *encrypted, *correct;
228 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
229   /* Shadow passwd stuff for SVR3 and maybe other systems.  */
230   struct spwd *sp = getspnam (pw->pw_name);
231
232   endspent ();
233   if (sp)
234     correct = sp->sp_pwdp;
235   else
236 #endif
237     correct = pw->pw_passwd;
238
239   if (getuid () == 0 || !correct || correct[0] == '\0')
240     return true;
241
242   unencrypted = getpass (_("Password:"));
243   if (!unencrypted)
244     {
245       error (0, 0, _("getpass: cannot open /dev/tty"));
246       return false;
247     }
248   encrypted = crypt (unencrypted, correct);
249   memset (unencrypted, 0, strlen (unencrypted));
250   return STREQ (encrypted, correct);
251 }
252
253 /* Update `environ' for the new shell based on PW, with SHELL being
254    the value for the SHELL environment variable.  */
255
256 static void
257 modify_environment (const struct passwd *pw, const char *shell)
258 {
259   if (simulate_login)
260     {
261       /* Leave TERM unchanged.  Set HOME, SHELL, USER, LOGNAME, PATH.
262          Unset all other environment variables.  */
263       char const *term = getenv ("TERM");
264       if (term)
265         term = xstrdup (term);
266       environ = xmalloc ((6 + !!term) * sizeof (char *));
267       environ[0] = NULL;
268       if (term)
269         xsetenv ("TERM", term);
270       xsetenv ("HOME", pw->pw_dir);
271       xsetenv ("SHELL", shell);
272       xsetenv ("USER", pw->pw_name);
273       xsetenv ("LOGNAME", pw->pw_name);
274       xsetenv ("PATH", (pw->pw_uid
275                         ? DEFAULT_LOGIN_PATH
276                         : DEFAULT_ROOT_LOGIN_PATH));
277     }
278   else
279     {
280       /* Set HOME, SHELL, and if not becoming a super-user,
281          USER and LOGNAME.  */
282       if (change_environment)
283         {
284           xsetenv ("HOME", pw->pw_dir);
285           xsetenv ("SHELL", shell);
286           if (pw->pw_uid)
287             {
288               xsetenv ("USER", pw->pw_name);
289               xsetenv ("LOGNAME", pw->pw_name);
290             }
291         }
292     }
293 }
294
295 /* Become the user and group(s) specified by PW.  */
296
297 static void
298 change_identity (const struct passwd *pw)
299 {
300 #ifdef HAVE_INITGROUPS
301   errno = 0;
302   if (initgroups (pw->pw_name, pw->pw_gid) == -1)
303     error (EXIT_FAIL, errno, _("cannot set groups"));
304   endgrent ();
305 #endif
306   if (setgid (pw->pw_gid))
307     error (EXIT_FAIL, errno, _("cannot set group id"));
308   if (setuid (pw->pw_uid))
309     error (EXIT_FAIL, errno, _("cannot set user id"));
310 }
311
312 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
313    If COMMAND is nonzero, pass it to the shell with the -c option.
314    Pass ADDITIONAL_ARGS to the shell as more arguments; there
315    are N_ADDITIONAL_ARGS extra arguments.  */
316
317 static void
318 run_shell (char const *shell, char const *command, char **additional_args,
319            size_t n_additional_args)
320 {
321   size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
322   char const **args = xnmalloc (n_args, sizeof *args);
323   size_t argno = 1;
324
325   if (simulate_login)
326     {
327       char *arg0;
328       char *shell_basename;
329
330       shell_basename = last_component (shell);
331       arg0 = xmalloc (strlen (shell_basename) + 2);
332       arg0[0] = '-';
333       strcpy (arg0 + 1, shell_basename);
334       args[0] = arg0;
335     }
336   else
337     args[0] = last_component (shell);
338   if (fast_startup)
339     args[argno++] = "-f";
340   if (command)
341     {
342       args[argno++] = "-c";
343       args[argno++] = command;
344     }
345   memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
346   args[argno + n_additional_args] = NULL;
347   execv (shell, (char **) args);
348
349   {
350     int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
351     error (0, errno, "%s", shell);
352     exit (exit_status);
353   }
354 }
355
356 /* Return true if SHELL is a restricted shell (one not returned by
357    getusershell), else false, meaning it is a standard shell.  */
358
359 static bool
360 restricted_shell (const char *shell)
361 {
362   char *line;
363
364   setusershell ();
365   while ((line = getusershell ()) != NULL)
366     {
367       if (*line != '#' && STREQ (line, shell))
368         {
369           endusershell ();
370           return false;
371         }
372     }
373   endusershell ();
374   return true;
375 }
376
377 void
378 usage (int status)
379 {
380   if (status != EXIT_SUCCESS)
381     fprintf (stderr, _("Try `%s --help' for more information.\n"),
382              program_name);
383   else
384     {
385       printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
386       fputs (_("\
387 Change the effective user id and group id to that of USER.\n\
388 \n\
389   -, -l, --login               make the shell a login shell\n\
390   -c, --command=COMMAND        pass a single COMMAND to the shell with -c\n\
391   -f, --fast                   pass -f to the shell (for csh or tcsh)\n\
392   -m, --preserve-environment   do not reset environment variables\n\
393   -p                           same as -m\n\
394   -s, --shell=SHELL            run SHELL if /etc/shells allows it\n\
395 "), stdout);
396       fputs (HELP_OPTION_DESCRIPTION, stdout);
397       fputs (VERSION_OPTION_DESCRIPTION, stdout);
398       fputs (_("\
399 \n\
400 A mere - implies -l.   If USER not given, assume root.\n\
401 "), stdout);
402       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
403     }
404   exit (status);
405 }
406
407 int
408 main (int argc, char **argv)
409 {
410   int optc;
411   const char *new_user = DEFAULT_USER;
412   char *command = NULL;
413   char *shell = NULL;
414   struct passwd *pw;
415   struct passwd pw_copy;
416
417   initialize_main (&argc, &argv);
418   program_name = argv[0];
419   setlocale (LC_ALL, "");
420   bindtextdomain (PACKAGE, LOCALEDIR);
421   textdomain (PACKAGE);
422
423   initialize_exit_failure (EXIT_FAIL);
424   atexit (close_stdout);
425
426   fast_startup = false;
427   simulate_login = false;
428   change_environment = true;
429
430   while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
431     {
432       switch (optc)
433         {
434         case 'c':
435           command = optarg;
436           break;
437
438         case 'f':
439           fast_startup = true;
440           break;
441
442         case 'l':
443           simulate_login = true;
444           break;
445
446         case 'm':
447         case 'p':
448           change_environment = false;
449           break;
450
451         case 's':
452           shell = optarg;
453           break;
454
455         case_GETOPT_HELP_CHAR;
456
457         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
458
459         default:
460           usage (EXIT_FAIL);
461         }
462     }
463
464   if (optind < argc && STREQ (argv[optind], "-"))
465     {
466       simulate_login = true;
467       ++optind;
468     }
469   if (optind < argc)
470     new_user = argv[optind++];
471
472   pw = getpwnam (new_user);
473   if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
474          && pw->pw_passwd))
475     error (EXIT_FAIL, 0, _("user %s does not exist"), new_user);
476
477   /* Make a copy of the password information and point pw at the local
478      copy instead.  Otherwise, some systems (e.g. Linux) would clobber
479      the static data through the getlogin call from log_su.
480      Also, make sure pw->pw_shell is a nonempty string.
481      It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
482      but that doesn't have a default shell listed.  */
483   pw_copy = *pw;
484   pw = &pw_copy;
485   pw->pw_name = xstrdup (pw->pw_name);
486   pw->pw_passwd = xstrdup (pw->pw_passwd);
487   pw->pw_dir = xstrdup (pw->pw_dir);
488   pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
489                           ? pw->pw_shell
490                           : DEFAULT_SHELL);
491   endpwent ();
492
493   if (!correct_password (pw))
494     {
495 #ifdef SYSLOG_FAILURE
496       log_su (pw, false);
497 #endif
498       error (EXIT_FAIL, 0, _("incorrect password"));
499     }
500 #ifdef SYSLOG_SUCCESS
501   else
502     {
503       log_su (pw, true);
504     }
505 #endif
506
507   if (!shell && !change_environment)
508     shell = getenv ("SHELL");
509   if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
510     {
511       /* The user being su'd to has a nonstandard shell, and so is
512          probably a uucp account or has restricted access.  Don't
513          compromise the account by allowing access with a standard
514          shell.  */
515       error (0, 0, _("using restricted shell %s"), pw->pw_shell);
516       shell = NULL;
517     }
518   shell = xstrdup (shell ? shell : pw->pw_shell);
519   modify_environment (pw, shell);
520
521   change_identity (pw);
522   if (simulate_login && chdir (pw->pw_dir) != 0)
523     error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
524
525   run_shell (shell, command, argv + optind, MAX (0, argc - optind));
526 }