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>. */
61 # define MAX(a,b) ((a) > (b) ? (a) : (b))
64 /* Exit statuses for programs like 'env' that exec other programs.
65 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
69 EXIT_CANNOT_INVOKE
= 126,
76 #include <sys/types.h>
80 # include <security/pam_appl.h>
81 # include <security/pam_misc.h>
83 # include <sys/wait.h>
84 # include <sys/fsuid.h>
87 #if HAVE_SYSLOG_H && HAVE_SYSLOG
89 # define SYSLOG_SUCCESS 1
90 # define SYSLOG_FAILURE 1
91 # define SYSLOG_NON_ROOT 1
93 # undef SYSLOG_SUCCESS
94 # undef SYSLOG_FAILURE
95 # undef SYSLOG_NON_ROOT
99 # include <sys/param.h>
102 #ifndef HAVE_ENDGRENT
103 # define endgrent() ((void) 0)
106 #ifndef HAVE_ENDPWENT
107 # define endpwent() ((void) 0)
120 /* The official name of this program (e.g., no `g' prefix). */
121 #define PROGRAM_NAME "su"
123 #define AUTHORS "David MacKenzie"
131 /* The default PATH for simulated logins to non-superuser accounts. */
132 #define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin"
134 /* The default PATH for simulated logins to superuser accounts. */
135 #define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin"
137 /* The shell to run if none is given in the user's passwd entry. */
138 #define DEFAULT_SHELL "/bin/sh"
140 /* The user to become if none is specified. */
141 #define DEFAULT_USER "root"
147 extern char **environ
;
149 static void run_shell (char const *, char const *, char **, size_t)
150 __attribute__ ((__noreturn__
));
152 /* The name this program was run with. */
155 /* If true, pass the `-f' option to the subshell. */
156 static bool fast_startup
;
158 /* If true, simulate a login instead of just starting a shell. */
159 static bool simulate_login
;
161 /* If true, change some environment vars to indicate the user su'd to. */
162 static bool change_environment
;
164 /* If true, then don't call setsid() with a command. */
165 int same_session
= 0;
168 static bool _pam_session_opened
;
169 static bool _pam_cred_established
;
173 static struct option
const longopts
[] =
175 {"command", required_argument
, NULL
, 'c'},
176 {"session-command", required_argument
, NULL
, 'C'},
177 {"fast", no_argument
, NULL
, 'f'},
178 {"login", no_argument
, NULL
, 'l'},
179 {"preserve-environment", no_argument
, NULL
, 'p'},
180 {"shell", required_argument
, NULL
, 's'},
181 {"help", no_argument
, 0, 'u'},
182 {"version", no_argument
, 0, 'v'},
186 /* Add NAME=VAL to the environment, checking for out of memory errors. */
189 xsetenv (char const *name
, char const *val
)
191 size_t namelen
= strlen (name
);
192 size_t vallen
= strlen (val
);
193 char *string
= xmalloc (namelen
+ 1 + vallen
+ 1);
194 strcpy (string
, name
);
195 string
[namelen
] = '=';
196 strcpy (string
+ namelen
+ 1, val
);
197 if (putenv (string
) != 0)
198 error (EXIT_FAILURE
, 0, _("out of memory"));
201 #if defined SYSLOG_SUCCESS || defined SYSLOG_FAILURE
202 /* Log the fact that someone has run su to the user given by PW;
203 if SUCCESSFUL is true, they gave the correct password, etc. */
206 log_su (struct passwd
const *pw
, bool successful
)
208 const char *new_user
, *old_user
, *tty
;
210 # ifndef SYSLOG_NON_ROOT
214 new_user
= pw
->pw_name
;
215 /* The utmp entry (via getlogin) is probably the best way to identify
216 the user, especially if someone su's from a su-shell. */
217 old_user
= getlogin ();
220 /* getlogin can fail -- usually due to lack of utmp entry.
221 Resort to getpwuid. */
222 struct passwd
*pwd
= getpwuid (getuid ());
223 old_user
= (pwd
? pwd
->pw_name
: "");
225 tty
= ttyname (STDERR_FILENO
);
228 /* 4.2BSD openlog doesn't have the third parameter. */
229 openlog (basename (program_name
), 0
235 # ifdef SYSLOG_NON_ROOT
236 "%s(to %s) %s on %s",
240 successful
? "" : "FAILED SU ",
241 # ifdef SYSLOG_NON_ROOT
250 # define PAM_SERVICE_NAME PROGRAM_NAME
251 # define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
252 static sig_atomic_t volatile caught_signal
= false;
253 static pam_handle_t
*pamh
= NULL
;
255 static struct pam_conv conv
=
261 # define PAM_BAIL_P(a) \
264 pam_end (pamh, retval); \
269 cleanup_pam (int retcode
)
271 if (_pam_session_opened
)
272 pam_close_session (pamh
, 0);
274 if (_pam_cred_established
)
275 pam_setcred (pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
277 pam_end(pamh
, retcode
);
280 /* Signal handler for parent process. */
282 su_catch_sig (int sig
)
284 caught_signal
= true;
287 /* Export env variables declared by PAM modules. */
293 /* This is a copy but don't care to free as we exec later anyways. */
294 env
= pam_getenvlist (pamh
);
297 if (putenv (*env
) != 0)
298 error (EXIT_FAILURE
, 0, _("out of memory"));
304 create_watching_parent (void)
310 retval
= pam_open_session (pamh
, 0);
311 if (retval
!= PAM_SUCCESS
)
313 cleanup_pam (retval
);
314 error (EXIT_FAILURE
, 0, _("cannot not open session: %s"),
315 pam_strerror (pamh
, retval
));
318 _pam_session_opened
= 1;
321 if (child
== (pid_t
) -1)
323 cleanup_pam (PAM_ABORT
);
324 error (EXIT_FAILURE
, errno
, _("cannot create child process"));
327 /* the child proceeds to run the shell */
331 /* In the parent watch the child. */
333 /* su without pam support does not have a helper that keeps
334 sitting on any directory so let's go to /. */
335 if (chdir ("/") != 0)
336 error (0, errno
, _("warning: cannot change directory to %s"), "/");
338 sigfillset (&ourset
);
339 if (sigprocmask (SIG_BLOCK
, &ourset
, NULL
))
341 error (0, errno
, _("cannot block signals"));
342 caught_signal
= true;
346 struct sigaction action
;
347 action
.sa_handler
= su_catch_sig
;
348 sigemptyset (&action
.sa_mask
);
350 sigemptyset (&ourset
);
353 if (sigaddset(&ourset
, SIGINT
) || sigaddset(&ourset
, SIGQUIT
))
355 error (0, errno
, _("cannot set signal handler"));
356 caught_signal
= true;
359 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
360 || sigaddset(&ourset
, SIGALRM
)
361 || sigaction(SIGTERM
, &action
, NULL
)
362 || sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
))) {
363 error (0, errno
, _("cannot set signal handler"));
364 caught_signal
= true;
366 if (!caught_signal
&& !same_session
&& (sigaction(SIGINT
, &action
, NULL
)
367 || sigaction(SIGQUIT
, &action
, NULL
)))
369 error (0, errno
, _("cannot set signal handler"));
370 caught_signal
= true;
378 pid
= waitpid (child
, &status
, WUNTRACED
);
380 if (pid
!= (pid_t
)-1 && WIFSTOPPED (status
))
382 kill (getpid (), SIGSTOP
);
383 /* once we get here, we must have resumed */
389 if (pid
!= (pid_t
)-1)
390 if (WIFSIGNALED (status
))
391 status
= WTERMSIG (status
) + 128;
393 status
= WEXITSTATUS (status
);
402 fprintf (stderr
, _("\nSession terminated, killing shell..."));
403 kill (child
, SIGTERM
);
406 cleanup_pam (PAM_SUCCESS
);
411 kill (child
, SIGKILL
);
412 fprintf (stderr
, _(" ...killed.\n"));
418 /* Ask the user for a password.
419 If PAM is in use, let PAM ask for the password if necessary.
420 Return true if the user gives the correct password for entry PW,
421 false if not. Return true without asking for a password if run by UID 0
422 or if PW has an empty password. */
425 correct_password (const struct passwd
*pw
)
428 const struct passwd
*lpw
;
431 retval
= pam_start (simulate_login
? PAM_SERVICE_NAME_L
: PAM_SERVICE_NAME
,
432 pw
->pw_name
, &conv
, &pamh
);
433 PAM_BAIL_P (return false);
435 if (isatty (0) && (cp
= ttyname (0)) != NULL
)
439 if (strncmp (cp
, "/dev/", 5) == 0)
443 retval
= pam_set_item (pamh
, PAM_TTY
, tty
);
444 PAM_BAIL_P (return false);
446 # if 0 /* Manpage discourages use of getlogin. */
448 if (!(cp
&& *cp
&& (lpw
= getpwnam (cp
)) != NULL
&& lpw
->pw_uid
== getuid ()))
450 lpw
= getpwuid (getuid ());
451 if (lpw
&& lpw
->pw_name
)
453 retval
= pam_set_item (pamh
, PAM_RUSER
, (const void *) lpw
->pw_name
);
454 PAM_BAIL_P (return false);
456 retval
= pam_authenticate (pamh
, 0);
457 PAM_BAIL_P (return false);
458 retval
= pam_acct_mgmt (pamh
, 0);
459 if (retval
== PAM_NEW_AUTHTOK_REQD
)
461 /* Password has expired. Offer option to change it. */
462 retval
= pam_chauthtok (pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
463 PAM_BAIL_P (return false);
465 PAM_BAIL_P (return false);
466 /* Must be authenticated if this point was reached. */
469 char *unencrypted
, *encrypted
, *correct
;
470 # if HAVE_GETSPNAM && HAVE_STRUCT_SPWD_SP_PWDP
471 /* Shadow passwd stuff for SVR3 and maybe other systems. */
472 const struct spwd
*sp
= getspnam (pw
->pw_name
);
476 correct
= sp
->sp_pwdp
;
479 correct
= pw
->pw_passwd
;
481 if (getuid () == 0 || !correct
|| correct
[0] == '\0')
484 unencrypted
= getpass (_("Password:"));
487 error (0, 0, _("getpass: cannot open /dev/tty"));
490 encrypted
= crypt (unencrypted
, correct
);
491 memset (unencrypted
, 0, strlen (unencrypted
));
492 return !strcmp (encrypted
, correct
);
493 #endif /* !USE_PAM */
496 /* Add or clear /sbin and /usr/sbin for the su command
499 /* Set if /sbin is found in path. */
500 #define SBIN_MASK 0x01
501 /* Set if /usr/sbin is found in path. */
502 #define USBIN_MASK 0x02
505 addsbin (const char *const path
)
507 unsigned char smask
= 0;
508 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
511 if (!path
|| *path
== 0)
514 tmp
= xstrdup (path
);
516 for (ptr
= strsep (&cur
, ":"); ptr
!= NULL
; ptr
= strsep (&cur
, ":"))
518 if (!strcmp (ptr
, "/sbin"))
520 if (!strcmp (ptr
, "/usr/sbin"))
524 if ((smask
& (USBIN_MASK
|SBIN_MASK
)) == (USBIN_MASK
|SBIN_MASK
))
531 if (!(smask
& USBIN_MASK
))
532 len
+= strlen ("/usr/sbin:");
534 if (!(smask
& SBIN_MASK
))
535 len
+= strlen (":/sbin");
537 ret
= xmalloc (len
+ 1);
542 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
544 if (!strcmp (ptr
, "."))
548 if (!(smask
& USBIN_MASK
) && !strcmp (ptr
, "/bin"))
550 strcat (ret
, "/usr/sbin:");
555 if (!(smask
& SBIN_MASK
) && !strcmp (ptr
, "/usr/bin"))
558 strcat (ret
, ":/sbin");
566 if (!(smask
& USBIN_MASK
))
567 strcat (ret
, ":/usr/sbin");
569 if (!(smask
& SBIN_MASK
))
570 strcat (ret
, ":/sbin");
576 clearsbin (const char *const path
)
578 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
580 if (!path
|| *path
== 0)
587 ret
= xmalloc (strlen (path
) + 1);
590 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
592 if (!strcmp (ptr
, "/sbin"))
594 if (!strcmp (ptr
, "/usr/sbin"))
596 if (!strcmp (ptr
, "/usr/local/sbin"))
607 /* Update `environ' for the new shell based on PW, with SHELL being
608 the value for the SHELL environment variable. */
611 modify_environment (const struct passwd
*pw
, const char *shell
)
615 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
616 Unset all other environment variables. */
617 char const *term
= getenv ("TERM");
619 term
= xstrdup (term
);
620 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
623 xsetenv ("TERM", term
);
624 xsetenv ("HOME", pw
->pw_dir
);
625 xsetenv ("SHELL", shell
);
626 xsetenv ("USER", pw
->pw_name
);
627 xsetenv ("LOGNAME", pw
->pw_name
);
628 xsetenv ("PATH", (pw
->pw_uid
629 ? getdef_str ("PATH", DEFAULT_LOGIN_PATH
)
630 : getdef_str ("SUPATH", DEFAULT_ROOT_LOGIN_PATH
)));
634 /* Set HOME, SHELL, and if not becoming a super-user,
636 if (change_environment
)
638 xsetenv ("HOME", pw
->pw_dir
);
639 xsetenv ("SHELL", shell
);
640 if (getdef_bool ("ALWAYS_SET_PATH", 0))
641 xsetenv ("PATH", (pw
->pw_uid
642 ? getdef_str ("PATH",
644 : getdef_str ("SUPATH",
645 DEFAULT_ROOT_LOGIN_PATH
)));
648 char const *path
= getenv ("PATH");
652 new = clearsbin (path
);
654 new = addsbin (path
);
658 xsetenv ("PATH", new);
664 xsetenv ("USER", pw
->pw_name
);
665 xsetenv ("LOGNAME", pw
->pw_name
);
675 /* Become the user and group(s) specified by PW. */
678 init_groups (const struct passwd
*pw
)
681 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
684 cleanup_pam (PAM_ABORT
);
686 error (EXIT_FAIL
, errno
, _("cannot set groups"));
691 retval
= pam_setcred (pamh
, PAM_ESTABLISH_CRED
);
692 if (retval
!= PAM_SUCCESS
)
693 error (EXIT_FAILURE
, 0, "%s", pam_strerror (pamh
, retval
));
695 _pam_cred_established
= 1;
700 change_identity (const struct passwd
*pw
)
702 if (setgid (pw
->pw_gid
))
703 error (EXIT_FAIL
, errno
, _("cannot set group id"));
704 if (setuid (pw
->pw_uid
))
705 error (EXIT_FAIL
, errno
, _("cannot set user id"));
708 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
709 If COMMAND is nonzero, pass it to the shell with the -c option.
710 Pass ADDITIONAL_ARGS to the shell as more arguments; there
711 are N_ADDITIONAL_ARGS extra arguments. */
714 run_shell (char const *shell
, char const *command
, char **additional_args
,
715 size_t n_additional_args
)
717 size_t n_args
= 1 + fast_startup
+ 2 * !!command
+ n_additional_args
+ 1;
718 char const **args
= xcalloc (n_args
, sizeof *args
);
724 char *shell_basename
;
726 shell_basename
= basename (shell
);
727 arg0
= xmalloc (strlen (shell_basename
) + 2);
729 strcpy (arg0
+ 1, shell_basename
);
733 args
[0] = basename (shell
);
735 args
[argno
++] = "-f";
738 args
[argno
++] = "-c";
739 args
[argno
++] = command
;
741 memcpy (args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
742 args
[argno
+ n_additional_args
] = NULL
;
743 execv (shell
, (char **) args
);
746 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
747 error (0, errno
, "%s", shell
);
752 /* Return true if SHELL is a restricted shell (one not returned by
753 getusershell), else false, meaning it is a standard shell. */
756 restricted_shell (const char *shell
)
761 while ((line
= getusershell ()) != NULL
)
763 if (*line
!= '#' && !strcmp (line
, shell
))
776 if (status
!= EXIT_SUCCESS
)
777 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
781 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name
);
783 Change the effective user id and group id to that of USER.\n\
785 -, -l, --login make the shell a login shell\n\
786 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
787 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
788 and do not create a new session\n\
789 -f, --fast pass -f to the shell (for csh or tcsh)\n\
790 -m, --preserve-environment do not reset environment variables\n\
792 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
794 fputs (_(" -u, --help display this help and exit\n"), stdout
);
795 fputs (_(" -v, --version output version information and exit\n"), stdout
);
798 A mere - implies -l. If USER not given, assume root.\n\
805 main (int argc
, char **argv
)
808 const char *new_user
= DEFAULT_USER
;
809 char *command
= NULL
;
810 int request_same_session
= 0;
813 struct passwd pw_copy
;
815 program_name
= argv
[0];
816 setlocale (LC_ALL
, "");
817 bindtextdomain (PACKAGE
, LOCALEDIR
);
818 textdomain (PACKAGE
);
820 fast_startup
= false;
821 simulate_login
= false;
822 change_environment
= true;
824 while ((optc
= getopt_long (argc
, argv
, "c:flmps:", longopts
, NULL
)) != -1)
834 request_same_session
= 1;
842 simulate_login
= true;
847 change_environment
= false;
858 printf(UTIL_LINUX_VERSION
);
866 if (optind
< argc
&& !strcmp (argv
[optind
], "-"))
868 simulate_login
= true;
872 new_user
= argv
[optind
++];
874 pw
= getpwnam (new_user
);
875 if (! (pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
877 error (EXIT_FAIL
, 0, _("user %s does not exist"), new_user
);
879 /* Make a copy of the password information and point pw at the local
880 copy instead. Otherwise, some systems (e.g. Linux) would clobber
881 the static data through the getlogin call from log_su.
882 Also, make sure pw->pw_shell is a nonempty string.
883 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
884 but that doesn't have a default shell listed. */
887 pw
->pw_name
= xstrdup (pw
->pw_name
);
888 pw
->pw_passwd
= xstrdup (pw
->pw_passwd
);
889 pw
->pw_dir
= xstrdup (pw
->pw_dir
);
890 pw
->pw_shell
= xstrdup (pw
->pw_shell
&& pw
->pw_shell
[0]
895 if (!correct_password (pw
))
897 #ifdef SYSLOG_FAILURE
900 sleep (getdef_num ("FAIL_DELAY", 1));
901 error (EXIT_FAIL
, 0, _("incorrect password"));
903 #ifdef SYSLOG_SUCCESS
910 if (request_same_session
|| !command
|| !pw
->pw_uid
)
913 if (!shell
&& !change_environment
)
914 shell
= getenv ("SHELL");
915 if (shell
&& getuid () != 0 && restricted_shell (pw
->pw_shell
))
917 /* The user being su'd to has a nonstandard shell, and so is
918 probably a uucp account or has restricted access. Don't
919 compromise the account by allowing access with a standard
921 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
924 shell
= xstrdup (shell
? shell
: pw
->pw_shell
);
929 create_watching_parent ();
930 /* Now we're in the child. */
933 change_identity (pw
);
937 /* Set environment after pam_open_session, which may put KRB5CCNAME
938 into the pam_env, etc. */
940 modify_environment (pw
, shell
);
942 if (simulate_login
&& chdir (pw
->pw_dir
) != 0)
943 error (0, errno
, _("warning: cannot change directory to %s"), pw
->pw_dir
);
945 run_shell (shell
, command
, argv
+ optind
, MAX (0, argc
- optind
));
948 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1