#include <nss_dbdefs.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <auth_attr.h>
#include <deflt.h>
#include <priv.h>
#include <secdb.h>
#include <user_attr.h>
#include <sys/task.h>
#include <libintl.h>
#include <project.h>
#include <errno.h>
#include <alloca.h>
#include <bsm/adt.h>
#include <bsm/adt_event.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_impl.h>
#define PROJECT "project="
#define PROJSZ (sizeof (PROJECT) - 1)
int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
return (PAM_IGNORE);
}
static int
getset(char *str, priv_set_t **res)
{
priv_set_t *tmp;
char *badp;
int len;
if (str == NULL)
return (0);
len = strlen(str) + 1;
badp = alloca(len);
(void) memset(badp, '\0', len);
do {
const char *q, *endp;
tmp = priv_str_to_set(str, ",", &endp);
if (tmp == NULL) {
if (endp == NULL)
break;
q = strchr(endp, ',');
if (q == NULL)
q = endp + strlen(endp);
if (*badp != '\0')
(void) strlcat(badp, ",", len);
(void) strncat(badp, endp, q - endp);
(void) memmove((void *)endp, q, strlen(q) + 1);
}
} while (tmp == NULL && *str != '\0');
if (tmp == NULL) {
syslog(LOG_AUTH|LOG_ERR,
"pam_setcred: can't parse privilege specification: %m\n");
return (-1);
} else if (*badp != '\0') {
syslog(LOG_AUTH|LOG_DEBUG,
"pam_setcred: unrecognized privilege(s): %s\n", badp);
}
*res = tmp;
return (0);
}
typedef struct deflim {
char *def;
char *lim;
} deflim_t;
static int
finddeflim(const char *name, kva_t *kva, void *ctxt, void *pres)
{
deflim_t *pdef = pres;
char *val;
if (pdef->def == NULL) {
val = kva_match(kva, USERATTR_DFLTPRIV_KW);
if (val != NULL)
pdef->def = strdup(val);
}
if (pdef->lim == NULL) {
val = kva_match(kva, USERATTR_LIMPRIV_KW);
if (val != NULL)
pdef->lim = strdup(val);
}
return (pdef->lim != NULL && pdef->def != NULL);
}
int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
int i;
int debug = 0;
uint_t nowarn = flags & PAM_SILENT;
int ret = PAM_SUCCESS;
const char *user;
const char *auser;
const char *rhost;
const char *tty;
au_id_t auid;
adt_session_data_t *ah;
adt_termid_t *termid = NULL;
priv_set_t *lim, *def, *tset;
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
char buf[PROJECT_BUFSZ];
struct project proj, *pproj;
int error;
char *projname;
const char *kvs;
char *nckvs = NULL;
struct passwd pwd;
char pwbuf[NSS_BUFLEN_PASSWD];
deflim_t deflim;
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0)
debug = 1;
else if (strcmp(argv[i], "nowarn") == 0)
nowarn |= 1;
}
if (debug)
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: pam_sm_setcred(flags = %x, argc= %d)",
flags, argc);
(void) pam_get_item(pamh, PAM_USER, (const void **)&user);
if (user == NULL || *user == '\0') {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: USER NULL or empty!\n");
return (PAM_USER_UNKNOWN);
}
(void) pam_get_item(pamh, PAM_AUSER, (const void **)&auser);
(void) pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
(void) pam_get_item(pamh, PAM_TTY, (const void **)&tty);
if (debug)
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: user = %s, auser = %s, rhost = %s, "
"tty = %s", user,
(auser == NULL) ? "NULL" : (*auser == '\0') ? "ZERO" :
auser,
(rhost == NULL) ? "NULL" : (*rhost == '\0') ? "ZERO" :
rhost,
(tty == NULL) ? "NULL" : (*tty == '\0') ? "ZERO" :
tty);
switch (flags & (PAM_ESTABLISH_CRED | PAM_DELETE_CRED |
PAM_REINITIALIZE_CRED | PAM_REFRESH_CRED)) {
case 0:
flags |= PAM_ESTABLISH_CRED;
break;
case PAM_ESTABLISH_CRED:
case PAM_REINITIALIZE_CRED:
case PAM_REFRESH_CRED:
break;
case PAM_DELETE_CRED:
return (PAM_SUCCESS);
default:
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: invalid flags %x", flags);
return (PAM_SYSTEM_ERR);
}
if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot create start audit session %m");
return (PAM_SYSTEM_ERR);
}
adt_get_auid(ah, &auid);
if (debug) {
int auditstate;
if (auditon(A_GETCOND, (caddr_t)&auditstate,
sizeof (auditstate)) != 0) {
auditstate = AUC_DISABLED;
}
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: state = %d, auid = %d", auditstate,
auid);
}
if (getpwnam_r(user, &pwd, pwbuf, sizeof (pwbuf)) == NULL) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot get passwd entry for user = %s",
user);
ret = PAM_USER_UNKNOWN;
goto adt_done;
}
if ((auid == AU_NOAUDITID) &&
(flags & PAM_ESTABLISH_CRED)) {
struct passwd apwd;
char apwbuf[NSS_BUFLEN_PASSWD];
errno = 0;
if ((rhost == NULL || *rhost == '\0')) {
if (adt_load_ttyname(tty, &termid) != 0) {
if (errno == ENETDOWN) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot load "
"ttyname: %m, continuing.");
goto adt_setuser;
} else if (errno != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot load "
"ttyname: %m.");
} else {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot load "
"ttyname.");
}
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
} else {
if (adt_load_hostname(rhost, &termid) != 0) {
if (errno != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot load "
"hostname: %m.");
} else {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot load "
"hostname.");
}
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
}
adt_setuser:
if ((auser != NULL) && (*auser != '\0') &&
(getpwnam_r(auser, &apwd, apwbuf,
sizeof (apwbuf)) != NULL)) {
if (adt_set_user(ah, apwd.pw_uid, apwd.pw_gid,
apwd.pw_uid, apwd.pw_gid, termid, ADT_NEW) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot set auser audit "
"%m");
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
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,
"pam_unix_cred: cannot merge user audit "
"%m");
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
if (debug) {
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: new audit set for %d:%d",
apwd.pw_uid, pwd.pw_uid);
}
} else {
if (adt_set_user(ah, pwd.pw_uid, pwd.pw_gid,
pwd.pw_uid, pwd.pw_gid, termid, ADT_NEW) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot set user audit %m");
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
}
if (adt_set_proc(ah) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot set process audit %m");
ret = PAM_CRED_ERR;
goto adt_done;
}
if (debug) {
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: new audit set for %d",
pwd.pw_uid);
}
} else if ((auid != AU_NOAUDITID) &&
(flags & PAM_REINITIALIZE_CRED)) {
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,
"pam_unix_cred: cannot set user audit %m");
ret = PAM_SYSTEM_ERR;
goto adt_done;
}
if (adt_set_proc(ah) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: cannot set process audit %m");
ret = PAM_CRED_ERR;
goto adt_done;
}
if (debug) {
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: audit merged for %d:%d",
auid, pwd.pw_uid);
}
} else if (debug) {
syslog(LOG_AUTH | LOG_DEBUG,
"pam_unix_cred: audit already set for %d", auid);
}
adt_done:
if (termid != NULL)
free(termid);
if (adt_end_session(ah) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: unable to end audit session");
}
if (ret != PAM_SUCCESS)
return (ret);
(void) pam_get_item(pamh, PAM_RESOURCE, (const void **)&kvs);
if (kvs != NULL) {
char *tmp, *lasts, *tok;
nckvs = tmp = strdup(kvs);
if (nckvs == NULL)
return (PAM_BUF_ERR);
while ((tok = strtok_r(tmp, ";", &lasts)) != NULL) {
if (strncmp(tok, PROJECT, PROJSZ) == 0) {
projname = tok + PROJSZ;
break;
}
tmp = NULL;
}
} else {
projname = NULL;
}
if (projname == NULL || *projname == '\0') {
pproj = getdefaultproj(user, &proj, (void *)&buf,
PROJECT_BUFSZ);
} else {
pproj = getprojbyname(projname, &proj, (void *)&buf,
PROJECT_BUFSZ);
}
free(nckvs);
if (pproj == NULL) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: no default project for user %s", user);
if (!nowarn) {
(void) snprintf(messages[0], sizeof (messages[0]),
dgettext(TEXT_DOMAIN, "No default project!"));
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
}
return (PAM_SYSTEM_ERR);
}
if ((error = setproject(proj.pj_name, user, TASK_NORMAL)) != 0) {
kva_t *kv_array;
switch (error) {
case SETPROJ_ERR_TASK:
if (errno == EAGAIN) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: project \"%s\" resource "
"control limit has been reached",
proj.pj_name);
(void) snprintf(messages[0],
sizeof (messages[0]), dgettext(
TEXT_DOMAIN,
"Resource control limit has been "
"reached"));
} else {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: user %s could not join "
"project \"%s\": %m", user, proj.pj_name);
(void) snprintf(messages[0],
sizeof (messages[0]), dgettext(
TEXT_DOMAIN,
"Could not join default project"));
}
if (!nowarn)
(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
messages, NULL);
break;
case SETPROJ_ERR_POOL:
(void) snprintf(messages[0], sizeof (messages[0]),
dgettext(TEXT_DOMAIN,
"Could not bind to resource pool"));
switch (errno) {
case EACCES:
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: project \"%s\" could not "
"bind to resource pool: No resource pool "
"accepting default bindings exists",
proj.pj_name);
(void) snprintf(messages[1],
sizeof (messages[1]),
dgettext(TEXT_DOMAIN,
"No resource pool accepting "
"default bindings exists"));
break;
case ESRCH:
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: project \"%s\" could not "
"bind to resource pool: The resource pool "
"is unknown", proj.pj_name);
(void) snprintf(messages[1],
sizeof (messages[1]),
dgettext(TEXT_DOMAIN,
"The specified resource pool "
"is unknown"));
break;
default:
(void) snprintf(messages[1],
sizeof (messages[1]),
dgettext(TEXT_DOMAIN,
"Failure during pool binding"));
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: project \"%s\" could not "
"bind to resource pool: %m", proj.pj_name);
}
if (!nowarn)
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
2, messages, NULL);
break;
default:
if (error < 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: unkwown error joining "
"project \"%s\" (%d)", proj.pj_name, error);
(void) snprintf(messages[0],
sizeof (messages[0]),
dgettext(TEXT_DOMAIN,
"unkwown error joining project \"%s\""
" (%d)"), proj.pj_name, error);
} else if ((kv_array = _str2kva(proj.pj_attr, KV_ASSIGN,
KV_DELIMITER)) != NULL) {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: %s resource control "
"assignment failed for project \"%s\"",
kv_array->data[error - 1].key,
proj.pj_name);
(void) snprintf(messages[0],
sizeof (messages[0]),
dgettext(TEXT_DOMAIN,
"%s resource control assignment failed for "
"project \"%s\""),
kv_array->data[error - 1].key,
proj.pj_name);
_kva_free(kv_array);
} else {
syslog(LOG_AUTH | LOG_ERR,
"pam_unix_cred: resource control "
"assignment failed for project \"%s\""
"attribute %d", proj.pj_name, error);
(void) snprintf(messages[0],
sizeof (messages[0]),
dgettext(TEXT_DOMAIN,
"resource control assignment failed for "
"project \"%s\" attribute %d"),
proj.pj_name, error);
}
if (!nowarn)
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
}
return (PAM_SYSTEM_ERR);
}
tset = def = lim = NULL;
deflim.def = deflim.lim = NULL;
(void) _enum_attrs(user, finddeflim, NULL, &deflim);
if (getset(deflim.lim, &lim) != 0 || getset(deflim.def, &def) != 0) {
ret = PAM_SYSTEM_ERR;
goto out;
}
if (def == NULL) {
def = priv_allocset();
if (def == NULL) {
ret = PAM_SYSTEM_ERR;
goto out;
}
priv_basicset(def);
errno = 0;
if ((pathconf("/", _PC_CHOWN_RESTRICTED) == -1) && (errno == 0))
(void) priv_addset(def, PRIV_FILE_CHOWN_SELF);
}
tset = priv_allocset();
if (tset == NULL) {
ret = PAM_SYSTEM_ERR;
goto out;
}
if (getppriv(PRIV_PERMITTED, tset) != 0) {
ret = PAM_SYSTEM_ERR;
goto out;
}
if (!priv_issubset(def, tset))
priv_intersect(tset, def);
(void) setpflags(PRIV_AWARE, 1);
if (setppriv(PRIV_SET, PRIV_INHERITABLE, def) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_setcred: setppriv(defaultpriv) failed: %m");
ret = PAM_CRED_ERR;
}
if (lim != NULL) {
if (getppriv(PRIV_LIMIT, tset) != 0) {
ret = PAM_SYSTEM_ERR;
goto out;
}
if (!priv_issubset(lim, tset))
priv_intersect(tset, lim);
if (setppriv(PRIV_SET, PRIV_LIMIT, lim) != 0) {
syslog(LOG_AUTH | LOG_ERR,
"pam_setcred: setppriv(limitpriv) failed: %m");
ret = PAM_CRED_ERR;
goto out;
}
(void) setpflags(PRIV_AWARE_RESET, 1);
}
(void) setpflags(PRIV_AWARE, 0);
(void) setpflags(PRIV_PFEXEC, 0);
out:
free(deflim.lim);
free(deflim.def);
if (lim != NULL)
priv_freeset(lim);
if (def != NULL)
priv_freeset(def);
if (tset != NULL)
priv_freeset(tset);
return (ret);
}