#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1988, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n";
#endif
#ifndef lint
#if 0
static char sccsid[] = "@(#)su.c 8.3 (Berkeley) 4/2/94";
#endif
static const char rcsid[] =
"$FreeBSD: src/usr.bin/su/su.c,v 1.48 2002/01/24 16:20:17 des Exp $";
#endif
#include <sys/param.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#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 <pam/pam_appl.h>
#include <pam/pam_misc.h>
#include <bsm/libbsm.h>
#include <bsm/audit_uevents.h>
#define PAM_END() do { \
int local_ret; \
if (pamh != NULL && creds_set) { \
local_ret = pam_setcred(pamh, PAM_DELETE_CRED); \
if (local_ret != PAM_SUCCESS) \
syslog(LOG_ERR, "pam_setcred: %s", \
pam_strerror(pamh, local_ret)); \
local_ret = pam_close_session(pamh, 0); \
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 int creds_set = 0;
static char **environ_pam;
static char *ontty(void);
static int chshell(char *);
static void usage(void);
static int export_pam_environment(void);
static int ok_to_export(const char *);
void audit_success(struct passwd *pwd);
void audit_fail(struct passwd *pwd, char *errmsg);
extern char **environ;
int
main(int argc, char *argv[])
{
struct passwd *pwd;
struct pam_conv conv = {misc_conv, NULL};
enum tristate iscsh;
union {
const char **a;
char * const *b;
} np;
uid_t ruid;
gid_t gid;
int asme, ch, asthem, fastlogin, prio, i, setwhat, retcode,
statusp, child_pid, child_pgrp, ret_pid;
char *username, *cleanenv, *class, shellbuf[MAXPATHLEN];
const char *p, *user, *shell, *mytty, **nargv;
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();
}
if (optind < argc)
user = argv[optind++];
if (user == NULL)
usage();
if (strlen(user) > MAXLOGNAME - 1) {
audit_fail(NULL, "username too long");
errx(1, "username too long");
}
nargv = malloc(sizeof(char *) * (argc + 4));
if (nargv == NULL) {
audit_fail(NULL, "malloc failure");
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) {
audit_fail(NULL, "who are you?");
errx(1, "who are you?");
}
gid = pwd->pw_gid;
username = strdup(pwd->pw_name);
if (username == NULL) {
audit_fail(NULL, "strdup failure");
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;
}
}
pwd = getpwnam(user);
retcode = pam_start("su", user, &conv, &pamh);
if (retcode != PAM_SUCCESS) {
syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, retcode));
audit_fail(pwd, "pam_start error");
errx(1, "pam_start: %s", pam_strerror(pamh, retcode));
}
PAM_SET_ITEM(PAM_RUSER, getlogin());
mytty = ttyname(STDERR_FILENO);
if (!mytty)
mytty = "tty";
PAM_SET_ITEM(PAM_TTY, mytty);
retcode = pam_authenticate(pamh, 0);
if (retcode != PAM_SUCCESS) {
syslog(LOG_ERR, "pam_authenticate: %s",
pam_strerror(pamh, retcode));
audit_fail(pwd, "pam_authenticate error");
errx(1, "Sorry");
}
retcode = pam_get_item(pamh, PAM_USER, (const void **)&p);
if (retcode == PAM_SUCCESS)
user = p;
else
syslog(LOG_ERR, "pam_get_item(PAM_USER): %s",
pam_strerror(pamh, retcode));
retcode = pam_acct_mgmt(pamh, 0);
if (retcode == PAM_NEW_AUTHTOK_REQD) {
retcode = pam_chauthtok(pamh,
PAM_CHANGE_EXPIRED_AUTHTOK);
if (retcode != PAM_SUCCESS) {
syslog(LOG_ERR, "pam_chauthtok: %s",
pam_strerror(pamh, retcode));
audit_fail(pwd, "pam_chauthtok error");
errx(1, "Sorry");
}
}
if (retcode != PAM_SUCCESS) {
syslog(LOG_ERR, "pam_acct_mgmt: %s",
pam_strerror(pamh, retcode));
audit_fail(pwd, "pam_acct_mgmt error");
errx(1, "Sorry");
}
pwd = getpwnam(user);
if (pwd == NULL) {
audit_fail(NULL, "unknown login");
errx(1, "unknown login: %s", user);
}
if (asme) {
if (ruid != 0 && !chshell(pwd->pw_shell)) {
audit_fail(pwd, "permission denied (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 (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);
if( initgroups(user, pwd->pw_gid) ) {
audit_fail(pwd, "initgroups failed");
err(1, "initgroups failed");
}
retcode = pam_open_session(pamh, 0);
if( retcode != PAM_SUCCESS ) {
syslog(LOG_ERR, "pam_open_session(pamh, 0): %s",
pam_strerror(pamh, retcode));
}
retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
if (retcode != PAM_SUCCESS)
syslog(LOG_ERR, "pam_setcred(pamh, PAM_ESTABLISH_CRED): %s",
pam_strerror(pamh, retcode));
else
creds_set = 1;
statusp = 1;
child_pid = fork();
switch (child_pid) {
default:
while ((ret_pid = waitpid(child_pid, &statusp, WUNTRACED)) != -1) {
if (WIFSTOPPED(statusp)) {
child_pgrp = tcgetpgrp(1);
kill(getpid(), SIGSTOP);
tcsetpgrp(1, child_pgrp);
kill(child_pid, SIGCONT);
statusp = 1;
continue;
}
break;
}
if (ret_pid == -1)
err(1, "waitpid");
PAM_END();
exit(WEXITSTATUS(statusp));
case -1:
audit_fail(pwd, "fork() error");
err(1, "fork");
PAM_END();
exit(1);
case 0:
audit_success(pwd);
if( setgid(pwd->pw_gid) )
err(1, "setgid");
if( setuid(pwd->pw_uid) )
err(1, "setuid");
if (!asme) {
if (asthem) {
p = getenv("TERM");
*environ = NULL;
environ_pam = pam_getenvlist(pamh);
if (environ_pam)
export_pam_environment();
if (p)
setenv("TERM", p, 1);
if (chdir(pwd->pw_dir) < 0)
errx(1, "no directory");
}
if (asthem || pwd->pw_uid)
setenv("USER", pwd->pw_name, 1);
setenv("HOME", pwd->pw_dir, 1);
setenv("SHELL", shell, 1);
}
if (iscsh == YES) {
if (fastlogin)
*np.a-- = "-f";
if (asme)
*np.a-- = "-m";
}
*np.a = asthem ? "-su" : "su";
if (ruid != 0)
syslog(LOG_NOTICE, "%s to %s%s", username, user,
ontty());
execv(shell, np.b);
err(1, "%s", shell);
}
}
static int
export_pam_environment(void)
{
char **pp;
for (pp = environ_pam; *pp != NULL; pp++) {
if (ok_to_export(*pp))
putenv(*pp);
free(*pp);
}
return PAM_SUCCESS;
}
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);
}
static int
chshell(char *sh)
{
int r;
char *cp;
r = 0;
setusershell();
do {
cp = getusershell();
r = strcmp(cp, sh);
} while (!r && cp != NULL);
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;
}
void audit_success(struct passwd *pwd)
{
int aufd;
token_t *tok;
auditinfo_t auinfo;
long au_cond;
uid_t uid = pwd->pw_uid;
gid_t gid = pwd->pw_gid;
pid_t pid = getpid();
if (auditon(A_GETCOND, &au_cond, sizeof(long)) < 0) {
fprintf(stderr, "su: Could not determine audit condition\n");
exit(1);
}
if (au_cond == AUC_NOAUDIT)
return;
if(getaudit(&auinfo) != 0) {
fprintf(stderr, "su: getaudit failed: %s\n", strerror(errno));
exit(1);
}
if((aufd = au_open()) == -1) {
fprintf(stderr, "su: Audit Error: au_open() failed\n");
exit(1);
}
if((tok = au_to_subject32(auinfo.ai_auid, geteuid(), getegid(),
uid, gid, pid, auinfo.ai_asid, &auinfo.ai_termid)) == NULL) {
fprintf(stderr, "su: Audit Error: au_to_subject32() failed\n");
exit(1);
}
au_write(aufd, tok);
if((tok = au_to_return32(0, 0)) == NULL) {
fprintf(stderr, "su: Audit Error: au_to_return32() failed\n");
exit(1);
}
au_write(aufd, tok);
if(au_close(aufd, 1, AUE_su) == -1) {
fprintf(stderr, "su: Audit Error: au_close() failed\n");
exit(1);
}
return;
}
void audit_fail(struct passwd *pwd, char *errmsg)
{
int aufd;
token_t *tok;
auditinfo_t auinfo;
long au_cond;
uid_t uid = pwd ? pwd->pw_uid : -1;
gid_t gid = pwd ? pwd->pw_gid : -1;
pid_t pid = getpid();
if (auditon(A_GETCOND, &au_cond, sizeof(long)) < 0) {
fprintf(stderr, "su: Could not determine audit condition\n");
exit(1);
}
if (au_cond == AUC_NOAUDIT)
return;
if(getaudit(&auinfo) != 0) {
fprintf(stderr, "su: getaudit failed: %s\n", strerror(errno));
exit(1);
}
if((aufd = au_open()) == -1) {
fprintf(stderr, "su: Audit Error: au_open() failed\n");
exit(1);
}
if((tok = au_to_subject32(auinfo.ai_auid, geteuid(), getegid(),
uid, gid, pid, auinfo.ai_asid, &auinfo.ai_termid)) == NULL) {
fprintf(stderr, "su: Audit Error: au_to_subject32() failed\n");
exit(1);
}
au_write(aufd, tok);
if((tok = au_to_text(errmsg)) == NULL) {
fprintf(stderr, "su: Audit Error: au_to_text() failed\n");
exit(1);
}
au_write(aufd, tok);
if((tok = au_to_return32(1, errno)) == NULL) {
fprintf(stderr, "su: Audit Error: au_to_return32() failed\n");
exit(1);
}
au_write(aufd, tok);
if(au_close(aufd, 1, AUE_su) == -1) {
fprintf(stderr, "su: Audit Error: au_close() failed\n");
exit(1);
}
return;
}