#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1988, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n";
#endif
#if 0
#ifndef lint
static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
#endif
#endif
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/usr.bin/su/su.c,v 1.91 2009/12/13 03:14:06 delphij Exp $");
#include <sys/param.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#ifdef USE_BSM_AUDIT
#include <bsm/libbsm.h>
#include <bsm/audit_uevents.h>
#endif
#include <err.h>
#include <errno.h>
#include <grp.h>
#ifndef __APPLE__
#include <login_cap.h>
#endif
#include <paths.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <stdarg.h>
#include <security/pam_appl.h>
#include <security/openpam.h>
#ifdef __APPLE__
#include <bsm/audit_session.h>
#endif
#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)); \
\
} \
} 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;
#ifndef __APPLE__
login_cap_t *lc;
#endif
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, setmaclabel;
#ifndef __APPLE__
u_int setwhat;
#endif
char *username, *class, shellbuf[MAXPATHLEN];
const char *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
#ifdef __APPLE__
const char *avshell;
char avshellbuf[MAXPATHLEN];
#endif
shell = class = cleanenv = NULL;
asme = asthem = fastlogin = statusp = 0;
user = "root";
iscsh = UNSET;
setmaclabel = 0;
#ifdef __APPLE__
while ((ch = getopt(argc, argv, "-flm")) != -1)
#else
while ((ch = getopt(argc, argv, "-flmsc:")) != -1)
#endif
switch ((char)ch) {
case 'f':
fastlogin = 1;
break;
case '-':
case 'l':
asme = 0;
asthem = 1;
break;
case 'm':
asme = 1;
asthem = 0;
break;
#ifndef __APPLE__
case 's':
setmaclabel = 1;
break;
case 'c':
class = optarg;
break;
#endif
case '?':
default:
usage();
}
if (optind < argc)
user = argv[optind++];
if (user == NULL)
usage();
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,
EPERM, 1, "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);
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, EPERM, 1,
"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') {
shell = strncpy(shellbuf, pwd->pw_shell,
sizeof(shellbuf));
shellbuf[sizeof(shellbuf) - 1] = '\0';
}
else {
shell = _PATH_BSHELL;
iscsh = NO;
}
}
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, EPERM, 1, "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, EPERM, 1,
"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, EPERM, 1,
"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, EPERM, 1, "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");
}
#ifndef __APPLE__
if (class == NULL)
lc = login_getpwclass(pwd);
else {
if (ruid != 0) {
#ifdef USE_BSM_AUDIT
if (audit_submit(AUE_su, auid, EPERM, 1,
"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);
}
#endif
if (asme) {
if (ruid != 0 && !chshell(pwd->pw_shell))
errx(1, "permission denied (shell)");
}
else if (pwd->pw_shell && *pwd->pw_shell) {
#ifdef __APPLE__
shell = strncpy(shellbuf, pwd->pw_shell, sizeof(shellbuf));
shellbuf[sizeof(shellbuf) - 1] = '\0';
#else
shell = pwd->pw_shell;
#endif
iscsh = UNSET;
}
else {
shell = _PATH_BSHELL;
iscsh = NO;
}
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);
#ifdef __APPLE__
if (initgroups(user, pwd->pw_gid))
err(1, "initgroups");
#else
if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGROUP) < 0)
err(1, "setusercontext");
#endif
#ifdef __APPLE__
if (asthem) {
auditinfo_addr_t auinfo = {
.ai_termid = { .at_type = AU_IPv4 },
.ai_asid = AU_ASSIGN_ASID,
.ai_auid = getuid(),
.ai_flags = 0,
};
if (setaudit_addr(&auinfo, sizeof(auinfo)) == 0) {
char session[16];
snprintf(session, sizeof(session), "%x", auinfo.ai_asid);
setenv("SECURITYSESSIONID", session, 1);
} else {
errx(1, "failed to create session.");
}
}
#endif
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.");
}
}
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);
#ifdef __APPLE__
if (setgid(pwd->pw_gid))
err(1, "setgid");
if (initgroups(user, pwd->pw_gid))
err(1, "initgroups");
if (setuid(pwd->pw_uid))
err(1, "setuid");
#else
setwhat = LOGIN_SETALL & ~(LOGIN_SETENV | LOGIN_SETUMASK |
LOGIN_SETLOGIN | LOGIN_SETPATH | LOGIN_SETGROUP |
LOGIN_SETMAC);
if (setmaclabel)
setwhat |= LOGIN_SETMAC;
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");
#endif
if (!asme) {
if (asthem) {
p = getenv("TERM");
environ = &cleanenv;
}
if (asthem || pwd->pw_uid)
setenv("USER", pwd->pw_name, 1);
setenv("HOME", pwd->pw_dir, 1);
setenv("SHELL", shell, 1);
#ifdef __APPLE__
unsetenv("TMPDIR");
#endif
if (asthem) {
environ_pam = pam_getenvlist(pamh);
if (environ_pam)
export_pam_environment();
#ifdef __APPLE__
setenv("PATH", "/bin:/usr/bin", 1);
#else
setusercontext(lc, pwd, pwd->pw_uid,
LOGIN_SETPATH | LOGIN_SETUMASK |
LOGIN_SETENV);
#endif
if (p)
setenv("TERM", p, 1);
p = pam_getenv(pamh, "HOME");
if (chdir(p ? p : pwd->pw_dir) < 0)
errx(1, "no directory");
}
}
#ifndef __APPLE__
login_close(lc);
#endif
if (iscsh == YES) {
if (fastlogin)
*np.a-- = "-f";
if (asme)
*np.a-- = "-m";
}
#ifdef __APPLE__
if ((p = strrchr(shell, '/')) != NULL)
avshell = p + 1;
else
avshell = shell;
if (asthem) {
avshellbuf[0] = '-';
strlcpy(avshellbuf+1, avshell, sizeof(avshellbuf) - 1);
avshell = avshellbuf;
}
*np.a = avshell;
#else
*np.a = asthem ? "-su" : iscsh == YES ? "_su" : "su";
#endif
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';
setenv(*pp, p + 1, 1);
}
free(*pp);
}
}
static int
ok_to_export(const char *s)
{
static const char *noexport[] = {
"SHELL", "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)
{
#ifdef __APPLE__
fprintf(stderr, "usage: su [-] [-flm] [login [args]]\n");
#else
fprintf(stderr, "usage: su [-] [-flms] [-c class] [login [args]]\n");
#endif
exit(1);
}
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;
}