#include <stdlib.h>
#include <stdio.h>
#include "pam_private.h"
#define PAM_MUST_FAIL_CODE PAM_PERM_DENIED
#define _PAM_UNDEF 0
#define _PAM_POSITIVE +1
#define _PAM_NEGATIVE -1
#define _PAM_PLEASE_FREEZE 0
#define _PAM_MAY_BE_FROZEN 1
#define _PAM_MUST_BE_FROZEN 2
static int _pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,
_pam_boolean resumed, int use_cached_chain)
{
int depth, impression, status, skip_depth;
IF_NO_PAMH("_pam_dispatch_aux", pamh, PAM_SYSTEM_ERR);
if (h == NULL) {
const char *service=NULL;
(void) pam_get_item(pamh, PAM_SERVICE, (const void **)&service);
_pam_system_log(LOG_ERR, "no modules loaded for `%s' service",
service ? service:"<unknown>" );
service = NULL;
return PAM_MUST_FAIL_CODE;
}
if (resumed) {
skip_depth = pamh->former.depth;
status = pamh->former.status;
impression = pamh->former.impression;
pamh->former.impression = _PAM_UNDEF;
pamh->former.status = PAM_MUST_FAIL_CODE;
pamh->former.depth = 0;
} else {
skip_depth = 0;
impression = _PAM_UNDEF;
status = PAM_MUST_FAIL_CODE;
}
for (depth=0 ; h != NULL ; h = h->next, ++depth) {
int retval, cached_retval, action;
if (depth < skip_depth) {
continue;
}
if (h->func == NULL) {
D(("module function is not defined, indicating failure"));
retval = PAM_MODULE_UNKNOWN;
} else {
D(("passing control to module..."));
retval = h->func(pamh, flags, h->argc, h->argv);
D(("module returned: %s", pam_strerror(pamh, retval)));
if (h->must_fail) {
D(("module poorly listed in PAM config; forcing failure"));
retval = PAM_MUST_FAIL_CODE;
}
}
if (retval == PAM_INCOMPLETE) {
pamh->former.impression = impression;
pamh->former.status = status;
pamh->former.depth = depth;
D(("module %d returned PAM_INCOMPLETE", depth));
return retval;
}
if (use_cached_chain != _PAM_PLEASE_FREEZE) {
cached_retval = *(h->cached_retval_p);
if (cached_retval == _PAM_INVALID_RETVAL) {
D(("use_cached_chain is set to [%d],"
" but cached_retval == _PAM_INVALID_RETVAL",
use_cached_chain));
if (use_cached_chain == _PAM_MAY_BE_FROZEN) {
cached_retval = retval;
} else {
D(("BUG in libpam -"
" chain is required to be frozen but isn't"));
}
}
} else {
cached_retval = h->cached_retval = retval;
}
if ((cached_retval < PAM_SUCCESS)
|| (cached_retval >= _PAM_RETURN_VALUES)) {
retval = PAM_MUST_FAIL_CODE;
action = _PAM_ACTION_BAD;
} else {
action = h->actions[cached_retval];
}
D(("use_cached_chain=%d action=%d cached_retval=%d retval=%d",
use_cached_chain, action, cached_retval, retval));
switch (action) {
case _PAM_ACTION_RESET:
impression = _PAM_UNDEF;
status = PAM_MUST_FAIL_CODE;
break;
case _PAM_ACTION_OK:
case _PAM_ACTION_DONE:
if ( impression == _PAM_UNDEF
|| (impression == _PAM_POSITIVE && status == PAM_SUCCESS) ) {
impression = _PAM_POSITIVE;
status = retval;
}
if ( impression == _PAM_POSITIVE && action == _PAM_ACTION_DONE ) {
goto decision_made;
}
break;
case _PAM_ACTION_BAD:
case _PAM_ACTION_DIE:
#ifdef PAM_FAIL_NOW_ON
if ( cached_retval == PAM_ABORT ) {
impression = _PAM_NEGATIVE;
status = PAM_PERM_DENIED;
goto decision_made;
}
#endif
if ( impression != _PAM_NEGATIVE ) {
impression = _PAM_NEGATIVE;
status = retval;
}
if ( action == _PAM_ACTION_DIE ) {
goto decision_made;
}
break;
case _PAM_ACTION_IGNORE:
break;
default:
if ( _PAM_ACTION_IS_JUMP(action) ) {
if (use_cached_chain) {
if (impression == _PAM_UNDEF
|| (impression == _PAM_POSITIVE
&& status == PAM_SUCCESS) ) {
impression = _PAM_POSITIVE;
status = retval;
}
}
do {
h = h->next;
} while ( --action > 0 && h != NULL );
}
if (action) {
D(("action syntax error"));
impression = _PAM_NEGATIVE;
status = PAM_MUST_FAIL_CODE;
}
}
}
decision_made:
if ( status == PAM_SUCCESS && impression != _PAM_POSITIVE ) {
D(("caught on sanity check -- this is probably a config error!"));
status = PAM_MUST_FAIL_CODE;
}
return status;
}
int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
{
struct handler *h = NULL;
int retval, use_cached_chain;
_pam_boolean resumed;
IF_NO_PAMH("_pam_dispatch", pamh, PAM_SYSTEM_ERR);
if (__PAM_FROM_MODULE(pamh)) {
D(("called from a module!?"));
return PAM_SYSTEM_ERR;
}
if ((retval = _pam_init_handlers(pamh)) != PAM_SUCCESS) {
_pam_system_log(LOG_ERR, "unable to dispatch function");
return retval;
}
use_cached_chain = _PAM_PLEASE_FREEZE;
switch (choice) {
case PAM_AUTHENTICATE:
h = pamh->handlers.conf.authenticate;
break;
case PAM_SETCRED:
h = pamh->handlers.conf.setcred;
use_cached_chain = _PAM_MAY_BE_FROZEN;
break;
case PAM_ACCOUNT:
h = pamh->handlers.conf.acct_mgmt;
break;
case PAM_OPEN_SESSION:
h = pamh->handlers.conf.open_session;
break;
case PAM_CLOSE_SESSION:
h = pamh->handlers.conf.close_session;
use_cached_chain = _PAM_MAY_BE_FROZEN;
break;
case PAM_CHAUTHTOK:
h = pamh->handlers.conf.chauthtok;
if (flags & PAM_UPDATE_AUTHTOK) {
use_cached_chain = _PAM_MUST_BE_FROZEN;
}
break;
default:
_pam_system_log(LOG_ERR, "undefined fn choice; %d", choice);
return PAM_ABORT;
}
if (h == NULL) {
switch (choice) {
case PAM_AUTHENTICATE:
h = pamh->handlers.other.authenticate;
break;
case PAM_SETCRED:
h = pamh->handlers.other.setcred;
break;
case PAM_ACCOUNT:
h = pamh->handlers.other.acct_mgmt;
break;
case PAM_OPEN_SESSION:
h = pamh->handlers.other.open_session;
break;
case PAM_CLOSE_SESSION:
h = pamh->handlers.other.close_session;
break;
case PAM_CHAUTHTOK:
h = pamh->handlers.other.chauthtok;
break;
}
}
if (pamh->former.choice != PAM_NOT_STACKED) {
if (pamh->former.choice != choice) {
_pam_system_log(LOG_ERR,
"application failed to re-exec stack [%d:%d]",
pamh->former.choice, choice);
return PAM_ABORT;
}
resumed = PAM_TRUE;
} else {
resumed = PAM_FALSE;
}
__PAM_TO_MODULE(pamh);
retval = _pam_dispatch_aux(pamh, flags, h, resumed, use_cached_chain);
resumed = PAM_FALSE;
__PAM_TO_APP(pamh);
if (retval == PAM_INCOMPLETE) {
D(("module [%d] returned PAM_INCOMPLETE"));
pamh->former.choice = choice;
} else {
pamh->former.choice = PAM_NOT_STACKED;
}
return retval;
}