Test yesterday's change to groups.
[coreutils.git] / src / su.c
blob7778002f84d2e305144e579e01109ac237c87d78
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 3 of the License, or
7 (at your option) 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, see <http://www.gnu.org/licenses/>. */
17 /* Run a shell with the real and effective UID and GID and groups
18 of USER, default `root'.
20 The shell run is taken from USER's password entry, /bin/sh if
21 none is specified there. If the account has a password, su
22 prompts for a password unless run by a user with real UID 0.
24 Does not change the current directory.
25 Sets `HOME' and `SHELL' from the password entry for USER, and if
26 USER is not root, sets `USER' and `LOGNAME' to USER.
27 The subshell is not a login shell.
29 If one or more ARGs are given, they are passed as additional
30 arguments to the subshell.
32 Does not handle /bin/sh or other shells specially
33 (setting argv[0] to "-su", passing -c only to certain shells, etc.).
34 I don't see the point in doing that, and it's ugly.
36 This program intentionally does not support a "wheel group" that
37 restricts who can su to UID 0 accounts. RMS considers that to
38 be fascist.
40 Compile-time options:
41 -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog.
42 -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog.
44 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
45 Never logs attempted su's to nonexistent accounts.
47 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
49 #include <config.h>
50 #include <stdio.h>
51 #include <getopt.h>
52 #include <sys/types.h>
53 #include <pwd.h>
54 #include <grp.h>
56 /* Hide any system prototype for getusershell.
57 This is necessary because some Cray systems have a conflicting
58 prototype (returning `int') in <unistd.h>. */
59 #define getusershell _getusershell_sys_proto_
61 #include "system.h"
62 #include "getpass.h"
64 #undef getusershell
66 #if HAVE_SYSLOG_H && HAVE_SYSLOG
67 # include <syslog.h>
68 #else
69 # undef SYSLOG_SUCCESS
70 # undef SYSLOG_FAILURE
71 # undef SYSLOG_NON_ROOT
72 #endif
74 #if HAVE_SYS_PARAM_H
75 # include <sys/param.h>
76 #endif
78 #ifndef HAVE_ENDGRENT
79 # define endgrent() ((void) 0)
80 #endif
82 #ifndef HAVE_ENDPWENT
83 # define endpwent() ((void) 0)
84 #endif
86 #if HAVE_SHADOW_H
87 # include <shadow.h>
88 #endif
90 #include "error.h"
92 /* The official name of this program (e.g., no `g' prefix). */
93 #define PROGRAM_NAME "su"
95 #define AUTHORS "David MacKenzie"
97 #if HAVE_PATHS_H
98 # include <paths.h>
99 #endif
101 /* The default PATH for simulated logins to non-superuser accounts. */
102 #ifdef _PATH_DEFPATH
103 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
104 #else
105 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
106 #endif
108 /* The default PATH for simulated logins to superuser accounts. */
109 #ifdef _PATH_DEFPATH_ROOT
110 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
111 #else
112 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
113 #endif
115 /* The shell to run if none is given in the user's passwd entry. */
116 #define DEFAULT_SHELL "/bin/sh"
118 /* The user to become if none is specified. */
119 #define DEFAULT_USER "root"
121 char *crypt ();
122 char *getusershell ();
123 void endusershell ();
124 void setusershell ();
126 extern char **environ;
128 static void run_shell (char const *, char const *, char **, size_t)
129 ATTRIBUTE_NORETURN;
131 /* The name this program was run with. */
132 char *program_name;
134 /* If true, pass the `-f' option to the subshell. */
135 static bool fast_startup;
137 /* If true, simulate a login instead of just starting a shell. */
138 static bool simulate_login;
140 /* If true, change some environment vars to indicate the user su'd to. */
141 static bool change_environment;
143 static struct option const longopts[] =
145 {"command", required_argument, NULL, 'c'},
146 {"fast", no_argument, NULL, 'f'},
147 {"login", no_argument, NULL, 'l'},
148 {"preserve-environment", no_argument, NULL, 'p'},
149 {"shell", required_argument, NULL, 's'},
150 {GETOPT_HELP_OPTION_DECL},
151 {GETOPT_VERSION_OPTION_DECL},
152 {NULL, 0, NULL, 0}
155 /* Add NAME=VAL to the environment, checking for out of memory errors. */
157 static void
158 xsetenv (char const *name, char const *val)
160 size_t namelen = strlen (name);
161 size_t vallen = strlen (val);
162 char *string = xmalloc (namelen + 1 + vallen + 1);
163 strcpy (string, name);
164 string[namelen] = '=';
165 strcpy (string + namelen + 1, val);
166 if (putenv (string) != 0)
167 xalloc_die ();
170 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
171 /* Log the fact that someone has run su to the user given by PW;
172 if SUCCESSFUL is true, they gave the correct password, etc. */
174 static void
175 log_su (struct passwd const *pw, bool successful)
177 const char *new_user, *old_user, *tty;
179 # ifndef SYSLOG_NON_ROOT
180 if (pw->pw_uid)
181 return;
182 # endif
183 new_user = pw->pw_name;
184 /* The utmp entry (via getlogin) is probably the best way to identify
185 the user, especially if someone su's from a su-shell. */
186 old_user = getlogin ();
187 if (!old_user)
189 /* getlogin can fail -- usually due to lack of utmp entry.
190 Resort to getpwuid. */
191 struct passwd *pwd = getpwuid (getuid ());
192 old_user = (pwd ? pwd->pw_name : "");
194 tty = ttyname (STDERR_FILENO);
195 if (!tty)
196 tty = "none";
197 /* 4.2BSD openlog doesn't have the third parameter. */
198 openlog (last_component (program_name), 0
199 # ifdef LOG_AUTH
200 , LOG_AUTH
201 # endif
203 syslog (LOG_NOTICE,
204 # ifdef SYSLOG_NON_ROOT
205 "%s(to %s) %s on %s",
206 # else
207 "%s%s on %s",
208 # endif
209 successful ? "" : "FAILED SU ",
210 # ifdef SYSLOG_NON_ROOT
211 new_user,
212 # endif
213 old_user, tty);
214 closelog ();
216 #endif
218 /* Ask the user for a password.
219 Return true if the user gives the correct password for entry PW,
220 false if not. Return true without asking for a password if run by UID 0
221 or if PW has an empty password. */
223 static bool
224 correct_password (const struct passwd *pw)
226 char *unencrypted, *encrypted, *correct;
227 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
228 /* Shadow passwd stuff for SVR3 and maybe other systems. */
229 struct spwd *sp = getspnam (pw->pw_name);
231 endspent ();
232 if (sp)
233 correct = sp->sp_pwdp;
234 else
235 #endif
236 correct = pw->pw_passwd;
238 if (getuid () == 0 || !correct || correct[0] == '\0')
239 return true;
241 unencrypted = getpass (_("Password:"));
242 if (!unencrypted)
244 error (0, 0, _("getpass: cannot open /dev/tty"));
245 return false;
247 encrypted = crypt (unencrypted, correct);
248 memset (unencrypted, 0, strlen (unencrypted));
249 return STREQ (encrypted, correct);
252 /* Update `environ' for the new shell based on PW, with SHELL being
253 the value for the SHELL environment variable. */
255 static void
256 modify_environment (const struct passwd *pw, const char *shell)
258 if (simulate_login)
260 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
261 Unset all other environment variables. */
262 char const *term = getenv ("TERM");
263 if (term)
264 term = xstrdup (term);
265 environ = xmalloc ((6 + !!term) * sizeof (char *));
266 environ[0] = NULL;
267 if (term)
268 xsetenv ("TERM", term);
269 xsetenv ("HOME", pw->pw_dir);
270 xsetenv ("SHELL", shell);
271 xsetenv ("USER", pw->pw_name);
272 xsetenv ("LOGNAME", pw->pw_name);
273 xsetenv ("PATH", (pw->pw_uid
274 ? DEFAULT_LOGIN_PATH
275 : DEFAULT_ROOT_LOGIN_PATH));
277 else
279 /* Set HOME, SHELL, and if not becoming a super-user,
280 USER and LOGNAME. */
281 if (change_environment)
283 xsetenv ("HOME", pw->pw_dir);
284 xsetenv ("SHELL", shell);
285 if (pw->pw_uid)
287 xsetenv ("USER", pw->pw_name);
288 xsetenv ("LOGNAME", pw->pw_name);
294 /* Become the user and group(s) specified by PW. */
296 static void
297 change_identity (const struct passwd *pw)
299 #ifdef HAVE_INITGROUPS
300 errno = 0;
301 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
302 error (EXIT_FAILURE, errno, _("cannot set groups"));
303 endgrent ();
304 #endif
305 if (setgid (pw->pw_gid))
306 error (EXIT_FAILURE, errno, _("cannot set group id"));
307 if (setuid (pw->pw_uid))
308 error (EXIT_FAILURE, errno, _("cannot set user id"));
311 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
312 If COMMAND is nonzero, pass it to the shell with the -c option.
313 Pass ADDITIONAL_ARGS to the shell as more arguments; there
314 are N_ADDITIONAL_ARGS extra arguments. */
316 static void
317 run_shell (char const *shell, char const *command, char **additional_args,
318 size_t n_additional_args)
320 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
321 char const **args = xnmalloc (n_args, sizeof *args);
322 size_t argno = 1;
324 if (simulate_login)
326 char *arg0;
327 char *shell_basename;
329 shell_basename = last_component (shell);
330 arg0 = xmalloc (strlen (shell_basename) + 2);
331 arg0[0] = '-';
332 strcpy (arg0 + 1, shell_basename);
333 args[0] = arg0;
335 else
336 args[0] = last_component (shell);
337 if (fast_startup)
338 args[argno++] = "-f";
339 if (command)
341 args[argno++] = "-c";
342 args[argno++] = command;
344 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
345 args[argno + n_additional_args] = NULL;
346 execv (shell, (char **) args);
349 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
350 error (0, errno, "%s", shell);
351 exit (exit_status);
355 /* Return true if SHELL is a restricted shell (one not returned by
356 getusershell), else false, meaning it is a standard shell. */
358 static bool
359 restricted_shell (const char *shell)
361 char *line;
363 setusershell ();
364 while ((line = getusershell ()) != NULL)
366 if (*line != '#' && STREQ (line, shell))
368 endusershell ();
369 return false;
372 endusershell ();
373 return true;
376 void
377 usage (int status)
379 if (status != EXIT_SUCCESS)
380 fprintf (stderr, _("Try `%s --help' for more information.\n"),
381 program_name);
382 else
384 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
385 fputs (_("\
386 Change the effective user id and group id to that of USER.\n\
388 -, -l, --login make the shell a login shell\n\
389 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
390 -f, --fast pass -f to the shell (for csh or tcsh)\n\
391 -m, --preserve-environment do not reset environment variables\n\
392 -p same as -m\n\
393 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
394 "), stdout);
395 fputs (HELP_OPTION_DESCRIPTION, stdout);
396 fputs (VERSION_OPTION_DESCRIPTION, stdout);
397 fputs (_("\
399 A mere - implies -l. If USER not given, assume root.\n\
400 "), stdout);
401 emit_bug_reporting_address ();
403 exit (status);
407 main (int argc, char **argv)
409 int optc;
410 const char *new_user = DEFAULT_USER;
411 char *command = NULL;
412 char *shell = NULL;
413 struct passwd *pw;
414 struct passwd pw_copy;
416 initialize_main (&argc, &argv);
417 program_name = argv[0];
418 setlocale (LC_ALL, "");
419 bindtextdomain (PACKAGE, LOCALEDIR);
420 textdomain (PACKAGE);
422 initialize_exit_failure (EXIT_FAILURE);
423 atexit (close_stdout);
425 fast_startup = false;
426 simulate_login = false;
427 change_environment = true;
429 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
431 switch (optc)
433 case 'c':
434 command = optarg;
435 break;
437 case 'f':
438 fast_startup = true;
439 break;
441 case 'l':
442 simulate_login = true;
443 break;
445 case 'm':
446 case 'p':
447 change_environment = false;
448 break;
450 case 's':
451 shell = optarg;
452 break;
454 case_GETOPT_HELP_CHAR;
456 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
458 default:
459 usage (EXIT_FAILURE);
463 if (optind < argc && STREQ (argv[optind], "-"))
465 simulate_login = true;
466 ++optind;
468 if (optind < argc)
469 new_user = argv[optind++];
471 pw = getpwnam (new_user);
472 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
473 && pw->pw_passwd))
474 error (EXIT_FAILURE, 0, _("user %s does not exist"), new_user);
476 /* Make a copy of the password information and point pw at the local
477 copy instead. Otherwise, some systems (e.g. Linux) would clobber
478 the static data through the getlogin call from log_su.
479 Also, make sure pw->pw_shell is a nonempty string.
480 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
481 but that doesn't have a default shell listed. */
482 pw_copy = *pw;
483 pw = &pw_copy;
484 pw->pw_name = xstrdup (pw->pw_name);
485 pw->pw_passwd = xstrdup (pw->pw_passwd);
486 pw->pw_dir = xstrdup (pw->pw_dir);
487 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
488 ? pw->pw_shell
489 : DEFAULT_SHELL);
490 endpwent ();
492 if (!correct_password (pw))
494 #ifdef SYSLOG_FAILURE
495 log_su (pw, false);
496 #endif
497 error (EXIT_FAILURE, 0, _("incorrect password"));
499 #ifdef SYSLOG_SUCCESS
500 else
502 log_su (pw, true);
504 #endif
506 if (!shell && !change_environment)
507 shell = getenv ("SHELL");
508 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
510 /* The user being su'd to has a nonstandard shell, and so is
511 probably a uucp account or has restricted access. Don't
512 compromise the account by allowing access with a standard
513 shell. */
514 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
515 shell = NULL;
517 shell = xstrdup (shell ? shell : pw->pw_shell);
518 modify_environment (pw, shell);
520 change_identity (pw);
521 if (simulate_login && chdir (pw->pw_dir) != 0)
522 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
524 run_shell (shell, command, argv + optind, MAX (0, argc - optind));