Clean up after the change of 2006-12-28.
[coreutils.git] / src / su.c
blob70828b8fd8603a4f9c950a28fb164b98d80ac08d
1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006 Free Software Foundation, Inc.
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.
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.
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. */
18 /* Run a shell with the real and effective UID and GID and groups
19 of USER, default `root'.
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.
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.
30 If one or more ARGs are given, they are passed as additional
31 arguments to the subshell.
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.
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.
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.
45 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
46 Never logs attempted su's to nonexistent accounts.
48 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
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>
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_
62 #include "system.h"
63 #include "getpass.h"
65 #undef getusershell
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
75 #if HAVE_SYS_PARAM_H
76 # include <sys/param.h>
77 #endif
79 #ifndef HAVE_ENDGRENT
80 # define endgrent() ((void) 0)
81 #endif
83 #ifndef HAVE_ENDPWENT
84 # define endpwent() ((void) 0)
85 #endif
87 #if HAVE_SHADOW_H
88 # include <shadow.h>
89 #endif
91 #include "error.h"
93 /* The official name of this program (e.g., no `g' prefix). */
94 #define PROGRAM_NAME "su"
96 #define AUTHORS "David MacKenzie"
98 #if HAVE_PATHS_H
99 # include <paths.h>
100 #endif
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
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
116 /* The shell to run if none is given in the user's passwd entry. */
117 #define DEFAULT_SHELL "/bin/sh"
119 /* The user to become if none is specified. */
120 #define DEFAULT_USER "root"
122 char *crypt ();
123 char *getusershell ();
124 void endusershell ();
125 void setusershell ();
127 extern char **environ;
129 static void run_shell (char const *, char const *, char **, size_t)
130 ATTRIBUTE_NORETURN;
132 /* The name this program was run with. */
133 char *program_name;
135 /* If true, pass the `-f' option to the subshell. */
136 static bool fast_startup;
138 /* If true, simulate a login instead of just starting a shell. */
139 static bool simulate_login;
141 /* If true, change some environment vars to indicate the user su'd to. */
142 static bool change_environment;
144 static struct option const longopts[] =
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}
156 /* Add NAME=VAL to the environment, checking for out of memory errors. */
158 static void
159 xsetenv (char const *name, char const *val)
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 ();
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. */
175 static void
176 log_su (struct passwd const *pw, bool successful)
178 const char *new_user, *old_user, *tty;
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)
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 : "");
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
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 ();
217 #endif
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. */
224 static bool
225 correct_password (const struct passwd *pw)
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);
232 endspent ();
233 if (sp)
234 correct = sp->sp_pwdp;
235 else
236 #endif
237 correct = pw->pw_passwd;
239 if (getuid () == 0 || !correct || correct[0] == '\0')
240 return true;
242 unencrypted = getpass (_("Password:"));
243 if (!unencrypted)
245 error (0, 0, _("getpass: cannot open /dev/tty"));
246 return false;
248 encrypted = crypt (unencrypted, correct);
249 memset (unencrypted, 0, strlen (unencrypted));
250 return STREQ (encrypted, correct);
253 /* Update `environ' for the new shell based on PW, with SHELL being
254 the value for the SHELL environment variable. */
256 static void
257 modify_environment (const struct passwd *pw, const char *shell)
259 if (simulate_login)
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));
278 else
280 /* Set HOME, SHELL, and if not becoming a super-user,
281 USER and LOGNAME. */
282 if (change_environment)
284 xsetenv ("HOME", pw->pw_dir);
285 xsetenv ("SHELL", shell);
286 if (pw->pw_uid)
288 xsetenv ("USER", pw->pw_name);
289 xsetenv ("LOGNAME", pw->pw_name);
295 /* Become the user and group(s) specified by PW. */
297 static void
298 change_identity (const struct passwd *pw)
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"));
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. */
317 static void
318 run_shell (char const *shell, char const *command, char **additional_args,
319 size_t n_additional_args)
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;
325 if (simulate_login)
327 char *arg0;
328 char *shell_basename;
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;
336 else
337 args[0] = last_component (shell);
338 if (fast_startup)
339 args[argno++] = "-f";
340 if (command)
342 args[argno++] = "-c";
343 args[argno++] = command;
345 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
346 args[argno + n_additional_args] = NULL;
347 execv (shell, (char **) args);
350 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
351 error (0, errno, "%s", shell);
352 exit (exit_status);
356 /* Return true if SHELL is a restricted shell (one not returned by
357 getusershell), else false, meaning it is a standard shell. */
359 static bool
360 restricted_shell (const char *shell)
362 char *line;
364 setusershell ();
365 while ((line = getusershell ()) != NULL)
367 if (*line != '#' && STREQ (line, shell))
369 endusershell ();
370 return false;
373 endusershell ();
374 return true;
377 void
378 usage (int status)
380 if (status != EXIT_SUCCESS)
381 fprintf (stderr, _("Try `%s --help' for more information.\n"),
382 program_name);
383 else
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\
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 (_("\
400 A mere - implies -l. If USER not given, assume root.\n\
401 "), stdout);
402 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
404 exit (status);
408 main (int argc, char **argv)
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;
417 initialize_main (&argc, &argv);
418 program_name = argv[0];
419 setlocale (LC_ALL, "");
420 bindtextdomain (PACKAGE, LOCALEDIR);
421 textdomain (PACKAGE);
423 initialize_exit_failure (EXIT_FAIL);
424 atexit (close_stdout);
426 fast_startup = false;
427 simulate_login = false;
428 change_environment = true;
430 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
432 switch (optc)
434 case 'c':
435 command = optarg;
436 break;
438 case 'f':
439 fast_startup = true;
440 break;
442 case 'l':
443 simulate_login = true;
444 break;
446 case 'm':
447 case 'p':
448 change_environment = false;
449 break;
451 case 's':
452 shell = optarg;
453 break;
455 case_GETOPT_HELP_CHAR;
457 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
459 default:
460 usage (EXIT_FAIL);
464 if (optind < argc && STREQ (argv[optind], "-"))
466 simulate_login = true;
467 ++optind;
469 if (optind < argc)
470 new_user = argv[optind++];
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);
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 ();
493 if (!correct_password (pw))
495 #ifdef SYSLOG_FAILURE
496 log_su (pw, false);
497 #endif
498 error (EXIT_FAIL, 0, _("incorrect password"));
500 #ifdef SYSLOG_SUCCESS
501 else
503 log_su (pw, true);
505 #endif
507 if (!shell && !change_environment)
508 shell = getenv ("SHELL");
509 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
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;
518 shell = xstrdup (shell ? shell : pw->pw_shell);
519 modify_environment (pw, shell);
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);
525 run_shell (shell, command, argv + optind, MAX (0, argc - optind));