doc: tweak an @uref so its alt reference text renders in info
[coreutils.git] / src / su.c
blob1a6aeac9fada1708977eb862de4f8a27bcf76b45
1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006, 2008-2012 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 #include "system.h"
57 #include "getpass.h"
59 #if HAVE_SYSLOG_H && HAVE_SYSLOG
60 # include <syslog.h>
61 #else
62 # undef SYSLOG_SUCCESS
63 # undef SYSLOG_FAILURE
64 # undef SYSLOG_NON_ROOT
65 #endif
67 #if HAVE_SYS_PARAM_H
68 # include <sys/param.h>
69 #endif
71 #ifndef HAVE_ENDGRENT
72 # define endgrent() ((void) 0)
73 #endif
75 #ifndef HAVE_ENDPWENT
76 # define endpwent() ((void) 0)
77 #endif
79 #if HAVE_SHADOW_H
80 # include <shadow.h>
81 #endif
83 #include "error.h"
85 /* The official name of this program (e.g., no 'g' prefix). */
86 #define PROGRAM_NAME "su"
88 #define AUTHORS proper_name ("David MacKenzie")
90 #if HAVE_PATHS_H
91 # include <paths.h>
92 #endif
94 /* The default PATH for simulated logins to non-superuser accounts. */
95 #ifdef _PATH_DEFPATH
96 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
97 #else
98 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
99 #endif
101 /* The default PATH for simulated logins to superuser accounts. */
102 #ifdef _PATH_DEFPATH_ROOT
103 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
104 #else
105 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
106 #endif
108 /* The shell to run if none is given in the user's passwd entry. */
109 #define DEFAULT_SHELL "/bin/sh"
111 /* The user to become if none is specified. */
112 #define DEFAULT_USER "root"
114 char *crypt (char const *key, char const *salt);
116 static void run_shell (char const *, char const *, char **, size_t)
117 ATTRIBUTE_NORETURN;
119 /* If true, pass the '-f' option to the subshell. */
120 static bool fast_startup;
122 /* If true, simulate a login instead of just starting a shell. */
123 static bool simulate_login;
125 /* If true, change some environment vars to indicate the user su'd to. */
126 static bool change_environment;
128 static struct option const longopts[] =
130 {"command", required_argument, NULL, 'c'},
131 {"fast", no_argument, NULL, 'f'},
132 {"login", no_argument, NULL, 'l'},
133 {"preserve-environment", no_argument, NULL, 'p'},
134 {"shell", required_argument, NULL, 's'},
135 {GETOPT_HELP_OPTION_DECL},
136 {GETOPT_VERSION_OPTION_DECL},
137 {NULL, 0, NULL, 0}
140 /* Add NAME=VAL to the environment, checking for out of memory errors. */
142 static void
143 xsetenv (char const *name, char const *val)
145 size_t namelen = strlen (name);
146 size_t vallen = strlen (val);
147 char *string = xmalloc (namelen + 1 + vallen + 1);
148 strcpy (string, name);
149 string[namelen] = '=';
150 strcpy (string + namelen + 1, val);
151 if (putenv (string) != 0)
152 xalloc_die ();
155 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
156 /* Log the fact that someone has run su to the user given by PW;
157 if SUCCESSFUL is true, they gave the correct password, etc. */
159 static void
160 log_su (struct passwd const *pw, bool successful)
162 const char *new_user, *old_user, *tty;
164 # ifndef SYSLOG_NON_ROOT
165 if (pw->pw_uid)
166 return;
167 # endif
168 new_user = pw->pw_name;
169 /* The utmp entry (via getlogin) is probably the best way to identify
170 the user, especially if someone su's from a su-shell. */
171 old_user = getlogin ();
172 if (!old_user)
174 /* getlogin can fail -- usually due to lack of utmp entry.
175 Resort to getpwuid. */
176 errno = 0;
177 uid_t ruid = getuid ();
178 uid_t NO_UID = -1;
179 struct passwd *pwd = (ruid == NO_UID && errno ? NULL : getpwuid (ruid));
180 old_user = (pwd ? pwd->pw_name : "");
182 tty = ttyname (STDERR_FILENO);
183 if (!tty)
184 tty = "none";
185 /* 4.2BSD openlog doesn't have the third parameter. */
186 openlog (last_component (program_name), 0
187 # ifdef LOG_AUTH
188 , LOG_AUTH
189 # endif
191 syslog (LOG_NOTICE,
192 # ifdef SYSLOG_NON_ROOT
193 "%s(to %s) %s on %s",
194 # else
195 "%s%s on %s",
196 # endif
197 successful ? "" : "FAILED SU ",
198 # ifdef SYSLOG_NON_ROOT
199 new_user,
200 # endif
201 old_user, tty);
202 closelog ();
204 #endif
206 /* Ask the user for a password.
207 Return true if the user gives the correct password for entry PW,
208 false if not. Return true without asking for a password if run by UID 0
209 or if PW has an empty password. */
211 static bool
212 correct_password (const struct passwd *pw)
214 char *unencrypted, *encrypted, *correct;
215 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
216 /* Shadow passwd stuff for SVR3 and maybe other systems. */
217 struct spwd *sp = getspnam (pw->pw_name);
219 endspent ();
220 if (sp)
221 correct = sp->sp_pwdp;
222 else
223 #endif
224 correct = pw->pw_passwd;
226 if (getuid () == 0 || !correct || correct[0] == '\0')
227 return true;
229 unencrypted = getpass (_("Password:"));
230 if (!unencrypted)
232 error (0, 0, _("getpass: cannot open /dev/tty"));
233 return false;
235 encrypted = crypt (unencrypted, correct);
236 memset (unencrypted, 0, strlen (unencrypted));
237 return STREQ (encrypted, correct);
240 /* Update 'environ' for the new shell based on PW, with SHELL being
241 the value for the SHELL environment variable. */
243 static void
244 modify_environment (const struct passwd *pw, const char *shell)
246 if (simulate_login)
248 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
249 Unset all other environment variables. */
250 char const *term = getenv ("TERM");
251 if (term)
252 term = xstrdup (term);
253 environ = xmalloc ((6 + !!term) * sizeof (char *));
254 environ[0] = NULL;
255 if (term)
256 xsetenv ("TERM", term);
257 xsetenv ("HOME", pw->pw_dir);
258 xsetenv ("SHELL", shell);
259 xsetenv ("USER", pw->pw_name);
260 xsetenv ("LOGNAME", pw->pw_name);
261 xsetenv ("PATH", (pw->pw_uid
262 ? DEFAULT_LOGIN_PATH
263 : DEFAULT_ROOT_LOGIN_PATH));
265 else
267 /* Set HOME, SHELL, and if not becoming a super-user,
268 USER and LOGNAME. */
269 if (change_environment)
271 xsetenv ("HOME", pw->pw_dir);
272 xsetenv ("SHELL", shell);
273 if (pw->pw_uid)
275 xsetenv ("USER", pw->pw_name);
276 xsetenv ("LOGNAME", pw->pw_name);
282 /* Become the user and group(s) specified by PW. */
284 static void
285 change_identity (const struct passwd *pw)
287 #ifdef HAVE_INITGROUPS
288 errno = 0;
289 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
290 error (EXIT_CANCELED, errno, _("cannot set groups"));
291 endgrent ();
292 #endif
293 if (setgid (pw->pw_gid))
294 error (EXIT_CANCELED, errno, _("cannot set group id"));
295 if (setuid (pw->pw_uid))
296 error (EXIT_CANCELED, errno, _("cannot set user id"));
299 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
300 If COMMAND is nonzero, pass it to the shell with the -c option.
301 Pass ADDITIONAL_ARGS to the shell as more arguments; there
302 are N_ADDITIONAL_ARGS extra arguments. */
304 static void
305 run_shell (char const *shell, char const *command, char **additional_args,
306 size_t n_additional_args)
308 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
309 char const **args = xnmalloc (n_args, sizeof *args);
310 size_t argno = 1;
312 if (simulate_login)
314 char *arg0;
315 char *shell_basename;
317 shell_basename = last_component (shell);
318 arg0 = xmalloc (strlen (shell_basename) + 2);
319 arg0[0] = '-';
320 strcpy (arg0 + 1, shell_basename);
321 args[0] = arg0;
323 else
324 args[0] = last_component (shell);
325 if (fast_startup)
326 args[argno++] = "-f";
327 if (command)
329 args[argno++] = "-c";
330 args[argno++] = command;
332 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
333 args[argno + n_additional_args] = NULL;
334 execv (shell, (char **) args);
337 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
338 error (0, errno, "%s", shell);
339 exit (exit_status);
343 /* Return true if SHELL is a restricted shell (one not returned by
344 getusershell), else false, meaning it is a standard shell. */
346 static bool
347 restricted_shell (const char *shell)
349 char *line;
351 setusershell ();
352 while ((line = getusershell ()) != NULL)
354 if (*line != '#' && STREQ (line, shell))
356 endusershell ();
357 return false;
360 endusershell ();
361 return true;
364 void
365 usage (int status)
367 if (status != EXIT_SUCCESS)
368 emit_try_help ();
369 else
371 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
372 fputs (_("\
373 Change the effective user id and group id to that of USER.\n\
375 -, -l, --login make the shell a login shell\n\
376 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
377 -f, --fast pass -f to the shell (for csh or tcsh)\n\
378 -m, --preserve-environment do not reset environment variables\n\
379 -p same as -m\n\
380 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
381 "), stdout);
382 fputs (HELP_OPTION_DESCRIPTION, stdout);
383 fputs (VERSION_OPTION_DESCRIPTION, stdout);
384 fputs (_("\
386 A mere - implies -l. If USER not given, assume root.\n\
387 "), stdout);
388 emit_ancillary_info ();
390 exit (status);
394 main (int argc, char **argv)
396 int optc;
397 const char *new_user = DEFAULT_USER;
398 char *command = NULL;
399 char *shell = NULL;
400 struct passwd *pw;
401 struct passwd pw_copy;
403 initialize_main (&argc, &argv);
404 set_program_name (argv[0]);
405 setlocale (LC_ALL, "");
406 bindtextdomain (PACKAGE, LOCALEDIR);
407 textdomain (PACKAGE);
409 initialize_exit_failure (EXIT_CANCELED);
410 atexit (close_stdout);
412 fast_startup = false;
413 simulate_login = false;
414 change_environment = true;
416 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
418 switch (optc)
420 case 'c':
421 command = optarg;
422 break;
424 case 'f':
425 fast_startup = true;
426 break;
428 case 'l':
429 simulate_login = true;
430 break;
432 case 'm':
433 case 'p':
434 change_environment = false;
435 break;
437 case 's':
438 shell = optarg;
439 break;
441 case_GETOPT_HELP_CHAR;
443 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
445 default:
446 usage (EXIT_CANCELED);
450 if (optind < argc && STREQ (argv[optind], "-"))
452 simulate_login = true;
453 ++optind;
455 if (optind < argc)
456 new_user = argv[optind++];
458 pw = getpwnam (new_user);
459 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
460 && pw->pw_passwd))
461 error (EXIT_CANCELED, 0, _("user %s does not exist"), new_user);
463 /* Make a copy of the password information and point pw at the local
464 copy instead. Otherwise, some systems (e.g. GNU/Linux) would clobber
465 the static data through the getlogin call from log_su.
466 Also, make sure pw->pw_shell is a nonempty string.
467 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
468 but that doesn't have a default shell listed. */
469 pw_copy = *pw;
470 pw = &pw_copy;
471 pw->pw_name = xstrdup (pw->pw_name);
472 pw->pw_passwd = xstrdup (pw->pw_passwd);
473 pw->pw_dir = xstrdup (pw->pw_dir);
474 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
475 ? pw->pw_shell
476 : DEFAULT_SHELL);
477 endpwent ();
479 if (!correct_password (pw))
481 #ifdef SYSLOG_FAILURE
482 log_su (pw, false);
483 #endif
484 error (EXIT_CANCELED, 0, _("incorrect password"));
486 #ifdef SYSLOG_SUCCESS
487 else
489 log_su (pw, true);
491 #endif
493 if (!shell && !change_environment)
494 shell = getenv ("SHELL");
495 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
497 /* The user being su'd to has a nonstandard shell, and so is
498 probably a uucp account or has restricted access. Don't
499 compromise the account by allowing access with a standard
500 shell. */
501 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
502 shell = NULL;
504 shell = xstrdup (shell ? shell : pw->pw_shell);
505 modify_environment (pw, shell);
507 change_identity (pw);
508 if (simulate_login && chdir (pw->pw_dir) != 0)
509 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
511 /* error() flushes stderr, but does not check for write failure.
512 Normally, we would catch this via our atexit() hook of
513 close_stdout, but execv() gets in the way. If stderr
514 encountered a write failure, there is no need to try calling
515 error() again. */
516 if (ferror (stderr))
517 exit (EXIT_CANCELED);
519 run_shell (shell, command, argv + optind, MAX (0, argc - optind));