2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc.
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * Copyright (c) 1988, 1993, 1994
35 * The Regents of the University of California. All rights reserved.
37 * Redistribution and use in source and binary forms, with or without
38 * modification, are permitted provided that the following conditions
40 * 1. Redistributions of source code must retain the above copyright
41 * notice, this list of conditions and the following disclaimer.
42 * 2. Redistributions in binary form must reproduce the above copyright
43 * notice, this list of conditions and the following disclaimer in the
44 * documentation and/or other materials provided with the distribution.
45 * 3. Neither the name of the University nor the names of its contributors
46 * may be used to endorse or promote products derived from this software
47 * without specific prior written permission.
49 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
50 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
52 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
53 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
55 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
57 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
58 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
63 static const char copyright[] =
64 "@(#) Copyright (c) 1988, 1993, 1994\n\
65 The Regents of the University of California. All rights reserved.\n";
70 static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
74 #include <sys/cdefs.h>
75 __FBSDID("$FreeBSD$");
77 #include <sys/param.h>
79 #include <sys/resource.h>
83 #include <bsm/libbsm.h>
84 #include <bsm/audit_uevents.h>
90 #include <login_cap.h>
101 #include <security/pam_appl.h>
102 #include <security/openpam.h>
104 #define PAM_END() do { \
106 if (pamh != NULL) { \
107 local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
108 if (local_ret != PAM_SUCCESS) \
109 syslog(LOG_ERR, "pam_setcred: %s", \
110 pam_strerror(pamh, local_ret)); \
112 local_ret = pam_close_session(pamh, 0); \
113 if (local_ret != PAM_SUCCESS) \
114 syslog(LOG_ERR, "pam_close_session: %s",\
115 pam_strerror(pamh, local_ret)); \
117 local_ret = pam_end(pamh, local_ret); \
118 if (local_ret != PAM_SUCCESS) \
119 syslog(LOG_ERR, "pam_end: %s", \
120 pam_strerror(pamh, local_ret)); \
125 #define PAM_SET_ITEM(what, item) do { \
127 local_ret = pam_set_item(pamh, what, item); \
128 if (local_ret != PAM_SUCCESS) { \
129 syslog(LOG_ERR, "pam_set_item(" #what "): %s", \
130 pam_strerror(pamh, local_ret)); \
131 errx(1, "pam_set_item(" #what "): %s", \
132 pam_strerror(pamh, local_ret)); \
137 enum tristate { UNSET, YES, NO };
139 static pam_handle_t *pamh = NULL;
140 static char **environ_pam;
142 static char *ontty(void);
143 static int chshell(const char *);
144 static void usage(void) __dead2;
145 static void export_pam_environment(void);
146 static int ok_to_export(const char *);
148 extern char **environ;
151 main(int argc, char *argv[])
153 static char *cleanenv;
154 struct passwd *pwd = NULL;
155 struct pam_conv conv = { openpam_ttyconv, NULL };
163 pid_t child_pid, child_pgrp, pid;
164 int asme, ch, asthem, fastlogin, prio, i, retcode,
165 statusp, setmaclabel;
167 char *username, *class, shellbuf[MAXPATHLEN];
168 const char *p, *user, *shell, *mytty, **nargv;
170 struct sigaction sa, sa_int, sa_quit, sa_pipe;
177 p = shell = class = cleanenv = NULL;
178 asme = asthem = fastlogin = statusp = 0;
183 while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
210 user = argv[optind++];
217 * Try to provide more helpful debugging output if su(1) is running
218 * non-setuid, or was run from a file system not mounted setuid.
221 errx(1, "not running setuid");
224 if (getauid(&auid) < 0 && errno != ENOSYS) {
225 syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno));
226 errx(1, "Permission denied");
229 if (strlen(user) > MAXLOGNAME - 1) {
231 if (audit_submit(AUE_su, auid,
232 EPERM, 1, "username too long: '%s'", user))
233 errx(1, "Permission denied");
235 errx(1, "username too long");
238 nargv = malloc(sizeof(char *) * (size_t)(argc + 4));
240 errx(1, "malloc failure");
242 nargv[argc + 3] = NULL;
243 for (i = argc; i >= optind; i--)
244 nargv[i + 3] = argv[i];
245 np.a = &nargv[i + 3];
250 prio = getpriority(PRIO_PROCESS, 0);
254 setpriority(PRIO_PROCESS, 0, -2);
255 openlog("su", LOG_CONS, LOG_AUTH);
257 /* get current login name, real uid and shell */
259 username = getlogin();
260 if (username != NULL)
261 pwd = getpwnam(username);
262 if (pwd == NULL || pwd->pw_uid != ruid)
263 pwd = getpwuid(ruid);
266 if (audit_submit(AUE_su, auid, EPERM, 1,
267 "unable to determine invoking subject: '%s'", username))
268 errx(1, "Permission denied");
270 errx(1, "who are you?");
273 username = strdup(pwd->pw_name);
274 if (username == NULL)
275 err(1, "strdup failure");
278 if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') {
279 /* must copy - pwd memory is recycled */
280 strlcpy(shellbuf, pwd->pw_shell,
285 shell = _PATH_BSHELL;
290 /* Do the whole PAM startup thing */
291 retcode = pam_start("su", user, &conv, &pamh);
292 if (retcode != PAM_SUCCESS) {
293 syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
294 errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
297 PAM_SET_ITEM(PAM_RUSER, username);
299 mytty = ttyname(STDERR_FILENO);
302 PAM_SET_ITEM(PAM_TTY, mytty);
304 retcode = pam_authenticate(pamh, 0);
305 if (retcode != PAM_SUCCESS) {
307 if (audit_submit(AUE_su, auid, EPERM, 1, "bad su %s to %s on %s",
308 username, user, mytty))
309 errx(1, "Permission denied");
311 syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s",
312 username, user, mytty);
316 if (audit_submit(AUE_su, auid, 0, 0, "successful authentication"))
317 errx(1, "Permission denied");
319 retcode = pam_get_item(pamh, PAM_USER, &v);
320 if (retcode == PAM_SUCCESS)
323 syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
324 pam_strerror(pamh, retcode));
325 pwd = getpwnam(user);
328 if (audit_submit(AUE_su, auid, EPERM, 1,
329 "unknown subject: %s", user))
330 errx(1, "Permission denied");
332 errx(1, "unknown login: %s", user);
335 retcode = pam_acct_mgmt(pamh, 0);
336 if (retcode == PAM_NEW_AUTHTOK_REQD) {
337 retcode = pam_chauthtok(pamh,
338 PAM_CHANGE_EXPIRED_AUTHTOK);
339 if (retcode != PAM_SUCCESS) {
341 aerr = pam_strerror(pamh, retcode);
343 aerr = "Unknown PAM error";
344 if (audit_submit(AUE_su, auid, EPERM, 1,
345 "pam_chauthtok: %s", aerr))
346 errx(1, "Permission denied");
348 syslog(LOG_ERR, "pam_chauthtok: %s",
349 pam_strerror(pamh, retcode));
353 if (retcode != PAM_SUCCESS) {
355 if (audit_submit(AUE_su, auid, EPERM, 1, "pam_acct_mgmt: %s",
356 pam_strerror(pamh, retcode)))
357 errx(1, "Permission denied");
359 syslog(LOG_ERR, "pam_acct_mgmt: %s",
360 pam_strerror(pamh, retcode));
364 /* get target login information */
366 lc = login_getpwclass(pwd);
370 if (audit_submit(AUE_su, auid, EPERM, 1,
371 "only root may use -c"))
372 errx(1, "Permission denied");
374 errx(1, "only root may use -c");
376 lc = login_getclass(class);
378 err(1, "login_getclass");
379 if (lc->lc_class == NULL || strcmp(class, lc->lc_class) != 0)
380 errx(1, "unknown class: %s", class);
383 /* if asme and non-standard target shell, must be root */
385 if (ruid != 0 && !chshell(pwd->pw_shell))
386 errx(1, "permission denied (shell)");
388 else if (pwd->pw_shell && *pwd->pw_shell) {
389 shell = pwd->pw_shell;
393 shell = _PATH_BSHELL;
397 /* if we're forking a csh, we want to slightly muck the args */
398 if (iscsh == UNSET) {
399 p = strrchr(shell, '/');
404 iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES;
406 setpriority(PRIO_PROCESS, 0, prio);
409 * PAM modules might add supplementary groups in pam_setcred(), so
410 * initialize them first.
412 if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
413 err(1, "setusercontext");
415 retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
416 if (retcode != PAM_SUCCESS) {
417 syslog(LOG_ERR, "pam_setcred: %s",
418 pam_strerror(pamh, retcode));
419 errx(1, "failed to establish credentials.");
422 retcode = pam_open_session(pamh, 0);
423 if (retcode != PAM_SUCCESS) {
424 syslog(LOG_ERR, "pam_open_session: %s",
425 pam_strerror(pamh, retcode));
426 errx(1, "failed to open session.");
431 * We must fork() before setuid() because we need to call
432 * pam_setcred(pamh, PAM_DELETE_CRED) as root.
434 sa.sa_flags = SA_RESTART;
435 sa.sa_handler = SIG_IGN;
436 sigemptyset(&sa.sa_mask);
437 sigaction(SIGINT, &sa, &sa_int);
438 sigaction(SIGQUIT, &sa, &sa_quit);
439 sigaction(SIGPIPE, &sa, &sa_pipe);
440 sa.sa_handler = SIG_DFL;
441 sigaction(SIGTSTP, &sa, NULL);
443 if (pipe(fds) == -1) {
450 sa.sa_handler = SIG_IGN;
451 sigaction(SIGTTOU, &sa, NULL);
453 setpgid(child_pid, child_pid);
454 if (tcgetpgrp(STDERR_FILENO) == getpgrp())
455 tcsetpgrp(STDERR_FILENO, child_pid);
457 sigaction(SIGPIPE, &sa_pipe, NULL);
458 while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
459 if (WIFSTOPPED(statusp)) {
460 child_pgrp = getpgid(child_pid);
461 if (tcgetpgrp(STDERR_FILENO) == child_pgrp)
462 tcsetpgrp(STDERR_FILENO, getpgrp());
463 kill(getpid(), SIGSTOP);
464 if (tcgetpgrp(STDERR_FILENO) == getpgrp()) {
465 child_pgrp = getpgid(child_pid);
466 tcsetpgrp(STDERR_FILENO, child_pgrp);
468 kill(child_pid, SIGCONT);
474 tcsetpgrp(STDERR_FILENO, getpgrp());
478 exit(WEXITSTATUS(statusp));
484 read(fds[0], &temp, 1);
486 sigaction(SIGPIPE, &sa_pipe, NULL);
487 sigaction(SIGINT, &sa_int, NULL);
488 sigaction(SIGQUIT, &sa_quit, NULL);
491 * Set all user context except for: Environmental variables
492 * Umask Login records (wtmp, etc) Path
494 setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
495 LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
498 * If -s is present, also set the MAC label.
501 setwhat |= LOGIN_SETMAC;
503 * Don't touch resource/priority settings if -m has been used
504 * or -l and -c hasn't, and we're not su'ing to root.
506 if ((asme || (!asthem && class == NULL)) && pwd->pw_uid)
507 setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES);
508 if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0)
509 err(1, "setusercontext");
517 if (asthem || pwd->pw_uid)
518 setenv("USER", pwd->pw_name, 1);
519 setenv("HOME", pwd->pw_dir, 1);
520 setenv("SHELL", shell, 1);
524 * Add any environmental variables that the
525 * PAM modules may have set.
527 environ_pam = pam_getenvlist(pamh);
529 export_pam_environment();
531 /* set the su'd user's environment & umask */
532 setusercontext(lc, pwd, pwd->pw_uid,
533 LOGIN_SETPATH | LOGIN_SETUMASK |
536 setenv("TERM", p, 1);
538 p = pam_getenv(pamh, "HOME");
539 if (chdir(p ? p : pwd->pw_dir) < 0)
540 errx(1, "no directory");
551 /* csh strips the first character... */
552 *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
555 syslog(LOG_NOTICE, "%s to %s%s", username, user,
564 export_pam_environment(void)
569 for (pp = environ_pam; *pp != NULL; pp++) {
570 if (ok_to_export(*pp)) {
571 p = strchr(*pp, '=');
573 setenv(*pp, p + 1, 1);
580 * Sanity checks on PAM environmental variables:
581 * - Make sure there is an '=' in the string.
582 * - Make sure the string doesn't run on too long.
583 * - Do not export certain variables. This list was taken from the
584 * Solaris pam_putenv(3) man page.
585 * Note that if the user is chrooted, PAM may have a better idea than we
586 * do of where her home directory is.
589 ok_to_export(const char *s)
591 static const char *noexport[] = {
592 "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH",
598 if (strlen(s) > 1024 || strchr(s, '=') == NULL)
600 if (strncmp(s, "LD_", 3) == 0)
602 for (pp = noexport; *pp != NULL; pp++) {
604 if (s[n] == '=' && strncmp(s, *pp, n) == 0)
614 fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
620 chshell(const char *sh)
627 while ((cp = getusershell()) != NULL && !r)
628 r = (strcmp(cp, sh) == 0);
637 static char buf[MAXPATHLEN + 4];
640 p = ttyname(STDERR_FILENO);
642 snprintf(buf, sizeof(buf), " on %s", p);