#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <unistd.h>
#include <stdlib.h>
#include <crypt.h>
#include <pwd.h>
#include <shadow.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <locale.h>
#include <syslog.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <grp.h>
#include <deflt.h>
#include <limits.h>
#include <errno.h>
#include <stdarg.h>
#include <user_attr.h>
#include <priv.h>
#include <bsm/adt.h>
#include <bsm/adt_event.h>
#include <security/pam_appl.h>
#define PATH "/usr/bin:"
#define SUPATH "/usr/sbin:/usr/bin"
#define SUPRMT "PS1=# "
#define ELIM 128
#define ROOT 0
#ifdef DYNAMIC_SU
#define EMBEDDED_NAME "embedded_su"
#define DEF_ATTEMPTS 3
#endif
#define PW_FALSE 1
#define PW_TRUE 2
#define PW_FAILED 3
#ifndef SLEEPTIME
#define SLEEPTIME 4
#endif
#define DEFAULT_LOGIN "/etc/default/login"
#define DEFFILE "/etc/default/su"
char *Sulog, *Console;
char *Path, *Supath;
static char *initvar;
static char *initenv[] = {
"TZ", "LANG", "LC_CTYPE",
"LC_NUMERIC", "LC_TIME", "LC_COLLATE",
"LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0};
static char mail[30] = { "MAIL=/var/mail/" };
static void envalt(void);
static void log(char *, char *, int);
static void to(int);
enum messagemode { USAGE, ERR, WARN };
static void message(enum messagemode, char *, ...);
static char *alloc_vsprintf(const char *, va_list);
static char *tail(char *);
static void audit_success(int, struct passwd *, boolean_t);
static void audit_logout(adt_session_data_t *, au_event_t);
static void audit_failure(int, struct passwd *, char *, int, boolean_t);
#ifdef DYNAMIC_SU
static void validate(char *, int *, boolean_t);
static int legalenvvar(char *);
static int su_conv(int, const struct pam_message **, struct pam_response **,
void *);
static int emb_su_conv(int, const struct pam_message **, struct pam_response **,
void *);
static void freeresponse(int, struct pam_response **response);
static struct pam_conv pam_conv = {su_conv, NULL};
static struct pam_conv emb_pam_conv = {emb_su_conv, NULL};
static void quotemsg(char *, ...);
static void readinitblock(void);
#else
static void update_audit(struct passwd *pwd);
#endif
static pam_handle_t *pamh = NULL;
struct passwd pwd;
char pwdbuf[1024];
char shell[] = "/usr/bin/sh";
char safe_shell[] = "/sbin/sh";
char su[PATH_MAX] = "su";
char homedir[PATH_MAX] = "HOME=";
char logname[20] = "LOGNAME=";
char *suprmt = SUPRMT;
char termtyp[PATH_MAX] = "TERM=";
char *term;
char shelltyp[PATH_MAX] = "SHELL=";
char *hz;
char tznam[PATH_MAX];
char hzname[10] = "HZ=";
char path[PATH_MAX] = "PATH=";
char supath[PATH_MAX] = "PATH=";
char *envinit[ELIM];
extern char **environ;
char *ttyn;
char *username;
static int dosyslog = 0;
char *myname;
#ifdef DYNAMIC_SU
int pam_flags = 0;
boolean_t embedded = B_FALSE;
#endif
int
main(int argc, char **argv)
{
#ifndef DYNAMIC_SU
struct spwd sp;
char spbuf[1024];
char *password;
#endif
char *nptr;
char *pshell;
int eflag = 0;
int envidx = 0;
uid_t uid;
gid_t gid;
char *dir, *shprog, *name;
char *ptr;
char *prog = argv[0];
#ifdef DYNAMIC_SU
int sleeptime = SLEEPTIME;
char **pam_env = 0;
int flags = 0;
int retcode;
int idx = 0;
userattr_t *user_entry;
char *authname;
#endif
int pw_change = PW_FALSE;
boolean_t isrole = B_FALSE;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
myname = tail(argv[0]);
#ifdef DYNAMIC_SU
if (strcmp(myname, EMBEDDED_NAME) == 0) {
embedded = B_TRUE;
setbuf(stdin, NULL);
setbuf(stdout, NULL);
readinitblock();
}
#endif
if (argc > 1 && *argv[1] == '-') {
if (strlen(argv[1]) == 1) {
eflag++;
argv++;
argc--;
} else {
message(USAGE,
gettext("Usage: %s [-] [ username [ arg ... ] ]"),
prog);
exit(1);
}
}
if (argc > 1) {
if (*argv[1] == '-') {
message(USAGE,
gettext("Usage: %s [-] [ username [ arg ... ] ]"),
prog);
exit(1);
} else
nptr = argv[1];
} else
nptr = "root";
if (defopen(DEFFILE) == 0) {
if ((Sulog = defread("SULOG=")) != NULL)
Sulog = strdup(Sulog);
if ((Console = defread("CONSOLE=")) != NULL)
Console = strdup(Console);
if ((Path = defread("PATH=")) != NULL)
Path = strdup(Path);
if ((Supath = defread("SUPATH=")) != NULL)
Supath = strdup(Supath);
if ((ptr = defread("SYSLOG=")) != NULL)
dosyslog = strcmp(ptr, "YES") == 0;
(void) defopen(NULL);
}
(void) strlcat(path, (Path) ? Path : PATH, sizeof (path));
(void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath));
if ((ttyn = ttyname(0)) == NULL)
if ((ttyn = ttyname(1)) == NULL)
if ((ttyn = ttyname(2)) == NULL)
ttyn = "/dev/???";
if ((username = cuserid(NULL)) == NULL)
username = "(null)";
if (Sulog != NULL) {
(void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT,
(S_IRUSR|S_IWUSR)));
(void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT);
}
#ifdef DYNAMIC_SU
authname = nptr;
if (((user_entry = getusernam(authname)) != NULL)) {
kva_t *attr = user_entry->attr;
char *kv;
if ((kv = kva_match(attr, USERATTR_TYPE_KW)) != NULL &&
((strcmp(kv, USERATTR_TYPE_NONADMIN_KW) == 0) ||
(strcmp(kv, USERATTR_TYPE_ADMIN_KW) == 0))) {
isrole = B_TRUE;
if ((kv = kva_match(attr,
USERATTR_ROLEAUTH_KW)) != NULL &&
strcmp(kv, USERATTR_ROLEAUTH_USER) == 0) {
authname = cuserid(NULL);
}
}
free_userattr(user_entry);
}
if (authname == NULL)
exit(1);
if (pam_start(embedded ? EMBEDDED_NAME : "su", authname,
embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS)
exit(1);
if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS)
exit(1);
if (getpwuid_r(getuid(), &pwd, pwdbuf, sizeof (pwdbuf)) == NULL ||
pam_set_item(pamh, PAM_AUSER, pwd.pw_name) != PAM_SUCCESS)
exit(1);
#endif
openlog("su", LOG_CONS, LOG_AUTH);
#ifdef DYNAMIC_SU
if (defopen(DEFAULT_LOGIN) == 0) {
if ((ptr = defread("SLEEPTIME=")) != NULL) {
sleeptime = atoi(ptr);
if (sleeptime < 0 || sleeptime > 5)
sleeptime = SLEEPTIME;
}
if ((ptr = defread("PASSREQ=")) != NULL &&
strcasecmp("YES", ptr) == 0)
pam_flags |= PAM_DISALLOW_NULL_AUTHTOK;
(void) defopen((char *)NULL);
}
(void) signal(SIGQUIT, SIG_IGN);
(void) signal(SIGINT, SIG_IGN);
if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL)
retcode = PAM_USER_UNKNOWN;
else if ((flags = (getuid() != (uid_t)ROOT)) != 0) {
retcode = pam_authenticate(pamh, pam_flags);
} else
retcode = PAM_SUCCESS;
if (retcode != PAM_SUCCESS) {
audit_failure(PW_FALSE, NULL, nptr, retcode, B_FALSE);
switch (retcode) {
case PAM_USER_UNKNOWN:
closelog();
(void) sleep(sleeptime);
message(ERR, gettext("Unknown id: %s"), nptr);
break;
case PAM_AUTH_ERR:
if (Sulog != NULL)
log(Sulog, nptr, 0);
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s on %s",
pwd.pw_name, username, ttyn);
closelog();
(void) sleep(sleeptime);
message(ERR, gettext("Sorry"));
break;
case PAM_CONV_ERR:
default:
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s on %s",
pwd.pw_name, username, ttyn);
closelog();
(void) sleep(sleeptime);
message(ERR, gettext("Sorry"));
break;
}
(void) signal(SIGQUIT, SIG_DFL);
(void) signal(SIGINT, SIG_DFL);
exit(1);
}
if (flags)
validate(username, &pw_change, isrole);
if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) {
message(ERR, gettext("unable to set credentials"));
exit(2);
}
if (dosyslog)
syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
"'su %s' succeeded for %s on %s",
pwd.pw_name, username, ttyn);
closelog();
(void) signal(SIGQUIT, SIG_DFL);
(void) signal(SIGINT, SIG_DFL);
#else
if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) ||
(getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) {
message(ERR, gettext("Unknown id: %s"), nptr);
audit_failure(PW_FALSE, NULL, nptr, PAM_USER_UNKNOWN, B_FALSE);
closelog();
exit(1);
}
if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT)
goto ok;
password = getpass(gettext("Password:"));
if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) {
(void) memset((void *)spbuf, 0, sizeof (spbuf));
if (Sulog != NULL)
log(Sulog, nptr, 0);
message(ERR, gettext("Sorry"));
audit_failure(PW_FALSE, NULL, nptr, PAM_AUTH_ERR, B_FALSE);
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s on %s",
pwd.pw_name, username, ttyn);
closelog();
exit(2);
}
(void) memset((void *)spbuf, 0, sizeof (spbuf));
ok:
update_audit(&pwd);
if (dosyslog)
syslog(pwd.pw_uid == 0 ? LOG_NOTICE : LOG_INFO,
"'su %s' succeeded for %s on %s",
pwd.pw_name, username, ttyn);
#endif
audit_success(pw_change, &pwd, isrole);
uid = pwd.pw_uid;
gid = pwd.pw_gid;
dir = strdup(pwd.pw_dir);
shprog = strdup(pwd.pw_shell);
name = strdup(pwd.pw_name);
if (Sulog != NULL)
log(Sulog, nptr, 1);
if (setgid(gid) == -1) {
message(ERR, gettext("Invalid GID"));
exit(2);
}
if (!nptr)
exit(2);
if (initgroups(nptr, gid) == -1) {
exit(2);
}
if (setuid(uid) == -1) {
message(ERR, gettext("Invalid UID"));
exit(2);
}
if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) {
char *p;
pshell = shprog;
(void) strcpy(su, eflag ? "-" : "");
if ((p = strrchr(pshell, '/')) != NULL)
(void) strlcat(su, p + 1, sizeof (su));
else
(void) strlcat(su, pshell, sizeof (su));
} else {
pshell = shell;
(void) strcpy(su, eflag ? "-su" : "su");
}
if (eflag) {
int j;
char *var;
if (strlen(dir) == 0) {
(void) strcpy(dir, "/");
message(WARN, gettext("No directory! Using home=/"));
}
(void) strlcat(homedir, dir, sizeof (homedir));
(void) strlcat(logname, name, sizeof (logname));
if ((hz = getenv("HZ")) != NULL)
(void) strlcat(hzname, hz, sizeof (hzname));
(void) strlcat(shelltyp, pshell, sizeof (shelltyp));
if (chdir(dir) < 0) {
message(ERR, gettext("No directory!"));
exit(1);
}
envinit[envidx = 0] = homedir;
envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path);
envinit[++envidx] = logname;
envinit[++envidx] = hzname;
if ((term = getenv("TERM")) != NULL) {
(void) strlcat(termtyp, term, sizeof (termtyp));
envinit[++envidx] = termtyp;
}
envinit[++envidx] = shelltyp;
(void) strlcat(mail, name, sizeof (mail));
envinit[++envidx] = mail;
tznam[0] = '\0';
for (j = 0; initenv[j] != 0; j++) {
if ((initvar = getenv(initenv[j])) != NULL) {
if (initvar[0] == '/') continue;
if (strcmp(initenv[j], "TZ") == 0) {
(void) strcpy(tznam, "TZ=");
(void) strlcat(tznam, initvar,
sizeof (tznam));
} else {
var = (char *)
malloc(strlen(initenv[j])
+ strlen(initvar)
+ 2);
if (var == NULL) {
perror("malloc");
exit(4);
}
(void) strcpy(var, initenv[j]);
(void) strcat(var, "=");
(void) strcat(var, initvar);
envinit[++envidx] = var;
}
}
}
if (tznam[0] == '\0') {
if (defopen(DEFAULT_LOGIN) == 0) {
if ((initvar = defread("TIMEZONE=")) != NULL) {
(void) strcpy(tznam, "TZ=");
(void) strlcat(tznam, initvar,
sizeof (tznam));
}
(void) defopen(NULL);
}
}
if (tznam[0] != '\0')
envinit[++envidx] = tznam;
#ifdef DYNAMIC_SU
if ((pam_env = pam_getenvlist(pamh)) != 0) {
while (pam_env[idx] != 0) {
if (envidx + 2 < ELIM &&
legalenvvar(pam_env[idx])) {
envinit[++envidx] = pam_env[idx];
}
idx++;
}
}
#endif
envinit[++envidx] = NULL;
environ = envinit;
} else {
char **pp = environ, **qq, *p;
while ((p = *pp) != NULL) {
if (*p == 'L' && p[1] == 'D' && p[2] == '_') {
for (qq = pp; (*qq = qq[1]) != NULL; qq++)
;
} else {
pp++;
}
}
}
#ifdef DYNAMIC_SU
if (pamh)
(void) pam_end(pamh, PAM_SUCCESS);
#endif
if (uid == (uid_t)ROOT) {
if (Console != NULL)
if (strcmp(ttyn, Console) != 0) {
(void) signal(SIGALRM, to);
(void) alarm(30);
log(Console, nptr, 1);
(void) alarm(0);
}
if (!eflag)
envalt();
}
(void) signal(SIGXCPU, SIG_DFL);
(void) signal(SIGXFSZ, SIG_DFL);
#ifdef DYNAMIC_SU
if (embedded) {
(void) puts("SUCCESS");
embedded = B_FALSE;
}
#endif
if (argc > 2) {
argv[1] = su;
(void) execv(pshell, &argv[1]);
} else
(void) execl(pshell, su, 0);
if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) &&
(strcmp(safe_shell, pshell) != 0)) {
message(WARN,
gettext("No shell %s. Trying fallback shell %s."),
pshell, safe_shell);
if (eflag) {
(void) strcpy(su, "-sh");
(void) strlcpy(shelltyp + strlen("SHELL="),
safe_shell, sizeof (shelltyp) - strlen("SHELL="));
} else {
(void) strcpy(su, "sh");
}
if (argc > 2) {
argv[1] = su;
(void) execv(safe_shell, &argv[1]);
} else {
(void) execl(safe_shell, su, 0);
}
message(ERR, gettext("Couldn't exec fallback shell %s: %s"),
safe_shell, strerror(errno));
} else {
message(ERR, gettext("No shell"));
}
return (3);
}
static void
envalt(void)
{
if (putenv(supath) != 0) {
message(ERR,
gettext("unable to obtain memory to expand environment"));
exit(4);
}
if (putenv(suprmt) != 0) {
message(ERR,
gettext("unable to obtain memory to expand environment"));
exit(4);
}
}
static void
log(char *where, char *towho, int how)
{
FILE *logf;
time_t now;
struct tm *tmp;
if ((logf = fopen(where, "a")) == NULL)
return;
now = time(0);
tmp = localtime(&now);
(void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n",
tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min,
how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho);
(void) fclose(logf);
}
static void
to(int sig)
{}
static void
audit_success(int pw_change, struct passwd *pwd, boolean_t isrole)
{
adt_session_data_t *ah = NULL;
adt_event_data_t *event;
au_event_t event_id = ADT_su;
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_start_session(ADT_su): %m");
return;
}
if (isrole)
event_id = ADT_role_login;
if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
pwd->pw_gid, NULL, ADT_USER) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"adt_set_user(ADT_su, ADT_FAILURE): %m");
}
if ((event = adt_alloc_event(ah, event_id)) == NULL) {
syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_su): %m");
} else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_put_event(ADT_su, ADT_SUCCESS): %m");
}
if (pw_change == PW_TRUE) {
adt_free_event(event);
if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_alloc_event(ADT_passwd): %m");
} else if (adt_put_event(event, ADT_SUCCESS,
ADT_SUCCESS) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_put_event(ADT_passwd, ADT_SUCCESS): %m");
}
}
adt_free_event(event);
if (adt_audit_state(AUC_AUDITING)) {
audit_logout(ah, event_id);
}
(void) adt_end_session(ah);
}
static void
audit_logout(adt_session_data_t *ah, au_event_t event_id)
{
adt_event_data_t *event;
int status;
pid_t pid;
priv_set_t *priv;
if (event_id == ADT_su) {
event_id = ADT_su_logout;
} else {
event_id = ADT_role_logout;
}
if ((event = adt_alloc_event(ah, event_id)) == NULL) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_alloc_event(ADT_su_logout): %m");
return;
}
if ((priv = priv_allocset()) == NULL) {
syslog(LOG_AUTH | LOG_ALERT,
"su audit_logout: could not alloc basic privs: %m");
adt_free_event(event);
return;
}
if ((pid = fork()) == 0) {
adt_free_event(event);
priv_freeset(priv);
return;
}
if (pid == -1) {
syslog(LOG_AUTH | LOG_ALERT,
"su audit_logout: could not fork: %m");
adt_free_event(event);
priv_freeset(priv);
return;
}
if (chdir("/") != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"su audit_logout: could not chdir /: %m");
}
priv_basicset(priv);
(void) priv_delset(priv, PRIV_PROC_EXEC);
(void) priv_delset(priv, PRIV_PROC_FORK);
(void) priv_delset(priv, PRIV_PROC_INFO);
(void) priv_delset(priv, PRIV_PROC_SESSION);
(void) priv_delset(priv, PRIV_FILE_LINK_ANY);
if ((priv_addset(priv, PRIV_PROC_AUDIT) != 0) ||
(setppriv(PRIV_SET, PRIV_PERMITTED, priv) != 0)) {
syslog(LOG_AUTH | LOG_ALERT,
"su audit_logout: could not reduce privs: %m");
}
closefrom(0);
priv_freeset(priv);
for (;;) {
if (pid != waitpid(pid, &status, WUNTRACED)) {
if (errno == ECHILD) {
break;
}
continue;
}
if (WIFEXITED(status) || WIFSIGNALED(status)) {
break;
} else if (WIFSTOPPED(status)) {
pid_t pgid;
int fd;
void (*sg_handler)();
sg_handler = signal(WSTOPSIG(status), SIG_DFL);
(void) sigsend(P_PGID, getpgrp(), WSTOPSIG(status));
(void) signal(WSTOPSIG(status), sg_handler);
pgid = getpgid(pid);
if ((fd = open("/dev/tty", O_RDWR)) != -1) {
if (tcgetpgrp(fd) == getpgrp()) {
(void) tcsetpgrp(fd, pgid);
}
(void) close(fd);
}
(void) sigsend(P_PGID, pgid, SIGCONT);
}
}
(void) adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS);
adt_free_event(event);
(void) adt_end_session(ah);
exit(WEXITSTATUS(status));
}
static void
audit_failure(int pw_change, struct passwd *pwd, char *user, int pamerr,
boolean_t isrole)
{
adt_session_data_t *ah;
adt_event_data_t *event;
au_event_t event_id = ADT_su;
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_start_session(ADT_su, ADT_FAILURE): %m");
return;
}
if (pwd != NULL) {
if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"adt_set_user(ADT_su, ADT_FAILURE): %m");
}
if (isrole)
event_id = ADT_role_login;
}
if ((event = adt_alloc_event(ah, event_id)) == NULL) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_alloc_event(ADT_su, ADT_FAILURE): %m");
return;
}
if (pwd == NULL) {
event->adt_su.message = user;
}
if (adt_put_event(event, ADT_FAILURE,
ADT_FAIL_PAM + pamerr) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"adt_put_event(ADT_su(ADT_FAIL, %s): %m",
pam_strerror(pamh, pamerr));
}
if (pw_change != PW_FALSE) {
adt_free_event(event);
if ((event = adt_alloc_event(ah, ADT_passwd)) == NULL) {
syslog(LOG_AUTH | LOG_ALERT,
"su: adt_alloc_event(ADT_passwd): %m");
} else if (adt_put_event(event, ADT_FAILURE,
ADT_FAIL_PAM + pamerr) != 0) {
syslog(LOG_AUTH | LOG_ALERT,
"su: adt_put_event(ADT_passwd, ADT_FAILURE): %m");
}
}
adt_free_event(event);
(void) adt_end_session(ah);
}
#ifdef DYNAMIC_SU
static int
su_conv(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *appdata_ptr)
{
const struct pam_message *m;
struct pam_response *r;
char *temp;
int k;
char respbuf[PAM_MAX_RESP_SIZE];
if (num_msg <= 0)
return (PAM_CONV_ERR);
*response = (struct pam_response *)calloc(num_msg,
sizeof (struct pam_response));
if (*response == NULL)
return (PAM_BUF_ERR);
k = num_msg;
m = *msg;
r = *response;
while (k--) {
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
errno = 0;
temp = getpassphrase(m->msg);
if (errno == EINTR)
return (PAM_CONV_ERR);
if (temp != NULL) {
r->resp = strdup(temp);
if (r->resp == NULL) {
freeresponse(num_msg, response);
return (PAM_BUF_ERR);
}
}
break;
case PAM_PROMPT_ECHO_ON:
if (m->msg != NULL) {
(void) fputs(m->msg, stdout);
}
(void) fgets(respbuf, sizeof (respbuf), stdin);
temp = strchr(respbuf, '\n');
if (temp != NULL)
*temp = '\0';
r->resp = strdup(respbuf);
if (r->resp == NULL) {
freeresponse(num_msg, response);
return (PAM_BUF_ERR);
}
break;
case PAM_ERROR_MSG:
if (m->msg != NULL) {
(void) fputs(m->msg, stderr);
(void) fputs("\n", stderr);
}
break;
case PAM_TEXT_INFO:
if (m->msg != NULL) {
(void) fputs(m->msg, stdout);
(void) fputs("\n", stdout);
}
break;
default:
break;
}
m++;
r++;
}
return (PAM_SUCCESS);
}
static int
emb_su_conv(int num_msg, const struct pam_message **msg,
struct pam_response **response, void *appdata_ptr)
{
const struct pam_message *m;
struct pam_response *r;
char *temp;
int k;
char respbuf[PAM_MAX_RESP_SIZE];
if (num_msg <= 0)
return (PAM_CONV_ERR);
*response = (struct pam_response *)calloc(num_msg,
sizeof (struct pam_response));
if (*response == NULL)
return (PAM_BUF_ERR);
(void) printf("CONV %d\n", num_msg);
k = num_msg;
m = *msg;
while (k--) {
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
(void) puts("PAM_PROMPT_ECHO_OFF");
goto msg_common;
case PAM_PROMPT_ECHO_ON:
(void) puts("PAM_PROMPT_ECHO_ON");
goto msg_common;
case PAM_ERROR_MSG:
(void) puts("PAM_ERROR_MSG");
goto msg_common;
case PAM_TEXT_INFO:
(void) puts("PAM_TEXT_INFO");
msg_common:
if (m->msg == NULL)
quotemsg(NULL);
else
quotemsg("%s", m->msg);
break;
default:
break;
}
m++;
}
k = num_msg;
m = *msg;
r = *response;
while (k--) {
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
(void) fgets(respbuf, sizeof (respbuf), stdin);
temp = strchr(respbuf, '\n');
if (temp != NULL)
*temp = '\0';
r->resp = strdup(respbuf);
if (r->resp == NULL) {
freeresponse(num_msg, response);
return (PAM_BUF_ERR);
}
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
break;
default:
break;
}
m++;
r++;
}
return (PAM_SUCCESS);
}
static void
freeresponse(int num_msg, struct pam_response **response)
{
struct pam_response *r;
int i;
r = *response;
for (i = 0; i < num_msg; i++, r++) {
if (r->resp != NULL) {
(void) memset(r->resp, '\0', strlen(r->resp));
free(r->resp);
}
}
free(*response);
*response = NULL;
}
static void
quotemsg(char *fmt, ...)
{
if (fmt != NULL) {
char *msg;
char *p;
boolean_t bol;
va_list v;
va_start(v, fmt);
msg = alloc_vsprintf(fmt, v);
va_end(v);
bol = B_TRUE;
for (p = msg; *p != '\0'; p++) {
if (bol) {
if (*p == '.')
(void) putchar('.');
bol = B_FALSE;
}
(void) putchar(*p);
if (*p == '\n')
bol = B_TRUE;
}
(void) putchar('\n');
free(msg);
}
(void) putchar('.');
(void) putchar('\n');
}
static void
validate(char *usernam, int *pw_change, boolean_t isrole)
{
int error;
int tries;
if ((error = pam_acct_mgmt(pamh, pam_flags)) != PAM_SUCCESS) {
if (Sulog != NULL)
log(Sulog, pwd.pw_name, 0);
if (error == PAM_NEW_AUTHTOK_REQD) {
tries = 0;
message(ERR, gettext("Password for user "
"'%s' has expired"), pwd.pw_name);
while ((error = pam_chauthtok(pamh,
PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) {
if ((error == PAM_AUTHTOK_ERR ||
error == PAM_TRY_AGAIN) &&
(tries++ < DEF_ATTEMPTS)) {
continue;
}
message(ERR, gettext("Sorry"));
audit_failure(PW_FAILED, &pwd, NULL, error,
isrole);
if (dosyslog)
syslog(LOG_CRIT,
"'su %s' failed for %s on %s",
pwd.pw_name, usernam, ttyn);
closelog();
exit(1);
}
*pw_change = PW_TRUE;
return;
} else {
message(ERR, gettext("Sorry"));
audit_failure(PW_FALSE, &pwd, NULL, error, isrole);
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s on %s",
pwd.pw_name, usernam, ttyn);
closelog();
exit(3);
}
}
}
static char *illegal[] = {
"SHELL=",
"HOME=",
"LOGNAME=",
#ifndef NO_MAIL
"MAIL=",
#endif
"CDPATH=",
"IFS=",
"PATH=",
"TZ=",
"HZ=",
"TERM=",
0
};
static int
legalenvvar(char *s)
{
register char **p;
for (p = illegal; *p; p++)
if (strncmp(s, *p, strlen(*p)) == 0)
return (0);
if (s[0] == 'L' && s[1] == 'D' && s[2] == '_')
return (0);
return (1);
}
static void
readinitblock(void)
{
char buf[100];
boolean_t bol;
bol = B_TRUE;
for (;;) {
if (fgets(buf, sizeof (buf), stdin) == NULL)
return;
if (bol && strcmp(buf, ".\n") == 0)
return;
bol = (strchr(buf, '\n') != NULL);
}
}
#else
static void
update_audit(struct passwd *pwd)
{
adt_session_data_t *ah;
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
message(ERR, gettext("Sorry"));
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s "
"cannot start audit session %m",
pwd->pw_name, username);
closelog();
exit(2);
}
if (adt_set_user(ah, pwd->pw_uid, pwd->pw_gid, pwd->pw_uid,
pwd->pw_gid, NULL, ADT_UPDATE) != 0) {
if (dosyslog)
syslog(LOG_CRIT, "'su %s' failed for %s "
"cannot update audit session %m",
pwd->pw_name, username);
closelog();
exit(2);
}
}
#endif
static void
message(enum messagemode mode, char *fmt, ...)
{
char *s;
va_list v;
va_start(v, fmt);
s = alloc_vsprintf(fmt, v);
va_end(v);
#ifdef DYNAMIC_SU
if (embedded) {
if (mode == WARN) {
(void) printf("CONV 1\n");
(void) printf("PAM_ERROR_MSG\n");
} else {
(void) printf("ERROR\n");
}
if (mode == USAGE) {
quotemsg("%s", s);
} else {
quotemsg("%s: %s", myname, s);
}
} else {
#endif
if (mode == USAGE) {
(void) fprintf(stderr, "%s\n", s);
} else {
(void) fprintf(stderr, "%s: %s\n", myname, s);
}
#ifdef DYNAMIC_SU
}
#endif
free(s);
}
static char *
tail(char *a)
{
char *p;
p = strrchr(a, '/');
if (p == NULL)
p = a;
else
p++;
return (p);
}
static char *
alloc_vsprintf(const char *fmt, va_list ap1)
{
va_list ap2;
int n;
char buf[1];
char *s;
va_copy(ap2, ap1);
n = vsnprintf(buf, sizeof (buf), fmt, ap2);
va_end(ap2);
s = malloc(n + 1);
if (s == NULL) {
perror("malloc");
exit(4);
}
(void) vsnprintf(s, n+1, fmt, ap1);
return (s);
}