1 /* su for Linux. Run a shell with substitute user and group IDs.
2 Copyright (C) 1992-2006 Free Software Foundation, Inc.
3 Copyright (C) 2012 SUSE Linux Products GmbH, Nuernberg
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
19 /* Run a shell with the real and effective UID and GID and groups
20 of USER, default `root'.
22 The shell run is taken from USER's password entry, /bin/sh if
23 none is specified there. If the account has a password, su
24 prompts for a password unless run by a user with real UID 0.
26 Does not change the current directory.
27 Sets `HOME' and `SHELL' from the password entry for USER, and if
28 USER is not root, sets `USER' and `LOGNAME' to USER.
29 The subshell is not a login shell.
31 If one or more ARGs are given, they are passed as additional
32 arguments to the subshell.
34 Does not handle /bin/sh or other shells specially
35 (setting argv[0] to "-su", passing -c only to certain shells, etc.).
36 I don't see the point in doing that, and it's ugly.
38 Based on an implemenation by David MacKenzie <djm@gnu.ai.mit.edu>. */
42 EXIT_CANNOT_INVOKE
= 126,
49 #include <sys/types.h>
52 #include <security/pam_appl.h>
53 #include <security/pam_misc.h>
64 #include "pathnames.h"
66 /* name of the pam configuration files. separate configs for su and su - */
67 #define PAM_SERVICE_NAME "su"
68 #define PAM_SERVICE_NAME_L "su-l"
70 #include "logindefs.h"
72 /* The shell to run if none is given in the user's passwd entry. */
73 #define DEFAULT_SHELL "/bin/sh"
75 /* The user to become if none is specified. */
76 #define DEFAULT_USER "root"
78 extern char **environ
;
80 static void run_shell (char const *, char const *, char **, size_t)
81 __attribute__ ((__noreturn__
));
83 /* If true, pass the `-f' option to the subshell. */
84 static bool fast_startup
;
86 /* If true, simulate a login instead of just starting a shell. */
87 static bool simulate_login
;
89 /* If true, change some environment vars to indicate the user su'd to. */
90 static bool change_environment
;
92 /* If true, then don't call setsid() with a command. */
95 static bool _pam_session_opened
;
96 static bool _pam_cred_established
;
97 static sig_atomic_t volatile caught_signal
= false;
98 static pam_handle_t
*pamh
= NULL
;
101 static struct option
const longopts
[] =
103 {"command", required_argument
, NULL
, 'c'},
104 {"session-command", required_argument
, NULL
, 'C'},
105 {"fast", no_argument
, NULL
, 'f'},
106 {"login", no_argument
, NULL
, 'l'},
107 {"preserve-environment", no_argument
, NULL
, 'p'},
108 {"shell", required_argument
, NULL
, 's'},
109 {"help", no_argument
, 0, 'u'},
110 {"version", no_argument
, 0, 'v'},
114 /* Add NAME=VAL to the environment, checking for out of memory errors. */
117 xsetenv (char const *name
, char const *val
)
119 size_t namelen
= strlen (name
);
120 size_t vallen
= strlen (val
);
121 char *string
= xmalloc (namelen
+ 1 + vallen
+ 1);
122 strcpy (string
, name
);
123 string
[namelen
] = '=';
124 strcpy (string
+ namelen
+ 1, val
);
125 if (putenv (string
) != 0)
126 error (EXIT_FAILURE
, 0, _("out of memory"));
129 /* Log the fact that someone has run su to the user given by PW;
130 if SUCCESSFUL is true, they gave the correct password, etc. */
133 log_su (struct passwd
const *pw
, bool successful
)
135 const char *new_user
, *old_user
, *tty
;
137 new_user
= pw
->pw_name
;
138 /* The utmp entry (via getlogin) is probably the best way to identify
139 the user, especially if someone su's from a su-shell. */
140 old_user
= getlogin ();
143 /* getlogin can fail -- usually due to lack of utmp entry.
144 Resort to getpwuid. */
145 struct passwd
*pwd
= getpwuid (getuid ());
146 old_user
= (pwd
? pwd
->pw_name
: "");
148 tty
= ttyname (STDERR_FILENO
);
152 openlog (program_invocation_short_name
, 0 , LOG_AUTH
);
153 syslog (LOG_NOTICE
, "%s(to %s) %s on %s",
154 successful
? "" : "FAILED SU ",
155 new_user
, old_user
, tty
);
159 static struct pam_conv conv
=
165 # define PAM_BAIL_P(a) \
168 pam_end (pamh, retval); \
173 cleanup_pam (int retcode
)
175 if (_pam_session_opened
)
176 pam_close_session (pamh
, 0);
178 if (_pam_cred_established
)
179 pam_setcred (pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
181 pam_end(pamh
, retcode
);
184 /* Signal handler for parent process. */
186 su_catch_sig (int sig
__attribute__((__unused__
)))
188 caught_signal
= true;
191 /* Export env variables declared by PAM modules. */
197 /* This is a copy but don't care to free as we exec later anyways. */
198 env
= pam_getenvlist (pamh
);
201 if (putenv (*env
) != 0)
202 error (EXIT_FAILURE
, 0, _("out of memory"));
208 create_watching_parent (void)
215 retval
= pam_open_session (pamh
, 0);
216 if (retval
!= PAM_SUCCESS
)
218 cleanup_pam (retval
);
219 error (EXIT_FAILURE
, 0, _("cannot not open session: %s"),
220 pam_strerror (pamh
, retval
));
223 _pam_session_opened
= 1;
226 if (child
== (pid_t
) -1)
228 cleanup_pam (PAM_ABORT
);
229 error (EXIT_FAILURE
, errno
, _("cannot create child process"));
232 /* the child proceeds to run the shell */
236 /* In the parent watch the child. */
238 /* su without pam support does not have a helper that keeps
239 sitting on any directory so let's go to /. */
240 if (chdir ("/") != 0)
241 error (0, errno
, _("warning: cannot change directory to %s"), "/");
243 sigfillset (&ourset
);
244 if (sigprocmask (SIG_BLOCK
, &ourset
, NULL
))
246 error (0, errno
, _("cannot block signals"));
247 caught_signal
= true;
251 struct sigaction action
;
252 action
.sa_handler
= su_catch_sig
;
253 sigemptyset (&action
.sa_mask
);
255 sigemptyset (&ourset
);
258 if (sigaddset(&ourset
, SIGINT
) || sigaddset(&ourset
, SIGQUIT
))
260 error (0, errno
, _("cannot set signal handler"));
261 caught_signal
= true;
264 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
265 || sigaddset(&ourset
, SIGALRM
)
266 || sigaction(SIGTERM
, &action
, NULL
)
267 || sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
))) {
268 error (0, errno
, _("cannot set signal handler"));
269 caught_signal
= true;
271 if (!caught_signal
&& !same_session
&& (sigaction(SIGINT
, &action
, NULL
)
272 || sigaction(SIGQUIT
, &action
, NULL
)))
274 error (0, errno
, _("cannot set signal handler"));
275 caught_signal
= true;
283 pid
= waitpid (child
, &status
, WUNTRACED
);
285 if (pid
!= (pid_t
)-1 && WIFSTOPPED (status
))
287 kill (getpid (), SIGSTOP
);
288 /* once we get here, we must have resumed */
294 if (pid
!= (pid_t
)-1)
295 if (WIFSIGNALED (status
))
296 status
= WTERMSIG (status
) + 128;
298 status
= WEXITSTATUS (status
);
307 fprintf (stderr
, _("\nSession terminated, killing shell..."));
308 kill (child
, SIGTERM
);
311 cleanup_pam (PAM_SUCCESS
);
316 kill (child
, SIGKILL
);
317 fprintf (stderr
, _(" ...killed.\n"));
323 correct_password (const struct passwd
*pw
)
325 const struct passwd
*lpw
;
329 retval
= pam_start (simulate_login
? PAM_SERVICE_NAME_L
: PAM_SERVICE_NAME
,
330 pw
->pw_name
, &conv
, &pamh
);
331 PAM_BAIL_P (return false);
333 if (isatty (0) && (cp
= ttyname (0)) != NULL
)
337 if (strncmp (cp
, "/dev/", 5) == 0)
341 retval
= pam_set_item (pamh
, PAM_TTY
, tty
);
342 PAM_BAIL_P (return false);
344 # if 0 /* Manpage discourages use of getlogin. */
346 if (!(cp
&& *cp
&& (lpw
= getpwnam (cp
)) != NULL
&& lpw
->pw_uid
== getuid ()))
348 lpw
= getpwuid (getuid ());
349 if (lpw
&& lpw
->pw_name
)
351 retval
= pam_set_item (pamh
, PAM_RUSER
, (const void *) lpw
->pw_name
);
352 PAM_BAIL_P (return false);
354 retval
= pam_authenticate (pamh
, 0);
355 PAM_BAIL_P (return false);
356 retval
= pam_acct_mgmt (pamh
, 0);
357 if (retval
== PAM_NEW_AUTHTOK_REQD
)
359 /* Password has expired. Offer option to change it. */
360 retval
= pam_chauthtok (pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
361 PAM_BAIL_P (return false);
363 PAM_BAIL_P (return false);
364 /* Must be authenticated if this point was reached. */
368 /* Add or clear /sbin and /usr/sbin for the su command
371 /* Set if /sbin is found in path. */
372 #define SBIN_MASK 0x01
373 /* Set if /usr/sbin is found in path. */
374 #define USBIN_MASK 0x02
377 addsbin (const char *const path
)
379 unsigned char smask
= 0;
380 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
383 if (!path
|| *path
== 0)
386 tmp
= xstrdup (path
);
388 for (ptr
= strsep (&cur
, ":"); ptr
!= NULL
; ptr
= strsep (&cur
, ":"))
390 if (!strcmp (ptr
, "/sbin"))
392 if (!strcmp (ptr
, "/usr/sbin"))
396 if ((smask
& (USBIN_MASK
|SBIN_MASK
)) == (USBIN_MASK
|SBIN_MASK
))
403 if (!(smask
& USBIN_MASK
))
404 len
+= strlen ("/usr/sbin:");
406 if (!(smask
& SBIN_MASK
))
407 len
+= strlen (":/sbin");
409 ret
= xmalloc (len
+ 1);
414 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
416 if (!strcmp (ptr
, "."))
420 if (!(smask
& USBIN_MASK
) && !strcmp (ptr
, "/bin"))
422 strcat (ret
, "/usr/sbin:");
427 if (!(smask
& SBIN_MASK
) && !strcmp (ptr
, "/usr/bin"))
430 strcat (ret
, ":/sbin");
438 if (!(smask
& USBIN_MASK
))
439 strcat (ret
, ":/usr/sbin");
441 if (!(smask
& SBIN_MASK
))
442 strcat (ret
, ":/sbin");
448 clearsbin (const char *const path
)
450 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
452 if (!path
|| *path
== 0)
459 ret
= xmalloc (strlen (path
) + 1);
462 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
464 if (!strcmp (ptr
, "/sbin"))
466 if (!strcmp (ptr
, "/usr/sbin"))
468 if (!strcmp (ptr
, "/usr/local/sbin"))
480 set_path(const struct passwd
* pw
)
484 r
= logindefs_setenv("PATH", "ENV_PATH", _PATH_DEFPATH
);
486 else if ((r
= logindefs_setenv("PATH", "ENV_ROOTPATH", NULL
)) != 0)
487 r
= logindefs_setenv("PATH", "ENV_SUPATH", _PATH_DEFPATH_ROOT
);
490 err (EXIT_FAILURE
, _("failed to set PATH"));
493 /* Update `environ' for the new shell based on PW, with SHELL being
494 the value for the SHELL environment variable. */
497 modify_environment (const struct passwd
*pw
, const char *shell
)
501 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
502 Unset all other environment variables. */
503 char const *term
= getenv ("TERM");
505 term
= xstrdup (term
);
506 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
509 xsetenv ("TERM", term
);
510 xsetenv ("HOME", pw
->pw_dir
);
511 xsetenv ("SHELL", shell
);
512 xsetenv ("USER", pw
->pw_name
);
513 xsetenv ("LOGNAME", pw
->pw_name
);
518 /* Set HOME, SHELL, and if not becoming a super-user,
520 if (change_environment
)
522 xsetenv ("HOME", pw
->pw_dir
);
523 xsetenv ("SHELL", shell
);
524 if (getlogindefs_bool ("ALWAYS_SET_PATH", 0))
528 char const *path
= getenv ("PATH");
532 new = clearsbin (path
);
534 new = addsbin (path
);
538 xsetenv ("PATH", new);
544 xsetenv ("USER", pw
->pw_name
);
545 xsetenv ("LOGNAME", pw
->pw_name
);
553 /* Become the user and group(s) specified by PW. */
556 init_groups (const struct passwd
*pw
)
560 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
562 cleanup_pam (PAM_ABORT
);
563 error (EXIT_FAILURE
, errno
, _("cannot set groups"));
567 retval
= pam_setcred (pamh
, PAM_ESTABLISH_CRED
);
568 if (retval
!= PAM_SUCCESS
)
569 error (EXIT_FAILURE
, 0, "%s", pam_strerror (pamh
, retval
));
571 _pam_cred_established
= 1;
575 change_identity (const struct passwd
*pw
)
577 if (setgid (pw
->pw_gid
))
578 error (EXIT_FAILURE
, errno
, _("cannot set group id"));
579 if (setuid (pw
->pw_uid
))
580 error (EXIT_FAILURE
, errno
, _("cannot set user id"));
583 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
584 If COMMAND is nonzero, pass it to the shell with the -c option.
585 Pass ADDITIONAL_ARGS to the shell as more arguments; there
586 are N_ADDITIONAL_ARGS extra arguments. */
589 run_shell (char const *shell
, char const *command
, char **additional_args
,
590 size_t n_additional_args
)
592 size_t n_args
= 1 + fast_startup
+ 2 * !!command
+ n_additional_args
+ 1;
593 char const **args
= xcalloc (n_args
, sizeof *args
);
599 char *shell_basename
;
601 shell_basename
= basename (shell
);
602 arg0
= xmalloc (strlen (shell_basename
) + 2);
604 strcpy (arg0
+ 1, shell_basename
);
608 args
[0] = basename (shell
);
610 args
[argno
++] = "-f";
613 args
[argno
++] = "-c";
614 args
[argno
++] = command
;
616 memcpy (args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
617 args
[argno
+ n_additional_args
] = NULL
;
618 execv (shell
, (char **) args
);
621 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
622 error (0, errno
, "%s", shell
);
627 /* Return true if SHELL is a restricted shell (one not returned by
628 getusershell), else false, meaning it is a standard shell. */
631 restricted_shell (const char *shell
)
636 while ((line
= getusershell ()) != NULL
)
638 if (*line
!= '#' && !strcmp (line
, shell
))
651 if (status
!= EXIT_SUCCESS
)
652 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
653 program_invocation_short_name
);
656 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_invocation_short_name
);
658 Change the effective user id and group id to that of USER.\n\
660 -, -l, --login make the shell a login shell\n\
661 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
662 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
663 and do not create a new session\n\
664 -f, --fast pass -f to the shell (for csh or tcsh)\n\
665 -m, --preserve-environment do not reset environment variables\n\
667 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
669 fputs (_(" -u, --help display this help and exit\n"), stdout
);
670 fputs (_(" -v, --version output version information and exit\n"), stdout
);
673 A mere - implies -l. If USER not given, assume root.\n\
679 void load_config(void)
681 logindefs_load_file("/etc/default/su");
682 logindefs_load_file(_PATH_LOGINDEFS
);
686 main (int argc
, char **argv
)
689 const char *new_user
= DEFAULT_USER
;
690 char *command
= NULL
;
691 int request_same_session
= 0;
694 struct passwd pw_copy
;
696 setlocale (LC_ALL
, "");
697 bindtextdomain (PACKAGE
, LOCALEDIR
);
698 textdomain (PACKAGE
);
700 fast_startup
= false;
701 simulate_login
= false;
702 change_environment
= true;
704 while ((optc
= getopt_long (argc
, argv
, "c:flmps:", longopts
, NULL
)) != -1)
714 request_same_session
= 1;
722 simulate_login
= true;
727 change_environment
= false;
738 printf(UTIL_LINUX_VERSION
);
742 usage (EXIT_FAILURE
);
746 if (optind
< argc
&& !strcmp (argv
[optind
], "-"))
748 simulate_login
= true;
752 new_user
= argv
[optind
++];
754 logindefs_load_defaults
= load_config
;
756 pw
= getpwnam (new_user
);
757 if (! (pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
759 error (EXIT_FAILURE
, 0, _("user %s does not exist"), new_user
);
761 /* Make a copy of the password information and point pw at the local
762 copy instead. Otherwise, some systems (e.g. Linux) would clobber
763 the static data through the getlogin call from log_su.
764 Also, make sure pw->pw_shell is a nonempty string.
765 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
766 but that doesn't have a default shell listed. */
769 pw
->pw_name
= xstrdup (pw
->pw_name
);
770 pw
->pw_passwd
= xstrdup (pw
->pw_passwd
);
771 pw
->pw_dir
= xstrdup (pw
->pw_dir
);
772 pw
->pw_shell
= xstrdup (pw
->pw_shell
&& pw
->pw_shell
[0]
777 if (!correct_password (pw
))
780 sleep (getlogindefs_num ("FAIL_DELAY", 1));
781 error (EXIT_FAILURE
, 0, _("incorrect password"));
788 if (request_same_session
|| !command
|| !pw
->pw_uid
)
791 if (!shell
&& !change_environment
)
792 shell
= getenv ("SHELL");
793 if (shell
&& getuid () != 0 && restricted_shell (pw
->pw_shell
))
795 /* The user being su'd to has a nonstandard shell, and so is
796 probably a uucp account or has restricted access. Don't
797 compromise the account by allowing access with a standard
799 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
802 shell
= xstrdup (shell
? shell
: pw
->pw_shell
);
806 create_watching_parent ();
807 /* Now we're in the child. */
809 change_identity (pw
);
813 /* Set environment after pam_open_session, which may put KRB5CCNAME
814 into the pam_env, etc. */
816 modify_environment (pw
, shell
);
818 if (simulate_login
&& chdir (pw
->pw_dir
) != 0)
819 error (0, errno
, _("warning: cannot change directory to %s"), pw
->pw_dir
);
821 run_shell (shell
, command
, argv
+ optind
, max (0, argc
- optind
));
824 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1