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>
63 #include <sys/fsuid.h>
73 /* The official name of this program (e.g., no `g' prefix). */
74 #define PROGRAM_NAME "su"
76 /* name of the pam configuration files. separate configs for su and su - */
77 #define PAM_SERVICE_NAME PROGRAM_NAME
78 #define PAM_SERVICE_NAME_L PROGRAM_NAME "-l"
82 /* The default PATH for simulated logins to non-superuser accounts. */
83 #define DEFAULT_LOGIN_PATH "/usr/local/bin:/bin:/usr/bin"
85 /* The default PATH for simulated logins to superuser accounts. */
86 #define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin"
88 /* The shell to run if none is given in the user's passwd entry. */
89 #define DEFAULT_SHELL "/bin/sh"
91 /* The user to become if none is specified. */
92 #define DEFAULT_USER "root"
94 extern char **environ
;
96 static void run_shell (char const *, char const *, char **, size_t)
97 __attribute__ ((__noreturn__
));
99 /* The name this program was run with. */
102 /* If true, pass the `-f' option to the subshell. */
103 static bool fast_startup
;
105 /* If true, simulate a login instead of just starting a shell. */
106 static bool simulate_login
;
108 /* If true, change some environment vars to indicate the user su'd to. */
109 static bool change_environment
;
111 /* If true, then don't call setsid() with a command. */
112 int same_session
= 0;
114 static bool _pam_session_opened
;
115 static bool _pam_cred_established
;
116 static sig_atomic_t volatile caught_signal
= false;
117 static pam_handle_t
*pamh
= NULL
;
120 static struct option
const longopts
[] =
122 {"command", required_argument
, NULL
, 'c'},
123 {"session-command", required_argument
, NULL
, 'C'},
124 {"fast", no_argument
, NULL
, 'f'},
125 {"login", no_argument
, NULL
, 'l'},
126 {"preserve-environment", no_argument
, NULL
, 'p'},
127 {"shell", required_argument
, NULL
, 's'},
128 {"help", no_argument
, 0, 'u'},
129 {"version", no_argument
, 0, 'v'},
133 /* Add NAME=VAL to the environment, checking for out of memory errors. */
136 xsetenv (char const *name
, char const *val
)
138 size_t namelen
= strlen (name
);
139 size_t vallen
= strlen (val
);
140 char *string
= xmalloc (namelen
+ 1 + vallen
+ 1);
141 strcpy (string
, name
);
142 string
[namelen
] = '=';
143 strcpy (string
+ namelen
+ 1, val
);
144 if (putenv (string
) != 0)
145 error (EXIT_FAILURE
, 0, _("out of memory"));
148 /* Log the fact that someone has run su to the user given by PW;
149 if SUCCESSFUL is true, they gave the correct password, etc. */
152 log_su (struct passwd
const *pw
, bool successful
)
154 const char *new_user
, *old_user
, *tty
;
156 new_user
= pw
->pw_name
;
157 /* The utmp entry (via getlogin) is probably the best way to identify
158 the user, especially if someone su's from a su-shell. */
159 old_user
= getlogin ();
162 /* getlogin can fail -- usually due to lack of utmp entry.
163 Resort to getpwuid. */
164 struct passwd
*pwd
= getpwuid (getuid ());
165 old_user
= (pwd
? pwd
->pw_name
: "");
167 tty
= ttyname (STDERR_FILENO
);
171 openlog (basename (program_name
), 0 , LOG_AUTH
);
172 syslog (LOG_NOTICE
, "%s(to %s) %s on %s",
173 successful
? "" : "FAILED SU ",
174 new_user
, old_user
, tty
);
178 static struct pam_conv conv
=
184 # define PAM_BAIL_P(a) \
187 pam_end (pamh, retval); \
192 cleanup_pam (int retcode
)
194 if (_pam_session_opened
)
195 pam_close_session (pamh
, 0);
197 if (_pam_cred_established
)
198 pam_setcred (pamh
, PAM_DELETE_CRED
| PAM_SILENT
);
200 pam_end(pamh
, retcode
);
203 /* Signal handler for parent process. */
205 su_catch_sig (int sig
)
207 caught_signal
= true;
210 /* Export env variables declared by PAM modules. */
216 /* This is a copy but don't care to free as we exec later anyways. */
217 env
= pam_getenvlist (pamh
);
220 if (putenv (*env
) != 0)
221 error (EXIT_FAILURE
, 0, _("out of memory"));
227 create_watching_parent (void)
234 retval
= pam_open_session (pamh
, 0);
235 if (retval
!= PAM_SUCCESS
)
237 cleanup_pam (retval
);
238 error (EXIT_FAILURE
, 0, _("cannot not open session: %s"),
239 pam_strerror (pamh
, retval
));
242 _pam_session_opened
= 1;
245 if (child
== (pid_t
) -1)
247 cleanup_pam (PAM_ABORT
);
248 error (EXIT_FAILURE
, errno
, _("cannot create child process"));
251 /* the child proceeds to run the shell */
255 /* In the parent watch the child. */
257 /* su without pam support does not have a helper that keeps
258 sitting on any directory so let's go to /. */
259 if (chdir ("/") != 0)
260 error (0, errno
, _("warning: cannot change directory to %s"), "/");
262 sigfillset (&ourset
);
263 if (sigprocmask (SIG_BLOCK
, &ourset
, NULL
))
265 error (0, errno
, _("cannot block signals"));
266 caught_signal
= true;
270 struct sigaction action
;
271 action
.sa_handler
= su_catch_sig
;
272 sigemptyset (&action
.sa_mask
);
274 sigemptyset (&ourset
);
277 if (sigaddset(&ourset
, SIGINT
) || sigaddset(&ourset
, SIGQUIT
))
279 error (0, errno
, _("cannot set signal handler"));
280 caught_signal
= true;
283 if (!caught_signal
&& (sigaddset(&ourset
, SIGTERM
)
284 || sigaddset(&ourset
, SIGALRM
)
285 || sigaction(SIGTERM
, &action
, NULL
)
286 || sigprocmask(SIG_UNBLOCK
, &ourset
, NULL
))) {
287 error (0, errno
, _("cannot set signal handler"));
288 caught_signal
= true;
290 if (!caught_signal
&& !same_session
&& (sigaction(SIGINT
, &action
, NULL
)
291 || sigaction(SIGQUIT
, &action
, NULL
)))
293 error (0, errno
, _("cannot set signal handler"));
294 caught_signal
= true;
302 pid
= waitpid (child
, &status
, WUNTRACED
);
304 if (pid
!= (pid_t
)-1 && WIFSTOPPED (status
))
306 kill (getpid (), SIGSTOP
);
307 /* once we get here, we must have resumed */
313 if (pid
!= (pid_t
)-1)
314 if (WIFSIGNALED (status
))
315 status
= WTERMSIG (status
) + 128;
317 status
= WEXITSTATUS (status
);
326 fprintf (stderr
, _("\nSession terminated, killing shell..."));
327 kill (child
, SIGTERM
);
330 cleanup_pam (PAM_SUCCESS
);
335 kill (child
, SIGKILL
);
336 fprintf (stderr
, _(" ...killed.\n"));
342 correct_password (const struct passwd
*pw
)
344 const struct passwd
*lpw
;
348 retval
= pam_start (simulate_login
? PAM_SERVICE_NAME_L
: PAM_SERVICE_NAME
,
349 pw
->pw_name
, &conv
, &pamh
);
350 PAM_BAIL_P (return false);
352 if (isatty (0) && (cp
= ttyname (0)) != NULL
)
356 if (strncmp (cp
, "/dev/", 5) == 0)
360 retval
= pam_set_item (pamh
, PAM_TTY
, tty
);
361 PAM_BAIL_P (return false);
363 # if 0 /* Manpage discourages use of getlogin. */
365 if (!(cp
&& *cp
&& (lpw
= getpwnam (cp
)) != NULL
&& lpw
->pw_uid
== getuid ()))
367 lpw
= getpwuid (getuid ());
368 if (lpw
&& lpw
->pw_name
)
370 retval
= pam_set_item (pamh
, PAM_RUSER
, (const void *) lpw
->pw_name
);
371 PAM_BAIL_P (return false);
373 retval
= pam_authenticate (pamh
, 0);
374 PAM_BAIL_P (return false);
375 retval
= pam_acct_mgmt (pamh
, 0);
376 if (retval
== PAM_NEW_AUTHTOK_REQD
)
378 /* Password has expired. Offer option to change it. */
379 retval
= pam_chauthtok (pamh
, PAM_CHANGE_EXPIRED_AUTHTOK
);
380 PAM_BAIL_P (return false);
382 PAM_BAIL_P (return false);
383 /* Must be authenticated if this point was reached. */
387 /* Add or clear /sbin and /usr/sbin for the su command
390 /* Set if /sbin is found in path. */
391 #define SBIN_MASK 0x01
392 /* Set if /usr/sbin is found in path. */
393 #define USBIN_MASK 0x02
396 addsbin (const char *const path
)
398 unsigned char smask
= 0;
399 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
402 if (!path
|| *path
== 0)
405 tmp
= xstrdup (path
);
407 for (ptr
= strsep (&cur
, ":"); ptr
!= NULL
; ptr
= strsep (&cur
, ":"))
409 if (!strcmp (ptr
, "/sbin"))
411 if (!strcmp (ptr
, "/usr/sbin"))
415 if ((smask
& (USBIN_MASK
|SBIN_MASK
)) == (USBIN_MASK
|SBIN_MASK
))
422 if (!(smask
& USBIN_MASK
))
423 len
+= strlen ("/usr/sbin:");
425 if (!(smask
& SBIN_MASK
))
426 len
+= strlen (":/sbin");
428 ret
= xmalloc (len
+ 1);
433 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
435 if (!strcmp (ptr
, "."))
439 if (!(smask
& USBIN_MASK
) && !strcmp (ptr
, "/bin"))
441 strcat (ret
, "/usr/sbin:");
446 if (!(smask
& SBIN_MASK
) && !strcmp (ptr
, "/usr/bin"))
449 strcat (ret
, ":/sbin");
457 if (!(smask
& USBIN_MASK
))
458 strcat (ret
, ":/usr/sbin");
460 if (!(smask
& SBIN_MASK
))
461 strcat (ret
, ":/sbin");
467 clearsbin (const char *const path
)
469 char *ptr
, *tmp
, *cur
, *ret
= NULL
;
471 if (!path
|| *path
== 0)
478 ret
= xmalloc (strlen (path
) + 1);
481 for (ptr
= strsep (&cur
, ":"); ptr
; ptr
= strsep (&cur
, ":"))
483 if (!strcmp (ptr
, "/sbin"))
485 if (!strcmp (ptr
, "/usr/sbin"))
487 if (!strcmp (ptr
, "/usr/local/sbin"))
498 /* Update `environ' for the new shell based on PW, with SHELL being
499 the value for the SHELL environment variable. */
502 modify_environment (const struct passwd
*pw
, const char *shell
)
506 /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH.
507 Unset all other environment variables. */
508 char const *term
= getenv ("TERM");
510 term
= xstrdup (term
);
511 environ
= xmalloc ((6 + !!term
) * sizeof (char *));
514 xsetenv ("TERM", term
);
515 xsetenv ("HOME", pw
->pw_dir
);
516 xsetenv ("SHELL", shell
);
517 xsetenv ("USER", pw
->pw_name
);
518 xsetenv ("LOGNAME", pw
->pw_name
);
519 xsetenv ("PATH", (pw
->pw_uid
520 ? getdef_str ("PATH", DEFAULT_LOGIN_PATH
)
521 : getdef_str ("SUPATH", DEFAULT_ROOT_LOGIN_PATH
)));
525 /* Set HOME, SHELL, and if not becoming a super-user,
527 if (change_environment
)
529 xsetenv ("HOME", pw
->pw_dir
);
530 xsetenv ("SHELL", shell
);
531 if (getdef_bool ("ALWAYS_SET_PATH", 0))
532 xsetenv ("PATH", (pw
->pw_uid
533 ? getdef_str ("PATH",
535 : getdef_str ("SUPATH",
536 DEFAULT_ROOT_LOGIN_PATH
)));
539 char const *path
= getenv ("PATH");
543 new = clearsbin (path
);
545 new = addsbin (path
);
549 xsetenv ("PATH", new);
555 xsetenv ("USER", pw
->pw_name
);
556 xsetenv ("LOGNAME", pw
->pw_name
);
564 /* Become the user and group(s) specified by PW. */
567 init_groups (const struct passwd
*pw
)
571 if (initgroups (pw
->pw_name
, pw
->pw_gid
) == -1)
573 cleanup_pam (PAM_ABORT
);
574 error (EXIT_FAIL
, errno
, _("cannot set groups"));
578 retval
= pam_setcred (pamh
, PAM_ESTABLISH_CRED
);
579 if (retval
!= PAM_SUCCESS
)
580 error (EXIT_FAILURE
, 0, "%s", pam_strerror (pamh
, retval
));
582 _pam_cred_established
= 1;
586 change_identity (const struct passwd
*pw
)
588 if (setgid (pw
->pw_gid
))
589 error (EXIT_FAIL
, errno
, _("cannot set group id"));
590 if (setuid (pw
->pw_uid
))
591 error (EXIT_FAIL
, errno
, _("cannot set user id"));
594 /* Run SHELL, or DEFAULT_SHELL if SHELL is empty.
595 If COMMAND is nonzero, pass it to the shell with the -c option.
596 Pass ADDITIONAL_ARGS to the shell as more arguments; there
597 are N_ADDITIONAL_ARGS extra arguments. */
600 run_shell (char const *shell
, char const *command
, char **additional_args
,
601 size_t n_additional_args
)
603 size_t n_args
= 1 + fast_startup
+ 2 * !!command
+ n_additional_args
+ 1;
604 char const **args
= xcalloc (n_args
, sizeof *args
);
610 char *shell_basename
;
612 shell_basename
= basename (shell
);
613 arg0
= xmalloc (strlen (shell_basename
) + 2);
615 strcpy (arg0
+ 1, shell_basename
);
619 args
[0] = basename (shell
);
621 args
[argno
++] = "-f";
624 args
[argno
++] = "-c";
625 args
[argno
++] = command
;
627 memcpy (args
+ argno
, additional_args
, n_additional_args
* sizeof *args
);
628 args
[argno
+ n_additional_args
] = NULL
;
629 execv (shell
, (char **) args
);
632 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
633 error (0, errno
, "%s", shell
);
638 /* Return true if SHELL is a restricted shell (one not returned by
639 getusershell), else false, meaning it is a standard shell. */
642 restricted_shell (const char *shell
)
647 while ((line
= getusershell ()) != NULL
)
649 if (*line
!= '#' && !strcmp (line
, shell
))
662 if (status
!= EXIT_SUCCESS
)
663 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
667 printf (_("Usage: %s [OPTION]... [-] [USER [ARG]...]\n"), program_name
);
669 Change the effective user id and group id to that of USER.\n\
671 -, -l, --login make the shell a login shell\n\
672 -c, --command=COMMAND pass a single COMMAND to the shell with -c\n\
673 --session-command=COMMAND pass a single COMMAND to the shell with -c\n\
674 and do not create a new session\n\
675 -f, --fast pass -f to the shell (for csh or tcsh)\n\
676 -m, --preserve-environment do not reset environment variables\n\
678 -s, --shell=SHELL run SHELL if /etc/shells allows it\n\
680 fputs (_(" -u, --help display this help and exit\n"), stdout
);
681 fputs (_(" -v, --version output version information and exit\n"), stdout
);
684 A mere - implies -l. If USER not given, assume root.\n\
691 main (int argc
, char **argv
)
694 const char *new_user
= DEFAULT_USER
;
695 char *command
= NULL
;
696 int request_same_session
= 0;
699 struct passwd pw_copy
;
701 program_name
= argv
[0];
702 setlocale (LC_ALL
, "");
703 bindtextdomain (PACKAGE
, LOCALEDIR
);
704 textdomain (PACKAGE
);
706 fast_startup
= false;
707 simulate_login
= false;
708 change_environment
= true;
710 while ((optc
= getopt_long (argc
, argv
, "c:flmps:", longopts
, NULL
)) != -1)
720 request_same_session
= 1;
728 simulate_login
= true;
733 change_environment
= false;
744 printf(UTIL_LINUX_VERSION
);
752 if (optind
< argc
&& !strcmp (argv
[optind
], "-"))
754 simulate_login
= true;
758 new_user
= argv
[optind
++];
760 pw
= getpwnam (new_user
);
761 if (! (pw
&& pw
->pw_name
&& pw
->pw_name
[0] && pw
->pw_dir
&& pw
->pw_dir
[0]
763 error (EXIT_FAIL
, 0, _("user %s does not exist"), new_user
);
765 /* Make a copy of the password information and point pw at the local
766 copy instead. Otherwise, some systems (e.g. Linux) would clobber
767 the static data through the getlogin call from log_su.
768 Also, make sure pw->pw_shell is a nonempty string.
769 It may be NULL when NEW_USER is a username that is retrieved via NIS (YP),
770 but that doesn't have a default shell listed. */
773 pw
->pw_name
= xstrdup (pw
->pw_name
);
774 pw
->pw_passwd
= xstrdup (pw
->pw_passwd
);
775 pw
->pw_dir
= xstrdup (pw
->pw_dir
);
776 pw
->pw_shell
= xstrdup (pw
->pw_shell
&& pw
->pw_shell
[0]
781 if (!correct_password (pw
))
784 sleep (getdef_num ("FAIL_DELAY", 1));
785 error (EXIT_FAIL
, 0, _("incorrect password"));
792 if (request_same_session
|| !command
|| !pw
->pw_uid
)
795 if (!shell
&& !change_environment
)
796 shell
= getenv ("SHELL");
797 if (shell
&& getuid () != 0 && restricted_shell (pw
->pw_shell
))
799 /* The user being su'd to has a nonstandard shell, and so is
800 probably a uucp account or has restricted access. Don't
801 compromise the account by allowing access with a standard
803 error (0, 0, _("using restricted shell %s"), pw
->pw_shell
);
806 shell
= xstrdup (shell
? shell
: pw
->pw_shell
);
810 create_watching_parent ();
811 /* Now we're in the child. */
813 change_identity (pw
);
817 /* Set environment after pam_open_session, which may put KRB5CCNAME
818 into the pam_env, etc. */
820 modify_environment (pw
, shell
);
822 if (simulate_login
&& chdir (pw
->pw_dir
) != 0)
823 error (0, errno
, _("warning: cannot change directory to %s"), pw
->pw_dir
);
825 run_shell (shell
, command
, argv
+ optind
, MAX (0, argc
- optind
));
828 // vim: sw=2 cinoptions=>4,n-2,{2,^-2,\:2,=2,g0,h2,p5,t0,+2,(0,u0,w1,m1