123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- /* pam_dispatch.c - handles module function dispatch */
- /*
- * Copyright (c) 1998, 2005 Andrew G. Morgan <morgan@kernel.org>
- *
- */
- #include "pam_private.h"
- #include <stdlib.h>
- #include <stdio.h>
- /*
- * this is the return code we return when a function pointer is NULL
- * or, the handler structure indicates a broken module config line
- */
- #define PAM_MUST_FAIL_CODE PAM_PERM_DENIED
- /* impression codes - this gives some sense to the logical choices */
- #define _PAM_UNDEF 0
- #define _PAM_POSITIVE +1
- #define _PAM_NEGATIVE -1
- /* frozen chain required codes */
- #define _PAM_PLEASE_FREEZE 0
- #define _PAM_MAY_BE_FROZEN 1
- #define _PAM_MUST_BE_FROZEN 2
- /*
- * walk a stack of modules. Interpret the administrator's instructions
- * when combining the return code of each module.
- */
- 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, prev_level, stack_level;
- struct _pam_substack_state *substates = NULL;
- IF_NO_PAMH("_pam_dispatch_aux", pamh, PAM_SYSTEM_ERR);
- if (h == NULL) {
- const void *service=NULL;
- (void) pam_get_item(pamh, PAM_SERVICE, &service);
- pam_syslog(pamh, LOG_ERR, "no modules loaded for `%s' service",
- service ? (const char *)service:"<unknown>" );
- service = NULL;
- return PAM_MUST_FAIL_CODE;
- }
- /* if we are recalling this module stack because a former call did
- not complete, we restore the state of play from pamh. */
- if (resumed) {
- skip_depth = pamh->former.depth;
- status = pamh->former.status;
- impression = pamh->former.impression;
- substates = pamh->former.substates;
- /* forget all that */
- pamh->former.impression = _PAM_UNDEF;
- pamh->former.status = PAM_MUST_FAIL_CODE;
- pamh->former.depth = 0;
- pamh->former.substates = NULL;
- } else {
- skip_depth = 0;
- substates = malloc(PAM_SUBSTACK_MAX_LEVEL * sizeof(*substates));
- if (substates == NULL) {
- pam_syslog(pamh, LOG_CRIT,
- "_pam_dispatch_aux: no memory for substack states");
- return PAM_BUF_ERR;
- }
- substates[0].impression = impression = _PAM_UNDEF;
- substates[0].status = status = PAM_MUST_FAIL_CODE;
- }
- prev_level = 0;
- /* Loop through module logic stack */
- for (depth=0 ; h != NULL ; prev_level = stack_level, h = h->next, ++depth) {
- int retval, cached_retval, action;
- stack_level = h->stack_level;
- /* skip leading modules if they have already returned */
- if (depth < skip_depth) {
- continue;
- }
- /* remember state if we are entering a substack */
- if (prev_level < stack_level) {
- substates[stack_level].impression = impression;
- substates[stack_level].status = status;
- }
- /* attempt to call the module */
- if (h->handler_type == PAM_HT_MUST_FAIL) {
- D(("module poorly listed in PAM config; forcing failure"));
- retval = PAM_MUST_FAIL_CODE;
- } else if (h->handler_type == PAM_HT_SUBSTACK) {
- D(("skipping substack handler"));
- continue;
- } else if (h->func == NULL) {
- D(("module function is not defined, indicating failure"));
- retval = PAM_MODULE_UNKNOWN;
- } else {
- D(("passing control to module..."));
- pamh->mod_name=h->mod_name;
- pamh->mod_argc = h->argc;
- pamh->mod_argv = h->argv;
- retval = h->func(pamh, flags, h->argc, h->argv);
- pamh->mod_name=NULL;
- pamh->mod_argc = 0;
- pamh->mod_argv = NULL;
- D(("module returned: %s", pam_strerror(pamh, retval)));
- }
- /*
- * PAM_INCOMPLETE return is special. It indicates that the
- * module wants to wait for the application before continuing.
- * In order to return this, the module will have saved its
- * state so it can resume from an equivalent position when it
- * is called next time. (This was added as of 0.65)
- */
- if (retval == PAM_INCOMPLETE) {
- pamh->former.impression = impression;
- pamh->former.status = status;
- pamh->former.depth = depth;
- pamh->former.substates = substates;
- D(("module %d returned PAM_INCOMPLETE", depth));
- return retval;
- }
- /*
- * use_cached_chain is how we ensure that the setcred and
- * close_session modules are called in the same order as they did
- * when they were invoked as auth/open_session. This feature was
- * added in 0.75 to make the behavior of pam_setcred sane.
- */
- if (use_cached_chain != _PAM_PLEASE_FREEZE) {
- /* a former stack execution should have frozen the chain */
- cached_retval = *(h->cached_retval_p);
- if (cached_retval == _PAM_INVALID_RETVAL) {
- /* This may be a problem condition. It implies that
- the application is running setcred, close_session,
- chauthtok(2nd) without having first run
- authenticate, open_session, chauthtok(1st)
- [respectively]. */
- D(("use_cached_chain is set to [%d],"
- " but cached_retval == _PAM_INVALID_RETVAL",
- use_cached_chain));
- /* In the case of close_session and setcred there is a
- backward compatibility reason for allowing this, in
- the chauthtok case we have encountered a bug in
- libpam! */
- if (use_cached_chain == _PAM_MAY_BE_FROZEN) {
- /* (not ideal) force non-frozen stack control. */
- cached_retval = retval;
- } else {
- D(("BUG in libpam -"
- " chain is required to be frozen but isn't"));
- /* cached_retval is already _PAM_INVALID_RETVAL */
- }
- }
- } else {
- /* this stack execution is defining the frozen chain */
- cached_retval = h->cached_retval = retval;
- }
- /* verify that the return value is a valid one */
- if ((cached_retval < PAM_SUCCESS)
- || (cached_retval >= _PAM_RETURN_VALUES)) {
- retval = PAM_MUST_FAIL_CODE;
- action = _PAM_ACTION_BAD;
- } else {
- /* We treat the current retval with some respect. It may
- (for example, in the case of setcred) have a value that
- needs to be propagated to the user. We want to use the
- cached_retval to determine the modules to be executed
- in the stacked chain, but we want to treat each
- non-ignored module in the cached chain as now being
- 'required'. We only need to treat the,
- _PAM_ACTION_IGNORE, _PAM_ACTION_IS_JUMP and
- _PAM_ACTION_RESET actions specially. */
- action = h->actions[cached_retval];
- }
- D(("use_cached_chain=%d action=%d cached_retval=%d retval=%d",
- use_cached_chain, action, cached_retval, retval));
- /* decide what to do */
- switch (action) {
- case _PAM_ACTION_RESET:
- impression = substates[stack_level].impression;
- status = substates[stack_level].status;
- break;
- case _PAM_ACTION_OK:
- case _PAM_ACTION_DONE:
- if ( impression == _PAM_UNDEF
- || (impression == _PAM_POSITIVE && status == PAM_SUCCESS) ) {
- /* in case of using cached chain
- we could get here with PAM_IGNORE - don't return it */
- if ( retval != PAM_IGNORE || cached_retval == retval ) {
- impression = _PAM_POSITIVE;
- status = retval;
- }
- }
- if ( impression == _PAM_POSITIVE ) {
- if ( retval == PAM_SUCCESS ) {
- h->grantor = 1;
- }
- if ( 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 /* PAM_FAIL_NOW_ON */
- if ( impression != _PAM_NEGATIVE ) {
- impression = _PAM_NEGATIVE;
- /* Don't return with PAM_IGNORE as status */
- if ( retval == PAM_IGNORE )
- status = PAM_MUST_FAIL_CODE;
- else
- status = retval;
- }
- if ( action == _PAM_ACTION_DIE ) {
- goto decision_made;
- }
- break;
- case _PAM_ACTION_IGNORE:
- break;
- /* if we get here, we expect action is a positive number --
- this is what the ...JUMP macro checks. */
- default:
- if ( _PAM_ACTION_IS_JUMP(action) ) {
- /* If we are evaluating a cached chain, we treat this
- module as required (aka _PAM_ACTION_OK) as well as
- executing the jump. */
- if (use_cached_chain) {
- if (impression == _PAM_UNDEF
- || (impression == _PAM_POSITIVE
- && status == PAM_SUCCESS) ) {
- if ( retval != PAM_IGNORE || cached_retval == retval ) {
- if ( impression == _PAM_UNDEF && retval == PAM_SUCCESS ) {
- h->grantor = 1;
- }
- impression = _PAM_POSITIVE;
- status = retval;
- }
- }
- }
- /* this means that we need to skip #action stacked modules */
- while (h->next != NULL && h->next->stack_level >= stack_level && action > 0) {
- do {
- h = h->next;
- ++depth;
- } while (h->next != NULL && h->next->stack_level > stack_level);
- --action;
- }
- /* note if we try to skip too many modules action is
- still non-zero and we snag the next if. */
- }
- /* this case is a syntax error: we can't succeed */
- if (action) {
- pam_syslog(pamh, LOG_ERR, "bad jump in stack");
- impression = _PAM_NEGATIVE;
- status = PAM_MUST_FAIL_CODE;
- }
- }
- continue;
- decision_made: /* by getting here we have made a decision */
- while (h->next != NULL && h->next->stack_level >= stack_level) {
- h = h->next;
- ++depth;
- }
- }
- /* Sanity check */
- if ( status == PAM_SUCCESS && impression != _PAM_POSITIVE ) {
- D(("caught on sanity check -- this is probably a config error!"));
- status = PAM_MUST_FAIL_CODE;
- }
- free(substates);
- /* We have made a decision about the modules executed */
- return status;
- }
- static void _pam_clear_grantors(struct handler *h)
- {
- for (; h != NULL; h = h->next) {
- h->grantor = 0;
- }
- }
- /*
- * This function translates the module dispatch request into a pointer
- * to the stack of modules that will actually be run. the
- * _pam_dispatch_aux() function (above) is responsible for walking the
- * module stack.
- */
- int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
- {
- struct handler *h = NULL;
- int retval = PAM_SYSTEM_ERR, 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!?"));
- goto end;
- }
- /* Load all modules, resolve all symbols */
- if ((retval = _pam_init_handlers(pamh)) != PAM_SUCCESS) {
- pam_syslog(pamh, LOG_ERR, "unable to dispatch function");
- goto end;
- }
- 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;
- break;
- default:
- pam_syslog(pamh, LOG_ERR, "undefined fn choice; %d", choice);
- retval = PAM_ABORT;
- goto end;
- }
- if (h == NULL) { /* there was no handlers.conf... entry; will use
- * handlers.other... */
- 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;
- }
- }
- /* Did a module return an "incomplete state" last time? */
- if (pamh->former.choice != PAM_NOT_STACKED) {
- if (pamh->former.choice != choice) {
- pam_syslog(pamh, LOG_ERR,
- "application failed to re-exec stack [%d:%d]",
- pamh->former.choice, choice);
- retval = PAM_ABORT;
- goto end;
- }
- resumed = PAM_TRUE;
- } else {
- resumed = PAM_FALSE;
- _pam_clear_grantors(h);
- }
- __PAM_TO_MODULE(pamh);
- /* call the list of module functions */
- pamh->choice = choice;
- retval = _pam_dispatch_aux(pamh, flags, h, resumed, use_cached_chain);
- __PAM_TO_APP(pamh);
- /* Should we recall where to resume next time? */
- if (retval == PAM_INCOMPLETE) {
- D(("module [%d] returned PAM_INCOMPLETE"));
- pamh->former.choice = choice;
- } else {
- pamh->former.choice = PAM_NOT_STACKED;
- }
- end:
- #ifdef HAVE_LIBAUDIT
- if (choice != PAM_CHAUTHTOK || flags & PAM_UPDATE_AUTHTOK || retval != PAM_SUCCESS) {
- retval = _pam_auditlog(pamh, choice, retval, flags, h);
- }
- #endif
- return retval;
- }
|