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
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>. */
52 #include <sys/types.h>
59 #if HAVE_SYSLOG_H && HAVE_SYSLOG
62 # undef SYSLOG_SUCCESS
63 # undef SYSLOG_FAILURE
64 # undef SYSLOG_NON_ROOT
68 # include <sys/param.h>
72 # define endgrent() ((void) 0)
76 # define endpwent() ((void) 0)
85 /* The official name of this program (e.g., no 'g' prefix). */
86 #define PROGRAM_NAME "su"
88 #define AUTHORS proper_name ("David MacKenzie")
94 /* The default PATH for simulated logins to non-superuser accounts. */
96 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
98 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
101 /* The default PATH for simulated logins to superuser accounts. */
102 #ifdef _PATH_DEFPATH_ROOT
103 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
105 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
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)
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
},
140 /* Add NAME=VAL to the environment, checking for out of memory errors. */
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)
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. */
160 log_su (struct passwd
const *pw
, bool successful
)
162 const char *new_user
, *old_user
, *tty
;
164 # ifndef SYSLOG_NON_ROOT
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 ();
174 /* getlogin can fail -- usually due to lack of utmp entry.
175 Resort to getpwuid. */
177 uid_t ruid
= getuid ();
179 struct passwd
*pwd
= (ruid
== NO_UID
&& errno
? NULL
: getpwuid (ruid
));
180 old_user
= (pwd
? pwd
->pw_name
: "");
182 tty
= ttyname (STDERR_FILENO
);
185 /* 4.2BSD openlog doesn't have the third parameter. */
186 openlog (last_component (program_name
), 0
192 # ifdef SYSLOG_NON_ROOT
193 "%s(to %s) %s on %s",
197 successful
? "" : "FAILED SU ",
198 # ifdef SYSLOG_NON_ROOT
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. */
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
);
221 correct
= sp
->sp_pwdp
;
224 correct
= pw
->pw_passwd
;
226 if (getuid () == 0 || !correct
|| correct
[0] == '\0')
229 unencrypted
= getpass (_("Password:"));
232 error (0, 0, _("getpass: cannot open /dev/tty"));
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. */
244 modify_environment (const struct passwd
*pw
, const char *shell
)
248 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
249 Unset all other environment variables. */
250 char const *term
= getenv ("TERM");
252 term
= xstrdup (term
);
253 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
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
263 : DEFAULT_ROOT_LOGIN_PATH
));
267 /* Set HOME, SHELL, and if not becoming a super-user,
269 if (change_environment
)
271 xsetenv ("HOME", pw
->pw_dir
);
272 xsetenv ("SHELL", shell
);
275 xsetenv ("USER", pw
->pw_name
);
276 xsetenv ("LOGNAME", pw
->pw_name
);
282 /* Become the user and group(s) specified by PW. */
285 change_identity (const struct passwd
*pw
)
287 #ifdef HAVE_INITGROUPS
289 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
290 error (EXIT_CANCELED
, errno
, _("cannot set groups"));
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. */
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
);
315 char *shell_basename
;
317 shell_basename
= last_component (shell
);
318 arg0
= xmalloc (strlen (shell_basename
) + 2);
320 strcpy (arg0
+ 1, shell_basename
);
324 args
[0] = last_component (shell
);
326 args
[argno
++] = "-f";
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
);
343 /* Return true if SHELL is a restricted shell (one not returned by
344 getusershell), else false, meaning it is a standard shell. */
347 restricted_shell (const char *shell
)
352 while ((line
= getusershell ()) != NULL
)
354 if (*line
!= '#' && STREQ (line
, shell
))
367 if (status
!= EXIT_SUCCESS
)
371 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name
);
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\
380 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
382 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
383 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
386 A mere - implies -l. If USER not given, assume root.\n\
388 emit_ancillary_info ();
394 main (int argc
, char **argv
)
397 const char *new_user
= DEFAULT_USER
;
398 char *command
= NULL
;
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)
429 simulate_login
= true;
434 change_environment
= false;
441 case_GETOPT_HELP_CHAR
;
443 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
446 usage (EXIT_CANCELED
);
450 if (optind
< argc
&& STREQ (argv
[optind
], "-"))
452 simulate_login
= true;
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]
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. */
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]
479 if (!correct_password (pw
))
481 #ifdef SYSLOG_FAILURE
484 error (EXIT_CANCELED
, 0, _("incorrect password"));
486 #ifdef SYSLOG_SUCCESS
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
501 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
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
517 exit (EXIT_CANCELED
);
519 run_shell (shell
, command
, argv
+ optind
, MAX (0, argc
- optind
));