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>. */
41 # define MAX(a,b) ((a) > (b) ? (a) : (b))
44 /* Exit statuses for programs like 'env' that exec other programs.
45 EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs. */
49 EXIT_CANNOT_INVOKE
= 126,
56 #include <sys/types.h>
59 #include <security/pam_appl.h>
60 #include <security/pam_misc.h>
70 #include "pathnames.h"
72 /* The official name of this program (e.g., no `g' prefix). */
73 #define PROGRAM_NAME "su"
75 /* name of the pam configuration files. separate configs for su and su - */
76 #define PAM_SERVICE_NAME PROGRAM_NAME
77 #define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
79 #include "logindefs.h"
81 /* The shell to run if none is given in the user's passwd entry. */
82 #define DEFAULT_SHELL "/bin/sh"
84 /* The user to become if none is specified. */
85 #define DEFAULT_USER "root"
87 extern char **environ
;
89 static void run_shell (char const *, char const *, char **, size_t)
90 __attribute__ ((__noreturn__
));
92 /* The name this program was run with. */
95 /* If true, pass the `-f' option to the subshell. */
96 static bool fast_startup
;
98 /* If true, simulate a login instead of just starting a shell. */
99 static bool simulate_login
;
101 /* If true, change some environment vars to indicate the user su'd to. */
102 static bool change_environment
;
104 /* If true, then don't call setsid() with a command. */
105 int same_session
= 0;
107 static bool _pam_session_opened
;
108 static bool _pam_cred_established
;
109 static sig_atomic_t volatile caught_signal
= false;
110 static pam_handle_t
*pamh
= NULL
;
113 static struct option
const longopts
[] =
115 {"command", required_argument
, NULL
, 'c'},
116 {"session-command", required_argument
, NULL
, 'C'},
117 {"fast", no_argument
, NULL
, 'f'},
118 {"login", no_argument
, NULL
, 'l'},
119 {"preserve-environment", no_argument
, NULL
, 'p'},
120 {"shell", required_argument
, NULL
, 's'},
121 {"help", no_argument
, 0, 'u'},
122 {"version", no_argument
, 0, 'v'},
126 /* Add NAME=VAL to the environment, checking for out of memory errors. */
129 xsetenv (char const *name
, char const *val
)
131 size_t namelen
= strlen (name
);
132 size_t vallen
= strlen (val
);
133 char *string
= xmalloc (namelen
+ 1 + vallen
+ 1);
134 strcpy (string
, name
);
135 string
[namelen
] = '=';
136 strcpy (string
+ namelen
+ 1, val
);
137 if (putenv (string
) != 0)
138 error (EXIT_FAILURE
, 0, _("out of memory"));
141 /* Log the fact that someone has run su to the user given by PW;
142 if SUCCESSFUL is true, they gave the correct password, etc. */
145 log_su (struct passwd
const *pw
, bool successful
)
147 const char *new_user
, *old_user
, *tty
;
149 new_user
= pw
->pw_name
;
150 /* The utmp entry (via getlogin) is probably the best way to identify
151 the user, especially if someone su's from a su-shell. */
152 old_user
= getlogin ();
155 /* getlogin can fail -- usually due to lack of utmp entry.
156 Resort to getpwuid. */
157 struct passwd
*pwd
= getpwuid (getuid ());
158 old_user
= (pwd
? pwd
->pw_name
: "");
160 tty
= ttyname (STDERR_FILENO
);
164 openlog (basename (program_name
), 0 , LOG_AUTH
);
165 syslog (LOG_NOTICE
, "%s(to %s) %s on %s",
166 successful
? "" : "FAILED SU ",
167 new_user
, old_user
, tty
);
171 static struct pam_conv conv
=
177 # define PAM_BAIL_P(a) \
180 pam_end (pamh, retval); \
185 cleanup_pam (int retcode
)
187 if (_pam_session_opened
)
188 pam_close_session (pamh
, 0);
190 if (_pam_cred_established
)
191 pam_setcred (pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
193 pam_end(pamh
, retcode
);
196 /* Signal handler for parent process. */
198 su_catch_sig (int sig
__attribute__((__unused__
)))
200 caught_signal
= true;
203 /* Export env variables declared by PAM modules. */
209 /* This is a copy but don't care to free as we exec later anyways. */
210 env
= pam_getenvlist (pamh
);
213 if (putenv (*env
) != 0)
214 error (EXIT_FAILURE
, 0, _("out of memory"));
220 create_watching_parent (void)
227 retval
= pam_open_session (pamh
, 0);
228 if (retval
!= PAM_SUCCESS
)
230 cleanup_pam (retval
);
231 error (EXIT_FAILURE
, 0, _("cannot not open session: %s"),
232 pam_strerror (pamh
, retval
));
235 _pam_session_opened
= 1;
238 if (child
== (pid_t
) -1)
240 cleanup_pam (PAM_ABORT
);
241 error (EXIT_FAILURE
, errno
, _("cannot create child process"));
244 /* the child proceeds to run the shell */
248 /* In the parent watch the child. */
250 /* su without pam support does not have a helper that keeps
251 sitting on any directory so let's go to /. */
252 if (chdir ("/") != 0)
253 error (0, errno
, _("warning: cannot change directory to %s"), "/");
255 sigfillset (&ourset
);
256 if (sigprocmask (SIG_BLOCK
, &ourset
, NULL
))
258 error (0, errno
, _("cannot block signals"));
259 caught_signal
= true;
263 struct sigaction action
;
264 action
.sa_handler
= su_catch_sig
;
265 sigemptyset (&action
.sa_mask
);
267 sigemptyset (&ourset
);
270 if (sigaddset(&ourset
, SIGINT
) || sigaddset(&ourset
, SIGQUIT
))
272 error (0, errno
, _("cannot set signal handler"));
273 caught_signal
= true;
276 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
277 || sigaddset(&ourset
, SIGALRM
)
278 || sigaction(SIGTERM
, &action
, NULL
)
279 || sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
))) {
280 error (0, errno
, _("cannot set signal handler"));
281 caught_signal
= true;
283 if (!caught_signal
&& !same_session
&& (sigaction(SIGINT
, &action
, NULL
)
284 || sigaction(SIGQUIT
, &action
, NULL
)))
286 error (0, errno
, _("cannot set signal handler"));
287 caught_signal
= true;
295 pid
= waitpid (child
, &status
, WUNTRACED
);
297 if (pid
!= (pid_t
)-1 && WIFSTOPPED (status
))
299 kill (getpid (), SIGSTOP
);
300 /* once we get here, we must have resumed */
306 if (pid
!= (pid_t
)-1)
307 if (WIFSIGNALED (status
))
308 status
= WTERMSIG (status
) + 128;
310 status
= WEXITSTATUS (status
);
319 fprintf (stderr
, _("\nSession terminated, killing shell..."));
320 kill (child
, SIGTERM
);
323 cleanup_pam (PAM_SUCCESS
);
328 kill (child
, SIGKILL
);
329 fprintf (stderr
, _(" ...killed.\n"));
335 correct_password (const struct passwd
*pw
)
337 const struct passwd
*lpw
;
341 retval
= pam_start (simulate_login
? PAM_SERVICE_NAME_L
: PAM_SERVICE_NAME
,
342 pw
->pw_name
, &conv
, &pamh
);
343 PAM_BAIL_P (return false);
345 if (isatty (0) && (cp
= ttyname (0)) != NULL
)
349 if (strncmp (cp
, "/dev/", 5) == 0)
353 retval
= pam_set_item (pamh
, PAM_TTY
, tty
);
354 PAM_BAIL_P (return false);
356 # if 0 /* Manpage discourages use of getlogin. */
358 if (!(cp
&& *cp
&& (lpw
= getpwnam (cp
)) != NULL
&& lpw
->pw_uid
== getuid ()))
360 lpw
= getpwuid (getuid ());
361 if (lpw
&& lpw
->pw_name
)
363 retval
= pam_set_item (pamh
, PAM_RUSER
, (const void *) lpw
->pw_name
);
364 PAM_BAIL_P (return false);
366 retval
= pam_authenticate (pamh
, 0);
367 PAM_BAIL_P (return false);
368 retval
= pam_acct_mgmt (pamh
, 0);
369 if (retval
== PAM_NEW_AUTHTOK_REQD
)
371 /* Password has expired. Offer option to change it. */
372 retval
= pam_chauthtok (pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
373 PAM_BAIL_P (return false);
375 PAM_BAIL_P (return false);
376 /* Must be authenticated if this point was reached. */
380 /* Add or clear /sbin and /usr/sbin for the su command
383 /* Set if /sbin is found in path. */
384 #define SBIN_MASK 0x01
385 /* Set if /usr/sbin is found in path. */
386 #define USBIN_MASK 0x02
389 addsbin (const char *const path
)
391 unsigned char smask
= 0;
392 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
395 if (!path
|| *path
== 0)
398 tmp
= xstrdup (path
);
400 for (ptr
= strsep (&cur
, ":"); ptr
!= NULL
; ptr
= strsep (&cur
, ":"))
402 if (!strcmp (ptr
, "/sbin"))
404 if (!strcmp (ptr
, "/usr/sbin"))
408 if ((smask
& (USBIN_MASK
|SBIN_MASK
)) == (USBIN_MASK
|SBIN_MASK
))
415 if (!(smask
& USBIN_MASK
))
416 len
+= strlen ("/usr/sbin:");
418 if (!(smask
& SBIN_MASK
))
419 len
+= strlen (":/sbin");
421 ret
= xmalloc (len
+ 1);
426 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
428 if (!strcmp (ptr
, "."))
432 if (!(smask
& USBIN_MASK
) && !strcmp (ptr
, "/bin"))
434 strcat (ret
, "/usr/sbin:");
439 if (!(smask
& SBIN_MASK
) && !strcmp (ptr
, "/usr/bin"))
442 strcat (ret
, ":/sbin");
450 if (!(smask
& USBIN_MASK
))
451 strcat (ret
, ":/usr/sbin");
453 if (!(smask
& SBIN_MASK
))
454 strcat (ret
, ":/sbin");
460 clearsbin (const char *const path
)
462 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
464 if (!path
|| *path
== 0)
471 ret
= xmalloc (strlen (path
) + 1);
474 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
476 if (!strcmp (ptr
, "/sbin"))
478 if (!strcmp (ptr
, "/usr/sbin"))
480 if (!strcmp (ptr
, "/usr/local/sbin"))
491 /* Update `environ' for the new shell based on PW, with SHELL being
492 the value for the SHELL environment variable. */
495 modify_environment (const struct passwd
*pw
, const char *shell
)
499 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
500 Unset all other environment variables. */
501 char const *term
= getenv ("TERM");
503 term
= xstrdup (term
);
504 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
507 xsetenv ("TERM", term
);
508 xsetenv ("HOME", pw
->pw_dir
);
509 xsetenv ("SHELL", shell
);
510 xsetenv ("USER", pw
->pw_name
);
511 xsetenv ("LOGNAME", pw
->pw_name
);
512 xsetenv ("PATH", (pw
->pw_uid
513 ? getlogindefs_str ("PATH", _PATH_DEFPATH
)
514 : getlogindefs_str ("SUPATH", _PATH_DEFPATH_ROOT
)));
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))
525 xsetenv ("PATH", (pw
->pw_uid
526 ? getlogindefs_str ("PATH",
528 : getlogindefs_str ("SUPATH",
529 _PATH_DEFPATH_ROOT
)));
532 char const *path
= getenv ("PATH");
536 new = clearsbin (path
);
538 new = addsbin (path
);
542 xsetenv ("PATH", new);
548 xsetenv ("USER", pw
->pw_name
);
549 xsetenv ("LOGNAME", pw
->pw_name
);
557 /* Become the user and group(s) specified by PW. */
560 init_groups (const struct passwd
*pw
)
564 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
566 cleanup_pam (PAM_ABORT
);
567 error (EXIT_FAIL
, errno
, _("cannot set groups"));
571 retval
= pam_setcred (pamh
, PAM_ESTABLISH_CRED
);
572 if (retval
!= PAM_SUCCESS
)
573 error (EXIT_FAILURE
, 0, "%s", pam_strerror (pamh
, retval
));
575 _pam_cred_established
= 1;
579 change_identity (const struct passwd
*pw
)
581 if (setgid (pw
->pw_gid
))
582 error (EXIT_FAIL
, errno
, _("cannot set group id"));
583 if (setuid (pw
->pw_uid
))
584 error (EXIT_FAIL
, errno
, _("cannot set user id"));
587 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
588 If COMMAND is nonzero, pass it to the shell with the -c option.
589 Pass ADDITIONAL_ARGS to the shell as more arguments; there
590 are N_ADDITIONAL_ARGS extra arguments. */
593 run_shell (char const *shell
, char const *command
, char **additional_args
,
594 size_t n_additional_args
)
596 size_t n_args
= 1 + fast_startup
+ 2 * !!command
+ n_additional_args
+ 1;
597 char const **args
= xcalloc (n_args
, sizeof *args
);
603 char *shell_basename
;
605 shell_basename
= basename (shell
);
606 arg0
= xmalloc (strlen (shell_basename
) + 2);
608 strcpy (arg0
+ 1, shell_basename
);
612 args
[0] = basename (shell
);
614 args
[argno
++] = "-f";
617 args
[argno
++] = "-c";
618 args
[argno
++] = command
;
620 memcpy (args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
621 args
[argno
+ n_additional_args
] = NULL
;
622 execv (shell
, (char **) args
);
625 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
626 error (0, errno
, "%s", shell
);
631 /* Return true if SHELL is a restricted shell (one not returned by
632 getusershell), else false, meaning it is a standard shell. */
635 restricted_shell (const char *shell
)
640 while ((line
= getusershell ()) != NULL
)
642 if (*line
!= '#' && !strcmp (line
, shell
))
655 if (status
!= EXIT_SUCCESS
)
656 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
660 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name
);
662 Change the effective user id and group id to that of USER.\n\
664 -, -l, --login make the shell a login shell\n\
665 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
666 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
667 and do not create a new session\n\
668 -f, --fast pass -f to the shell (for csh or tcsh)\n\
669 -m, --preserve-environment do not reset environment variables\n\
671 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
673 fputs (_(" -u, --help display this help and exit\n"), stdout
);
674 fputs (_(" -v, --version output version information and exit\n"), stdout
);
677 A mere - implies -l. If USER not given, assume root.\n\
683 void load_config(void)
685 logindefs_load_file("/etc/default/su");
686 logindefs_load_file(_PATH_LOGINDEFS
);
690 main (int argc
, char **argv
)
693 const char *new_user
= DEFAULT_USER
;
694 char *command
= NULL
;
695 int request_same_session
= 0;
698 struct passwd pw_copy
;
700 program_name
= argv
[0];
701 setlocale (LC_ALL
, "");
702 bindtextdomain (PACKAGE
, LOCALEDIR
);
703 textdomain (PACKAGE
);
705 fast_startup
= false;
706 simulate_login
= false;
707 change_environment
= true;
709 while ((optc
= getopt_long (argc
, argv
, "c:flmps:", longopts
, NULL
)) != -1)
719 request_same_session
= 1;
727 simulate_login
= true;
732 change_environment
= false;
743 printf(UTIL_LINUX_VERSION
);
751 if (optind
< argc
&& !strcmp (argv
[optind
], "-"))
753 simulate_login
= true;
757 new_user
= argv
[optind
++];
759 logindefs_load_defaults
= load_config
;
761 pw
= getpwnam (new_user
);
762 if (! (pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
764 error (EXIT_FAIL
, 0, _("user %s does not exist"), new_user
);
766 /* Make a copy of the password information and point pw at the local
767 copy instead. Otherwise, some systems (e.g. Linux) would clobber
768 the static data through the getlogin call from log_su.
769 Also, make sure pw->pw_shell is a nonempty string.
770 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
771 but that doesn't have a default shell listed. */
774 pw
->pw_name
= xstrdup (pw
->pw_name
);
775 pw
->pw_passwd
= xstrdup (pw
->pw_passwd
);
776 pw
->pw_dir
= xstrdup (pw
->pw_dir
);
777 pw
->pw_shell
= xstrdup (pw
->pw_shell
&& pw
->pw_shell
[0]
782 if (!correct_password (pw
))
785 sleep (getlogindefs_num ("FAIL_DELAY", 1));
786 error (EXIT_FAIL
, 0, _("incorrect password"));
793 if (request_same_session
|| !command
|| !pw
->pw_uid
)
796 if (!shell
&& !change_environment
)
797 shell
= getenv ("SHELL");
798 if (shell
&& getuid () != 0 && restricted_shell (pw
->pw_shell
))
800 /* The user being su'd to has a nonstandard shell, and so is
801 probably a uucp account or has restricted access. Don't
802 compromise the account by allowing access with a standard
804 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
807 shell
= xstrdup (shell
? shell
: pw
->pw_shell
);
811 create_watching_parent ();
812 /* Now we're in the child. */
814 change_identity (pw
);
818 /* Set environment after pam_open_session, which may put KRB5CCNAME
819 into the pam_env, etc. */
821 modify_environment (pw
, shell
);
823 if (simulate_login
&& chdir (pw
->pw_dir
) != 0)
824 error (0, errno
, _("warning: cannot change directory to %s"), pw
->pw_dir
);
826 run_shell (shell
, command
, argv
+ optind
, MAX (0, argc
- optind
));
829 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1