/* * Copyright (c) 2002, 2005 Networks Associates Technologies, Inc. * All rights reserved. * * Portions of this software were developed for the FreeBSD Project by * ThinkSec AS and NAI Labs, the Security Research Division of Network * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 * ("CBOSS"), as part of the DARPA CHATS research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /*- * Copyright (c) 1988, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)su.c 8.3 (Berkeley) 4/2/94 * $FreeBSD: src/usr.bin/su/su.c,v 1.88 2008/06/04 19:16:54 dwmalone Exp $ * $DragonFly: src/usr.bin/su/su.c,v 1.9 2006/01/12 13:43:11 corecode Exp $ */ #include #include #include #include #ifdef USE_BSM_AUDIT #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAM_END() do { \ int local_ret; \ if (pamh != NULL) { \ local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \ if (local_ret != PAM_SUCCESS) \ syslog(LOG_ERR, "pam_setcred: %s", \ pam_strerror(pamh, local_ret)); \ if (asthem) { \ local_ret = pam_close_session(pamh, 0); \ if (local_ret != PAM_SUCCESS) \ syslog(LOG_ERR, "pam_close_session: %s",\ pam_strerror(pamh, local_ret)); \ } \ local_ret = pam_end(pamh, local_ret); \ if (local_ret != PAM_SUCCESS) \ syslog(LOG_ERR, "pam_end: %s", \ pam_strerror(pamh, local_ret)); \ } \ } while (0) #define PAM_SET_ITEM(what, item) do { \ int local_ret; \ local_ret = pam_set_item(pamh, what, item); \ if (local_ret != PAM_SUCCESS) { \ syslog(LOG_ERR, "pam_set_item(" #what "): %s", \ pam_strerror(pamh, local_ret)); \ errx(1, "pam_set_item(" #what "): %s", \ pam_strerror(pamh, local_ret)); \ /* NOTREACHED */ \ } \ } while (0) enum tristate { UNSET, YES, NO }; static pam_handle_t *pamh = NULL; static char **environ_pam; static char *ontty(void); static int chshell(const char *); static void usage(void) __dead2; static void export_pam_environment(void); static int ok_to_export(const char *); extern char **environ; int main(int argc, char **argv) { static char *cleanenv; struct passwd *pwd; struct pam_conv conv = { openpam_ttyconv, NULL }; enum tristate iscsh; login_cap_t *lc; union { const char **a; char * const *b; } np; uid_t ruid; pid_t child_pid, child_pgrp, pid; int asme, ch, asthem, fastlogin, prio, i, retcode, statusp; u_int setwhat; char *username, *class, shellbuf[MAXPATHLEN]; const char *p = p, *user, *shell, *mytty, **nargv; const void *v; struct sigaction sa, sa_int, sa_quit, sa_pipe; int temp, fds[2]; #ifdef USE_BSM_AUDIT const char *aerr; au_id_t auid; #endif shell = class = cleanenv = NULL; asme = asthem = fastlogin = statusp = 0; user = "root"; iscsh = UNSET; while ((ch = getopt(argc, argv, "-flmc:")) != -1) switch ((char)ch) { case 'f': fastlogin = 1; break; case '-': case 'l': asme = 0; asthem = 1; break; case 'm': asme = 1; asthem = 0; break; case 'c': class = optarg; break; case '?': default: usage(); /* NOTREACHED */ } if (optind < argc && strcmp(argv[optind], "-") == 0) { asme = 0; asthem = 1; optind++; } if (optind < argc) user = argv[optind++]; if (user == NULL) usage(); /* NOTREACHED */ /* * Try to provide more helpful debugging output if su(1) is running * non-setuid, or was run from a file system not mounted setuid. */ if (geteuid() != 0) errx(1, "not running setuid"); #ifdef USE_BSM_AUDIT if (getauid(&auid) < 0 && errno != ENOSYS) { syslog(LOG_AUTH | LOG_ERR, "getauid: %s", strerror(errno)); errx(1, "Permission denied"); } #endif if (strlen(user) > MAXLOGNAME - 1) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "username too long: '%s'", user)) errx(1, "Permission denied"); #endif errx(1, "username too long"); } nargv = malloc(sizeof(char *) * (size_t)(argc + 4)); if (nargv == NULL) errx(1, "malloc failure"); nargv[argc + 3] = NULL; for (i = argc; i >= optind; i--) nargv[i + 3] = argv[i]; np.a = &nargv[i + 3]; argv += optind; errno = 0; prio = getpriority(PRIO_PROCESS, 0); if (errno) prio = 0; setpriority(PRIO_PROCESS, 0, -2); openlog("su", LOG_CONS, LOG_AUTH); /* get current login name, real uid and shell */ ruid = getuid(); username = getlogin(); pwd = getpwnam(username); if (username == NULL || pwd == NULL || pwd->pw_uid != ruid) pwd = getpwuid(ruid); if (pwd == NULL) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "unable to determine invoking subject: '%s'", username)) errx(1, "Permission denied"); #endif errx(1, "who are you?"); } username = strdup(pwd->pw_name); if (username == NULL) err(1, "strdup failure"); if (asme) { if (pwd->pw_shell != NULL && *pwd->pw_shell != '\0') { /* must copy - pwd memory is recycled */ shell = strncpy(shellbuf, pwd->pw_shell, sizeof(shellbuf)); shellbuf[sizeof(shellbuf) - 1] = '\0'; } else { shell = _PATH_BSHELL; iscsh = NO; } } /* Do the whole PAM startup thing */ retcode = pam_start("su", user, &conv, &pamh); if (retcode != PAM_SUCCESS) { syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode)); errx(1, "pam_start: %s", pam_strerror(pamh, retcode)); } PAM_SET_ITEM(PAM_RUSER, username); mytty = ttyname(STDERR_FILENO); if (!mytty) mytty = "tty"; PAM_SET_ITEM(PAM_TTY, mytty); retcode = pam_authenticate(pamh, 0); if (retcode != PAM_SUCCESS) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "bad su %s to %s on %s", username, user, mytty)) errx(1, "Permission denied"); #endif syslog(LOG_AUTH|LOG_WARNING, "BAD SU %s to %s on %s", username, user, mytty); errx(1, "Sorry"); } #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 0, 0, "successful authentication")) errx(1, "Permission denied"); #endif retcode = pam_get_item(pamh, PAM_USER, &v); if (retcode == PAM_SUCCESS) user = v; else syslog(LOG_ERR, "pam_get_item(PAM_USER): %s", pam_strerror(pamh, retcode)); pwd = getpwnam(user); if (pwd == NULL) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "unknown subject: %s", user)) errx(1, "Permission denied"); #endif errx(1, "unknown login: %s", user); } retcode = pam_acct_mgmt(pamh, 0); if (retcode == PAM_NEW_AUTHTOK_REQD) { retcode = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); if (retcode != PAM_SUCCESS) { #ifdef USE_BSM_AUDIT aerr = pam_strerror(pamh, retcode); if (aerr == NULL) aerr = "Unknown PAM error"; if (audit_submit(AUE_su, auid, 1, EPERM, "pam_chauthtok: %s", aerr)) errx(1, "Permission denied"); #endif syslog(LOG_ERR, "pam_chauthtok: %s", pam_strerror(pamh, retcode)); errx(1, "Sorry"); } } if (retcode != PAM_SUCCESS) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "pam_acct_mgmt: %s", pam_strerror(pamh, retcode))) errx(1, "Permission denied"); #endif syslog(LOG_ERR, "pam_acct_mgmt: %s", pam_strerror(pamh, retcode)); errx(1, "Sorry"); } /* get target login information */ if (class == NULL) lc = login_getpwclass(pwd); else { if (ruid != 0) { #ifdef USE_BSM_AUDIT if (audit_submit(AUE_su, auid, 1, EPERM, "only root may use -c")) errx(1, "Permission denied"); #endif errx(1, "only root may use -c"); } lc = login_getclass(class); if (lc == NULL) errx(1, "unknown class: %s", class); } /* if asme and non-standard target shell, must be root */ if (asme) { if (ruid != 0 && !chshell(pwd->pw_shell)) errx(1, "permission denied (shell)"); } else if (pwd->pw_shell && *pwd->pw_shell) { shell = pwd->pw_shell; iscsh = UNSET; } else { shell = _PATH_BSHELL; iscsh = NO; } /* if we're forking a csh, we want to slightly muck the args */ if (iscsh == UNSET) { p = strrchr(shell, '/'); if (p) ++p; else p = shell; iscsh = strcmp(p, "csh") ? (strcmp(p, "tcsh") ? NO : YES) : YES; } setpriority(PRIO_PROCESS, 0, prio); /* * PAM modules might add supplementary groups in pam_setcred(), so * initialize them first. */ if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0) err(1, "setusercontext"); retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED); if (retcode != PAM_SUCCESS) { syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, retcode)); errx(1, "failed to establish credentials."); } if (asthem) { retcode = pam_open_session(pamh, 0); if (retcode != PAM_SUCCESS) { syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, retcode)); errx(1, "failed to open session."); } } /* * We must fork() before setuid() because we need to call * pam_setcred(pamh, PAM_DELETE_CRED) as root. */ sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sigaction(SIGINT, &sa, &sa_int); sigaction(SIGQUIT, &sa, &sa_quit); sigaction(SIGPIPE, &sa, &sa_pipe); sa.sa_handler = SIG_DFL; sigaction(SIGTSTP, &sa, NULL); statusp = 1; if (pipe(fds) == -1) { PAM_END(); err(1, "pipe"); } child_pid = fork(); switch (child_pid) { default: sa.sa_handler = SIG_IGN; sigaction(SIGTTOU, &sa, NULL); close(fds[0]); setpgid(child_pid, child_pid); if (tcgetpgrp(STDERR_FILENO) == getpgrp()) tcsetpgrp(STDERR_FILENO, child_pid); close(fds[1]); sigaction(SIGPIPE, &sa_pipe, NULL); while ((pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) { if (WIFSTOPPED(statusp)) { child_pgrp = getpgid(child_pid); if (tcgetpgrp(STDERR_FILENO) == child_pgrp) tcsetpgrp(STDERR_FILENO, getpgrp()); kill(getpid(), SIGSTOP); if (tcgetpgrp(STDERR_FILENO) == getpgrp()) { child_pgrp = getpgid(child_pid); tcsetpgrp(STDERR_FILENO, child_pgrp); } kill(child_pid, SIGCONT); statusp = 1; continue; } break; } tcsetpgrp(STDERR_FILENO, getpgrp()); if (pid == -1) err(1, "waitpid"); PAM_END(); exit(WEXITSTATUS(statusp)); case -1: PAM_END(); err(1, "fork"); case 0: close(fds[1]); read(fds[0], &temp, 1); close(fds[0]); sigaction(SIGPIPE, &sa_pipe, NULL); sigaction(SIGINT, &sa_int, NULL); sigaction(SIGQUIT, &sa_quit, NULL); /* * Set all user context except for: Environmental variables * Umask Login records (wtmp, etc) Path * XXX Missing LOGIN_SETMAC */ setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK | LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP); #if 0 /* * If -s is present, also set the MAC label. */ if (setmaclabel) setwhat |= LOGIN_SETMAC; #endif /* * Don't touch resource/priority settings if -m has been used * or -l and -c hasn't, and we're not su'ing to root. */ if ((asme || (!asthem && class == NULL)) && pwd->pw_uid) setwhat &= ~(LOGIN_SETPRIORITY | LOGIN_SETRESOURCES); if (setusercontext(lc, pwd, pwd->pw_uid, setwhat) < 0) err(1, "setusercontext"); if (!asme) { if (asthem) { p = getenv("TERM"); environ = &cleanenv; } if (asthem || pwd->pw_uid) { if (setenv("USER", pwd->pw_name, 1) == -1) { err(1, "setenv: cannot set USER=%s", pwd->pw_name); } } if (setenv("HOME", pwd->pw_dir, 1) == -1) { err(1, "setenv: cannot set HOME=%s", pwd->pw_dir); } if (setenv("SHELL", shell, 1) == -1) err(1, "setenv: cannot set SHELL=%s", shell); if (asthem) { /* * Add any environmental variables that the * PAM modules may have set. */ environ_pam = pam_getenvlist(pamh); if (environ_pam) export_pam_environment(); /* set the su'd user's environment & umask */ setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETPATH | LOGIN_SETUMASK | LOGIN_SETENV); if (p) { if (setenv("TERM", p, 1) == -1) { err(1, "setenv: cannot set TERM=%s", p); } } p = pam_getenv(pamh, "HOME"); if (chdir(p ? p : pwd->pw_dir) < 0) errx(1, "no directory"); } } login_close(lc); if (iscsh == YES) { if (fastlogin) *np.a-- = "-f"; if (asme) *np.a-- = "-m"; } /* csh strips the first character... */ *np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su"; if (ruid != 0) syslog(LOG_NOTICE, "%s to %s%s", username, user, ontty()); execv(shell, np.b); err(1, "%s", shell); } } static void export_pam_environment(void) { char **pp; char *p; for (pp = environ_pam; *pp != NULL; pp++) { if (ok_to_export(*pp)) { p = strchr(*pp, '='); *p = '\0'; if (setenv(*pp, p + 1, 1) == -1) err(1, "setenv: cannot set %s=%s", *pp, p + 1); } free(*pp); } } /* * Sanity checks on PAM environmental variables: * - Make sure there is an '=' in the string. * - Make sure the string doesn't run on too long. * - Do not export certain variables. This list was taken from the * Solaris pam_putenv(3) man page. * Note that if the user is chrooted, PAM may have a better idea than we * do of where her home directory is. */ static int ok_to_export(const char *s) { static const char *noexport[] = { "SHELL", /* "HOME", */ "LOGNAME", "MAIL", "CDPATH", "IFS", "PATH", NULL }; const char **pp; size_t n; if (strlen(s) > 1024 || strchr(s, '=') == NULL) return 0; if (strncmp(s, "LD_", 3) == 0) return 0; for (pp = noexport; *pp != NULL; pp++) { n = strlen(*pp); if (s[n] == '=' && strncmp(s, *pp, n) == 0) return 0; } return 1; } static void usage(void) { fprintf(stderr, "usage: su [-] [-flm] [-c class] [login [args]]\n"); exit(1); /* NOTREACHED */ } static int chshell(const char *sh) { int r; char *cp; r = 0; setusershell(); while ((cp = getusershell()) != NULL && !r) r = (strcmp(cp, sh) == 0); endusershell(); return r; } static char * ontty(void) { char *p; static char buf[MAXPATHLEN + 4]; buf[0] = 0; p = ttyname(STDERR_FILENO); if (p) snprintf(buf, sizeof(buf), " on %s", p); return buf; }