/* pam_dispatch.c - handles module function dispatch */

/*
 * $Id: pam_dispatch.c,v 1.8 1997/01/04 20:04:09 morgan Exp morgan $
 *
 * last modified by AGM
 */

#include <stdlib.h>
#include <stdio.h>

#include "pam_private.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

static int _pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h)
{
    int impression = _PAM_UNDEF;
    int status = PAM_MUST_FAIL_CODE;

    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_log_error("no modules loaded for `%s' service"
		       , service ? service:"<unknown>" );
	service = NULL;
	return PAM_MUST_FAIL_CODE;
    }

    /* Loop through module logic stack */
    for ( ; h != NULL ; h = h->next ) {
	int retval, action;

	/* attempt to call the module */
	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.conf; forcing failure"));
		retval = PAM_MUST_FAIL_CODE;
	    }
	}

	/* verify that the return value is a valid one */
	if (retval < PAM_SUCCESS || retval >= _PAM_RETURN_VALUES) {
	    retval = PAM_MUST_FAIL_CODE;
	    action = _PAM_ACTION_BAD;
	} else {
	    action = h->actions[retval];
	}

	/* decide what to do */
	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 ( 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;
		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) ) {
		/* this means that we need to skip #action stacked modules */
		do {
 		    h = h->next;
		} while ( --action > 0 && h != NULL );

		/* 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) {
		D(("action syntax error"));
		impression = _PAM_NEGATIVE;
		status = PAM_MUST_FAIL_CODE;
	    }
	}
    }

decision_made:     /* by getting  here we have made a decision */

    /* 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;
    }

    /* We have made a decision about the modules executed */
    return status;
}

int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
{
    struct handler *h = NULL;
    int res;

    IF_NO_PAMH("_pam_dispatch",pamh,PAM_SYSTEM_ERR);

    /* Load all modules, resolve all symbols */

    if ((res = _pam_init_handlers(pamh)) != PAM_SUCCESS) {
	_pam_log_error("unable to dispatch function");
	return res;
    }

    switch (choice) {
    case PAM_AUTHENTICATE:
	h = pamh->handlers.conf.authenticate;
	break;
    case PAM_SETCRED:
	h = pamh->handlers.conf.setcred;
	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;
	break;
    case PAM_CHAUTHTOK:
	h = pamh->handlers.conf.chauthtok;
	break;
    default:
	_pam_log_error("undefined fn choice; %d",choice);
	return PAM_ABORT;
    }

    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;
	}
    }

    /* call the list of module functions */
    
    return _pam_dispatch_aux(pamh, flags, h);
}

/*
 * $Log: pam_dispatch.c,v $
 * Revision 1.8  1997/01/04 20:04:09  morgan
 * added "must fail" code to perform "garbled" syntax in pam.conf
 *
 * Revision 1.7  1996/12/18 04:16:56  morgan
 * stable code prior to new bad pam.conf handling...
 *
 */
