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)
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
43 Actually, with PAM, su has nothing to do with whether or not a
44 wheel group is enforced by su. RMS tries to restrict your access
45 to a su which implements the wheel group, but PAM considers that
46 to be fascist, and gives the user/sysadmin the opportunity to
47 enforce a wheel group by proper editing of /etc/pam.d/su
52 -DSYSLOG_SUCCESS Log successful su's (by default, to root) with syslog.
53 -DSYSLOG_FAILURE Log failed su's (by default, to root) with syslog.
55 -DSYSLOG_NON_ROOT Log all su's, not just those to root (UID 0).
56 Never logs attempted su's to nonexistent accounts.
58 Written by David MacKenzie <djm@gnu.ai.mit.edu>. */
63 #include <sys/types.h>
67 # include <security/pam_appl.h>
68 # include <security/pam_misc.h>
70 # include <sys/wait.h>
71 # include <sys/fsuid.h>
74 /* Hide any system prototype for getusershell.
75 This is necessary because some Cray systems have a conflicting
76 prototype (returning `int') in <unistd.h>. */
77 #define getusershell _getusershell_sys_proto_
84 #if HAVE_SYSLOG_H && HAVE_SYSLOG
86 # define SYSLOG_SUCCESS 1
87 # define SYSLOG_FAILURE 1
88 # define SYSLOG_NON_ROOT 1
90 # undef SYSLOG_SUCCESS
91 # undef SYSLOG_FAILURE
92 # undef SYSLOG_NON_ROOT
96 # include <sys/param.h>
100 # define endgrent() ((void) 0)
103 #ifndef HAVE_ENDPWENT
104 # define endpwent() ((void) 0)
113 /* The official name of this program (e.g., no `g' prefix). */
114 #define PROGRAM_NAME "su"
116 #define AUTHORS "David MacKenzie"
122 /* The default PATH for simulated logins to non-superuser accounts. */
124 # define DEFAULT_LOGIN_PATH _PATH_DEFPATH
126 # define DEFAULT_LOGIN_PATH ":/usr/ucb:/bin:/usr/bin"
129 /* The default PATH for simulated logins to superuser accounts. */
130 #ifdef _PATH_DEFPATH_ROOT
131 # define DEFAULT_ROOT_LOGIN_PATH _PATH_DEFPATH_ROOT
133 # define DEFAULT_ROOT_LOGIN_PATH "/usr/ucb:/bin:/usr/bin:/etc"
136 /* The shell to run if none is given in the user's passwd entry. */
137 #define DEFAULT_SHELL "/bin/sh"
139 /* The user to become if none is specified. */
140 #define DEFAULT_USER "root"
145 char *getusershell ();
146 void endusershell ();
147 void setusershell ();
149 extern char **environ
;
151 static void run_shell (char const *, char const *, char **, size_t)
154 /* The name this program was run with. */
157 /* If true, pass the `-f' option to the subshell. */
158 static bool fast_startup
;
160 /* If true, simulate a login instead of just starting a shell. */
161 static bool simulate_login
;
163 /* If true, change some environment vars to indicate the user su'd to. */
164 static bool change_environment
;
167 static bool _pam_session_opened
;
168 static bool _pam_cred_established
;
171 static struct option
const longopts
[] =
173 {"command", required_argument
, NULL
, 'c'},
174 {"fast", no_argument
, NULL
, 'f'},
175 {"login", no_argument
, NULL
, 'l'},
176 {"preserve-environment", no_argument
, NULL
, 'p'},
177 {"shell", required_argument
, NULL
, 's'},
178 {GETOPT_HELP_OPTION_DECL
},
179 {GETOPT_VERSION_OPTION_DECL
},
183 /* Add NAME=VAL to the environment, checking for out of memory errors. */
186 xsetenv (char const *name
, char const *val
)
188 size_t namelen
= strlen (name
);
189 size_t vallen
= strlen (val
);
190 char *string
= xmalloc (namelen
+ 1 + vallen
+ 1);
191 strcpy (string
, name
);
192 string
[namelen
] = '=';
193 strcpy (string
+ namelen
+ 1, val
);
194 if (putenv (string
) != 0)
198 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
199 /* Log the fact that someone has run su to the user given by PW;
200 if SUCCESSFUL is true, they gave the correct password, etc. */
203 log_su (struct passwd
const *pw
, bool successful
)
205 const char *new_user
, *old_user
, *tty
;
207 # ifndef SYSLOG_NON_ROOT
211 new_user
= pw
->pw_name
;
212 /* The utmp entry (via getlogin) is probably the best way to identify
213 the user, especially if someone su's from a su-shell. */
214 old_user
= getlogin ();
217 /* getlogin can fail -- usually due to lack of utmp entry.
218 Resort to getpwuid. */
219 struct passwd
*pwd
= getpwuid (getuid ());
220 old_user
= (pwd
? pwd
->pw_name
: "");
222 tty
= ttyname (STDERR_FILENO
);
225 /* 4.2BSD openlog doesn't have the third parameter. */
226 openlog (last_component (program_name
), 0
232 # ifdef SYSLOG_NON_ROOT
233 "%s(to %s) %s on %s",
237 successful
? "" : "FAILED SU ",
238 # ifdef SYSLOG_NON_ROOT
247 # define PAM_SERVICE_NAME PROGRAM_NAME
248 # define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
249 static sig_atomic_t volatile caught_signal
= false;
250 static pam_handle_t
*pamh
= NULL
;
252 static struct pam_conv conv
=
258 # define PAM_BAIL_P(a) \
261 pam_end (pamh, retval); \
266 cleanup_pam (int retcode
)
268 if (_pam_session_opened
)
269 pam_close_session (pamh
, 0);
271 if (_pam_cred_established
)
272 pam_setcred (pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
274 pam_end(pamh
, retcode
);
277 /* Signal handler for parent process. */
279 su_catch_sig (int sig
)
281 caught_signal
= true;
284 /* Export env variables declared by PAM modules. */
290 /* This is a copy but don't care to free as we exec later anyways. */
291 env
= pam_getenvlist (pamh
);
294 if (putenv (*env
) != 0)
301 create_watching_parent (void)
307 retval
= pam_open_session (pamh
, 0);
308 if (retval
!= PAM_SUCCESS
)
310 cleanup_pam (retval
);
311 error (EXIT_FAILURE
, 0, _("cannot not open session: %s"),
312 pam_strerror (pamh
, retval
));
315 _pam_session_opened
= 1;
318 if (child
== (pid_t
) -1)
320 cleanup_pam (PAM_ABORT
);
321 error (EXIT_FAILURE
, errno
, _("cannot create child process"));
324 /* the child proceeds to run the shell */
328 /* In the parent watch the child. */
330 /* su without pam support does not have a helper that keeps
331 sitting on any directory so let's go to /. */
332 if (chdir ("/") != 0)
333 error (0, errno
, _("warning: cannot change directory to %s"), "/");
335 sigfillset (&ourset
);
336 if (sigprocmask (SIG_BLOCK
, &ourset
, NULL
))
338 error (0, errno
, _("cannot block signals"));
339 caught_signal
= true;
343 struct sigaction action
;
344 action
.sa_handler
= su_catch_sig
;
345 sigemptyset (&action
.sa_mask
);
347 sigemptyset (&ourset
);
348 if (sigaddset (&ourset
, SIGTERM
)
349 || sigaddset (&ourset
, SIGALRM
)
350 || sigaction (SIGTERM
, &action
, NULL
)
351 || sigprocmask (SIG_UNBLOCK
, &ourset
, NULL
))
353 error (0, errno
, _("cannot set signal handler"));
354 caught_signal
= true;
362 pid
= waitpid (child
, &status
, WUNTRACED
);
364 if (pid
!= (pid_t
)-1 && WIFSTOPPED (status
))
366 kill (getpid (), SIGSTOP
);
367 /* once we get here, we must have resumed */
373 if (pid
!= (pid_t
)-1)
374 if (WIFSIGNALED (status
))
375 status
= WTERMSIG (status
) + 128;
377 status
= WEXITSTATUS (status
);
386 fprintf (stderr
, _("\nSession terminated, killing shell..."));
387 kill (child
, SIGTERM
);
390 cleanup_pam (PAM_SUCCESS
);
395 kill (child
, SIGKILL
);
396 fprintf (stderr
, _(" ...killed.\n"));
402 /* Ask the user for a password.
403 If PAM is in use, let PAM ask for the password if necessary.
404 Return true if the user gives the correct password for entry PW,
405 false if not. Return true without asking for a password if run by UID 0
406 or if PW has an empty password. */
409 correct_password (const struct passwd
*pw
)
412 const struct passwd
*lpw
;
415 retval
= pam_start (simulate_login
? PAM_SERVICE_NAME_L
: PAM_SERVICE_NAME
,
416 pw
->pw_name
, &conv
, &pamh
);
417 PAM_BAIL_P (return false);
419 if (isatty (0) && (cp
= ttyname (0)) != NULL
)
423 if (strncmp (cp
, "/dev/", 5) == 0)
427 retval
= pam_set_item (pamh
, PAM_TTY
, tty
);
428 PAM_BAIL_P (return false);
430 # if 0 /* Manpage discourages use of getlogin. */
432 if (!(cp
&& *cp
&& (lpw
= getpwnam (cp
)) != NULL
&& lpw
->pw_uid
== getuid ()))
434 lpw
= getpwuid (getuid ());
435 if (lpw
&& lpw
->pw_name
)
437 retval
= pam_set_item (pamh
, PAM_RUSER
, (const void *) lpw
->pw_name
);
438 PAM_BAIL_P (return false);
440 retval
= pam_authenticate (pamh
, 0);
441 PAM_BAIL_P (return false);
442 retval
= pam_acct_mgmt (pamh
, 0);
443 if (retval
== PAM_NEW_AUTHTOK_REQD
)
445 /* Password has expired. Offer option to change it. */
446 retval
= pam_chauthtok (pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
447 PAM_BAIL_P (return false);
449 PAM_BAIL_P (return false);
450 /* Must be authenticated if this point was reached. */
453 char *unencrypted
, *encrypted
, *correct
;
454 # if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
455 /* Shadow passwd stuff for SVR3 and maybe other systems. */
456 const struct spwd
*sp
= getspnam (pw
->pw_name
);
460 correct
= sp
->sp_pwdp
;
463 correct
= pw
->pw_passwd
;
465 if (getuid () == 0 || !correct
|| correct
[0] == '\0')
468 unencrypted
= getpass (_("Password:"));
471 error (0, 0, _("getpass: cannot open /dev/tty"));
474 encrypted
= crypt (unencrypted
, correct
);
475 memset (unencrypted
, 0, strlen (unencrypted
));
476 return STREQ (encrypted
, correct
);
477 #endif /* !USE_PAM */
480 /* Update `environ' for the new shell based on PW, with SHELL being
481 the value for the SHELL environment variable. */
484 modify_environment (const struct passwd
*pw
, const char *shell
)
488 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
489 Unset all other environment variables. */
490 char const *term
= getenv ("TERM");
492 term
= xstrdup (term
);
493 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
496 xsetenv ("TERM", term
);
497 xsetenv ("HOME", pw
->pw_dir
);
498 xsetenv ("SHELL", shell
);
499 xsetenv ("USER", pw
->pw_name
);
500 xsetenv ("LOGNAME", pw
->pw_name
);
501 xsetenv ("PATH", (pw
->pw_uid
503 : DEFAULT_ROOT_LOGIN_PATH
));
507 /* Set HOME, SHELL, and if not becoming a super-user,
509 if (change_environment
)
511 xsetenv ("HOME", pw
->pw_dir
);
512 xsetenv ("SHELL", shell
);
515 xsetenv ("USER", pw
->pw_name
);
516 xsetenv ("LOGNAME", pw
->pw_name
);
526 /* Become the user and group(s) specified by PW. */
529 init_groups (const struct passwd
*pw
)
531 #ifdef HAVE_INITGROUPS
533 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
536 cleanup_pam (PAM_ABORT
);
538 error (EXIT_FAIL
, errno
, _("cannot set groups"));
544 retval
= pam_setcred (pamh
, PAM_ESTABLISH_CRED
);
545 if (retval
!= PAM_SUCCESS
)
546 error (EXIT_FAILURE
, 0, "%s", pam_strerror (pamh
, retval
));
548 _pam_cred_established
= 1;
553 change_identity (const struct passwd
*pw
)
555 if (setgid (pw
->pw_gid
))
556 error (EXIT_FAIL
, errno
, _("cannot set group id"));
557 if (setuid (pw
->pw_uid
))
558 error (EXIT_FAIL
, errno
, _("cannot set user id"));
561 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
562 If COMMAND is nonzero, pass it to the shell with the -c option.
563 Pass ADDITIONAL_ARGS to the shell as more arguments; there
564 are N_ADDITIONAL_ARGS extra arguments. */
567 run_shell (char const *shell
, char const *command
, char **additional_args
,
568 size_t n_additional_args
)
570 size_t n_args
= 1 + fast_startup
+ 2 * !!command
+ n_additional_args
+ 1;
571 char const **args
= xnmalloc (n_args
, sizeof *args
);
577 char *shell_basename
;
579 shell_basename
= last_component (shell
);
580 arg0
= xmalloc (strlen (shell_basename
) + 2);
582 strcpy (arg0
+ 1, shell_basename
);
586 args
[0] = last_component (shell
);
588 args
[argno
++] = "-f";
591 args
[argno
++] = "-c";
592 args
[argno
++] = command
;
594 memcpy (args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
595 args
[argno
+ n_additional_args
] = NULL
;
596 execv (shell
, (char **) args
);
599 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
600 error (0, errno
, "%s", shell
);
605 /* Return true if SHELL is a restricted shell (one not returned by
606 getusershell), else false, meaning it is a standard shell. */
609 restricted_shell (const char *shell
)
614 while ((line
= getusershell ()) != NULL
)
616 if (*line
!= '#' && STREQ (line
, shell
))
629 if (status
!= EXIT_SUCCESS
)
630 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
634 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name
);
636 Change the effective user id and group id to that of USER.\n\
638 -, -l, --login make the shell a login shell\n\
639 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
640 -f, --fast pass -f to the shell (for csh or tcsh)\n\
641 -m, --preserve-environment do not reset environment variables\n\
643 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
645 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
646 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
649 A mere - implies -l. If USER not given, assume root.\n\
651 emit_bug_reporting_address ();
657 main (int argc
, char **argv
)
660 const char *new_user
= DEFAULT_USER
;
661 char *command
= NULL
;
664 struct passwd pw_copy
;
666 initialize_main (&argc
, &argv
);
667 program_name
= argv
[0];
668 setlocale (LC_ALL
, "");
669 bindtextdomain (PACKAGE
, LOCALEDIR
);
670 textdomain (PACKAGE
);
672 initialize_exit_failure (EXIT_FAIL
);
673 atexit (close_stdout
);
675 fast_startup
= false;
676 simulate_login
= false;
677 change_environment
= true;
679 while ((optc
= getopt_long (argc
, argv
, "c:flmps:", longopts
, NULL
)) != -1)
692 simulate_login
= true;
697 change_environment
= false;
704 case_GETOPT_HELP_CHAR
;
706 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
713 if (optind
< argc
&& STREQ (argv
[optind
], "-"))
715 simulate_login
= true;
719 new_user
= argv
[optind
++];
721 pw
= getpwnam (new_user
);
722 if (! (pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
724 error (EXIT_FAIL
, 0, _("user %s does not exist"), new_user
);
726 /* Make a copy of the password information and point pw at the local
727 copy instead. Otherwise, some systems (e.g. Linux) would clobber
728 the static data through the getlogin call from log_su.
729 Also, make sure pw->pw_shell is a nonempty string.
730 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
731 but that doesn't have a default shell listed. */
734 pw
->pw_name
= xstrdup (pw
->pw_name
);
735 pw
->pw_passwd
= xstrdup (pw
->pw_passwd
);
736 pw
->pw_dir
= xstrdup (pw
->pw_dir
);
737 pw
->pw_shell
= xstrdup (pw
->pw_shell
&& pw
->pw_shell
[0]
742 if (!correct_password (pw
))
744 #ifdef SYSLOG_FAILURE
747 error (EXIT_FAIL
, 0, _("incorrect password"));
749 #ifdef SYSLOG_SUCCESS
756 if (!shell
&& !change_environment
)
757 shell
= getenv ("SHELL");
758 if (shell
&& getuid () != 0 && restricted_shell (pw
->pw_shell
))
760 /* The user being su'd to has a nonstandard shell, and so is
761 probably a uucp account or has restricted access. Don't
762 compromise the account by allowing access with a standard
764 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
767 shell
= xstrdup (shell
? shell
: pw
->pw_shell
);
772 create_watching_parent ();
773 /* Now we're in the child. */
776 change_identity (pw
);
778 /* Set environment after pam_open_session, which may put KRB5CCNAME
779 into the pam_env, etc. */
781 modify_environment (pw
, shell
);
783 if (simulate_login
&& chdir (pw
->pw_dir
) != 0)
784 error (0, errno
, _("warning: cannot change directory to %s"), pw
->pw_dir
);
786 run_shell (shell
, command
, argv
+ optind
, MAX (0, argc
- optind
));