ls: --color now highlights hard linked files, too
[coreutils/bo.git] / src / su.c
blobf6b61f773557e45c97444025225e64c15377a586
1 /* su for GNU. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006, 2008 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 proper_name ("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 /* If true, pass the `-f' option to the subshell. */
132 static bool fast_startup;
134 /* If true, simulate a login instead of just starting a shell. */
135 static bool simulate_login;
137 /* If true, change some environment vars to indicate the user su'd to. */
138 static bool change_environment;
140 static struct option const longopts[] =
142 {"command", required_argument, NULL, 'c'},
143 {"fast", no_argument, NULL, 'f'},
144 {"login", no_argument, NULL, 'l'},
145 {"preserve-environment", no_argument, NULL, 'p'},
146 {"shell", required_argument, NULL, 's'},
147 {GETOPT_HELP_OPTION_DECL},
148 {GETOPT_VERSION_OPTION_DECL},
149 {NULL, 0, NULL, 0}
152 /* Add NAME=VAL to the environment, checking for out of memory errors. */
154 static void
155 xsetenv (char const *name, char const *val)
157 size_t namelen = strlen (name);
158 size_t vallen = strlen (val);
159 char *string = xmalloc (namelen + 1 + vallen + 1);
160 strcpy (string, name);
161 string[namelen] = '=';
162 strcpy (string + namelen + 1, val);
163 if (putenv (string) != 0)
164 xalloc_die ();
167 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
168 /* Log the fact that someone has run su to the user given by PW;
169 if SUCCESSFUL is true, they gave the correct password, etc. */
171 static void
172 log_su (struct passwd const *pw, bool successful)
174 const char *new_user, *old_user, *tty;
176 # ifndef SYSLOG_NON_ROOT
177 if (pw->pw_uid)
178 return;
179 # endif
180 new_user = pw->pw_name;
181 /* The utmp entry (via getlogin) is probably the best way to identify
182 the user, especially if someone su's from a su-shell. */
183 old_user = getlogin ();
184 if (!old_user)
186 /* getlogin can fail -- usually due to lack of utmp entry.
187 Resort to getpwuid. */
188 struct passwd *pwd = getpwuid (getuid ());
189 old_user = (pwd ? pwd->pw_name : "");
191 tty = ttyname (STDERR_FILENO);
192 if (!tty)
193 tty = "none";
194 /* 4.2BSD openlog doesn't have the third parameter. */
195 openlog (last_component (program_name), 0
196 # ifdef LOG_AUTH
197 , LOG_AUTH
198 # endif
200 syslog (LOG_NOTICE,
201 # ifdef SYSLOG_NON_ROOT
202 "%s(to %s) %s on %s",
203 # else
204 "%s%s on %s",
205 # endif
206 successful ? "" : "FAILED SU ",
207 # ifdef SYSLOG_NON_ROOT
208 new_user,
209 # endif
210 old_user, tty);
211 closelog ();
213 #endif
215 /* Ask the user for a password.
216 Return true if the user gives the correct password for entry PW,
217 false if not. Return true without asking for a password if run by UID 0
218 or if PW has an empty password. */
220 static bool
221 correct_password (const struct passwd *pw)
223 char *unencrypted, *encrypted, *correct;
224 #if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
225 /* Shadow passwd stuff for SVR3 and maybe other systems. */
226 struct spwd *sp = getspnam (pw->pw_name);
228 endspent ();
229 if (sp)
230 correct = sp->sp_pwdp;
231 else
232 #endif
233 correct = pw->pw_passwd;
235 if (getuid () == 0 || !correct || correct[0] == '\0')
236 return true;
238 unencrypted = getpass (_("Password:"));
239 if (!unencrypted)
241 error (0, 0, _("getpass: cannot open /dev/tty"));
242 return false;
244 encrypted = crypt (unencrypted, correct);
245 memset (unencrypted, 0, strlen (unencrypted));
246 return STREQ (encrypted, correct);
249 /* Update `environ' for the new shell based on PW, with SHELL being
250 the value for the SHELL environment variable. */
252 static void
253 modify_environment (const struct passwd *pw, const char *shell)
255 if (simulate_login)
257 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
258 Unset all other environment variables. */
259 char const *term = getenv ("TERM");
260 if (term)
261 term = xstrdup (term);
262 environ = xmalloc ((6 + !!term) * sizeof (char *));
263 environ[0] = NULL;
264 if (term)
265 xsetenv ("TERM", term);
266 xsetenv ("HOME", pw->pw_dir);
267 xsetenv ("SHELL", shell);
268 xsetenv ("USER", pw->pw_name);
269 xsetenv ("LOGNAME", pw->pw_name);
270 xsetenv ("PATH", (pw->pw_uid
271 ? DEFAULT_LOGIN_PATH
272 : DEFAULT_ROOT_LOGIN_PATH));
274 else
276 /* Set HOME, SHELL, and if not becoming a super-user,
277 USER and LOGNAME. */
278 if (change_environment)
280 xsetenv ("HOME", pw->pw_dir);
281 xsetenv ("SHELL", shell);
282 if (pw->pw_uid)
284 xsetenv ("USER", pw->pw_name);
285 xsetenv ("LOGNAME", pw->pw_name);
291 /* Become the user and group(s) specified by PW. */
293 static void
294 change_identity (const struct passwd *pw)
296 #ifdef HAVE_INITGROUPS
297 errno = 0;
298 if (initgroups (pw->pw_name, pw->pw_gid) == -1)
299 error (EXIT_FAILURE, errno, _("cannot set groups"));
300 endgrent ();
301 #endif
302 if (setgid (pw->pw_gid))
303 error (EXIT_FAILURE, errno, _("cannot set group id"));
304 if (setuid (pw->pw_uid))
305 error (EXIT_FAILURE, errno, _("cannot set user id"));
308 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
309 If COMMAND is nonzero, pass it to the shell with the -c option.
310 Pass ADDITIONAL_ARGS to the shell as more arguments; there
311 are N_ADDITIONAL_ARGS extra arguments. */
313 static void
314 run_shell (char const *shell, char const *command, char **additional_args,
315 size_t n_additional_args)
317 size_t n_args = 1 + fast_startup + 2 * !!command + n_additional_args + 1;
318 char const **args = xnmalloc (n_args, sizeof *args);
319 size_t argno = 1;
321 if (simulate_login)
323 char *arg0;
324 char *shell_basename;
326 shell_basename = last_component (shell);
327 arg0 = xmalloc (strlen (shell_basename) + 2);
328 arg0[0] = '-';
329 strcpy (arg0 + 1, shell_basename);
330 args[0] = arg0;
332 else
333 args[0] = last_component (shell);
334 if (fast_startup)
335 args[argno++] = "-f";
336 if (command)
338 args[argno++] = "-c";
339 args[argno++] = command;
341 memcpy (args + argno, additional_args, n_additional_args * sizeof *args);
342 args[argno + n_additional_args] = NULL;
343 execv (shell, (char **) args);
346 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
347 error (0, errno, "%s", shell);
348 exit (exit_status);
352 /* Return true if SHELL is a restricted shell (one not returned by
353 getusershell), else false, meaning it is a standard shell. */
355 static bool
356 restricted_shell (const char *shell)
358 char *line;
360 setusershell ();
361 while ((line = getusershell ()) != NULL)
363 if (*line != '#' && STREQ (line, shell))
365 endusershell ();
366 return false;
369 endusershell ();
370 return true;
373 void
374 usage (int status)
376 if (status != EXIT_SUCCESS)
377 fprintf (stderr, _("Try `%s --help' for more information.\n"),
378 program_name);
379 else
381 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name);
382 fputs (_("\
383 Change the effective user id and group id to that of USER.\n\
385 -, -l, --login make the shell a login shell\n\
386 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
387 -f, --fast pass -f to the shell (for csh or tcsh)\n\
388 -m, --preserve-environment do not reset environment variables\n\
389 -p same as -m\n\
390 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
391 "), stdout);
392 fputs (HELP_OPTION_DESCRIPTION, stdout);
393 fputs (VERSION_OPTION_DESCRIPTION, stdout);
394 fputs (_("\
396 A mere - implies -l. If USER not given, assume root.\n\
397 "), stdout);
398 emit_bug_reporting_address ();
400 exit (status);
404 main (int argc, char **argv)
406 int optc;
407 const char *new_user = DEFAULT_USER;
408 char *command = NULL;
409 char *shell = NULL;
410 struct passwd *pw;
411 struct passwd pw_copy;
413 initialize_main (&argc, &argv);
414 set_program_name (argv[0]);
415 setlocale (LC_ALL, "");
416 bindtextdomain (PACKAGE, LOCALEDIR);
417 textdomain (PACKAGE);
419 initialize_exit_failure (EXIT_FAILURE);
420 atexit (close_stdout);
422 fast_startup = false;
423 simulate_login = false;
424 change_environment = true;
426 while ((optc = getopt_long (argc, argv, "c:flmps:", longopts, NULL)) != -1)
428 switch (optc)
430 case 'c':
431 command = optarg;
432 break;
434 case 'f':
435 fast_startup = true;
436 break;
438 case 'l':
439 simulate_login = true;
440 break;
442 case 'm':
443 case 'p':
444 change_environment = false;
445 break;
447 case 's':
448 shell = optarg;
449 break;
451 case_GETOPT_HELP_CHAR;
453 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
455 default:
456 usage (EXIT_FAILURE);
460 if (optind < argc && STREQ (argv[optind], "-"))
462 simulate_login = true;
463 ++optind;
465 if (optind < argc)
466 new_user = argv[optind++];
468 pw = getpwnam (new_user);
469 if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
470 && pw->pw_passwd))
471 error (EXIT_FAILURE, 0, _("user %s does not exist"), new_user);
473 /* Make a copy of the password information and point pw at the local
474 copy instead. Otherwise, some systems (e.g. Linux) would clobber
475 the static data through the getlogin call from log_su.
476 Also, make sure pw->pw_shell is a nonempty string.
477 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
478 but that doesn't have a default shell listed. */
479 pw_copy = *pw;
480 pw = &pw_copy;
481 pw->pw_name = xstrdup (pw->pw_name);
482 pw->pw_passwd = xstrdup (pw->pw_passwd);
483 pw->pw_dir = xstrdup (pw->pw_dir);
484 pw->pw_shell = xstrdup (pw->pw_shell && pw->pw_shell[0]
485 ? pw->pw_shell
486 : DEFAULT_SHELL);
487 endpwent ();
489 if (!correct_password (pw))
491 #ifdef SYSLOG_FAILURE
492 log_su (pw, false);
493 #endif
494 error (EXIT_FAILURE, 0, _("incorrect password"));
496 #ifdef SYSLOG_SUCCESS
497 else
499 log_su (pw, true);
501 #endif
503 if (!shell && !change_environment)
504 shell = getenv ("SHELL");
505 if (shell && getuid () != 0 && restricted_shell (pw->pw_shell))
507 /* The user being su'd to has a nonstandard shell, and so is
508 probably a uucp account or has restricted access. Don't
509 compromise the account by allowing access with a standard
510 shell. */
511 error (0, 0, _("using restricted shell %s"), pw->pw_shell);
512 shell = NULL;
514 shell = xstrdup (shell ? shell : pw->pw_shell);
515 modify_environment (pw, shell);
517 change_identity (pw);
518 if (simulate_login && chdir (pw->pw_dir) != 0)
519 error (0, errno, _("warning: cannot change directory to %s"), pw->pw_dir);
521 run_shell (shell, command, argv + optind, MAX (0, argc - optind));