(root)/
Linux-PAM-1.5.3/
libpam/
pam_dispatch.c
       1  /* pam_dispatch.c - handles module function dispatch */
       2  
       3  /*
       4   * Copyright (c) 1998, 2005 Andrew G. Morgan <morgan@kernel.org>
       5   *
       6   */
       7  
       8  #include "pam_private.h"
       9  
      10  #include <stdlib.h>
      11  #include <stdio.h>
      12  
      13  /*
      14   * this is the return code we return when a function pointer is NULL
      15   * or, the handler structure indicates a broken module config line
      16   */
      17  #define PAM_MUST_FAIL_CODE        PAM_PERM_DENIED
      18  
      19  /* impression codes - this gives some sense to the logical choices */
      20  #define _PAM_UNDEF     0
      21  #define _PAM_POSITIVE +1
      22  #define _PAM_NEGATIVE -1
      23  
      24  /* frozen chain required codes */
      25  #define _PAM_PLEASE_FREEZE  0
      26  #define _PAM_MAY_BE_FROZEN  1
      27  #define _PAM_MUST_BE_FROZEN 2
      28  
      29  /*
      30   * walk a stack of modules.  Interpret the administrator's instructions
      31   * when combining the return code of each module.
      32   */
      33  
      34  static int _pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,
      35  			     _pam_boolean resumed, int use_cached_chain)
      36  {
      37      int depth, impression, status, skip_depth, prev_level, stack_level;
      38      struct _pam_substack_state *substates = NULL;
      39  
      40      IF_NO_PAMH("_pam_dispatch_aux", pamh, PAM_SYSTEM_ERR);
      41  
      42      if (h == NULL) {
      43          const void *service=NULL;
      44  
      45  	(void) pam_get_item(pamh, PAM_SERVICE, &service);
      46  	pam_syslog(pamh, LOG_ERR, "no modules loaded for `%s' service",
      47  		   service ? (const char *)service:"<unknown>" );
      48  	service = NULL;
      49  	return PAM_MUST_FAIL_CODE;
      50      }
      51  
      52      /* if we are recalling this module stack because a former call did
      53         not complete, we restore the state of play from pamh. */
      54      if (resumed) {
      55  	skip_depth = pamh->former.depth;
      56  	status = pamh->former.status;
      57  	impression = pamh->former.impression;
      58  	substates = pamh->former.substates;
      59  	/* forget all that */
      60  	pamh->former.impression = _PAM_UNDEF;
      61  	pamh->former.status = PAM_MUST_FAIL_CODE;
      62  	pamh->former.depth = 0;
      63  	pamh->former.substates = NULL;
      64      } else {
      65  	skip_depth = 0;
      66  	substates = malloc(PAM_SUBSTACK_MAX_LEVEL * sizeof(*substates));
      67  	if (substates == NULL) {
      68  	    pam_syslog(pamh, LOG_CRIT,
      69  		       "_pam_dispatch_aux: no memory for substack states");
      70  	    return PAM_BUF_ERR;
      71  	}
      72  	substates[0].impression = impression = _PAM_UNDEF;
      73  	substates[0].status = status = PAM_MUST_FAIL_CODE;
      74      }
      75  
      76      prev_level = 0;
      77  
      78      /* Loop through module logic stack */
      79      for (depth=0 ; h != NULL ; prev_level = stack_level, h = h->next, ++depth) {
      80  	int retval, cached_retval, action;
      81  
      82          stack_level = h->stack_level;
      83  
      84  	/* skip leading modules if they have already returned */
      85  	if (depth < skip_depth) {
      86  	    continue;
      87  	}
      88  
      89  	/* remember state if we are entering a substack */
      90  	if (prev_level < stack_level) {
      91  	    substates[stack_level].impression = impression;
      92  	    substates[stack_level].status = status;
      93  	}
      94  
      95  	/* attempt to call the module */
      96  	if (h->handler_type == PAM_HT_MUST_FAIL) {
      97  	    D(("module poorly listed in PAM config; forcing failure"));
      98  	    retval = PAM_MUST_FAIL_CODE;
      99  	} else if (h->handler_type == PAM_HT_SUBSTACK) {
     100  	    D(("skipping substack handler"));
     101  	    continue;
     102  	} else if (h->func == NULL) {
     103  	    D(("module function is not defined, indicating failure"));
     104  	    retval = PAM_MODULE_UNKNOWN;
     105  	} else {
     106  	    D(("passing control to module..."));
     107  	    pamh->mod_name=h->mod_name;
     108  	    pamh->mod_argc = h->argc;
     109  	    pamh->mod_argv = h->argv;
     110  	    retval = h->func(pamh, flags, h->argc, h->argv);
     111  	    pamh->mod_name=NULL;
     112  	    pamh->mod_argc = 0;
     113  	    pamh->mod_argv = NULL;
     114  	    D(("module returned: %s", pam_strerror(pamh, retval)));
     115  	}
     116  
     117  	/*
     118  	 * PAM_INCOMPLETE return is special.  It indicates that the
     119  	 * module wants to wait for the application before continuing.
     120  	 * In order to return this, the module will have saved its
     121  	 * state so it can resume from an equivalent position when it
     122  	 * is called next time.  (This was added as of 0.65)
     123  	 */
     124  	if (retval == PAM_INCOMPLETE) {
     125  	    pamh->former.impression = impression;
     126  	    pamh->former.status = status;
     127  	    pamh->former.depth = depth;
     128  	    pamh->former.substates = substates;
     129  
     130  	    D(("module %d returned PAM_INCOMPLETE", depth));
     131  	    return retval;
     132  	}
     133  
     134  	/*
     135  	 * use_cached_chain is how we ensure that the setcred and
     136           * close_session modules are called in the same order as they did
     137  	 * when they were invoked as auth/open_session. This feature was
     138  	 * added in 0.75 to make the behavior of pam_setcred sane.
     139  	 */
     140  	if (use_cached_chain != _PAM_PLEASE_FREEZE) {
     141  
     142  	    /* a former stack execution should have frozen the chain */
     143  
     144  	    cached_retval = *(h->cached_retval_p);
     145  	    if (cached_retval == _PAM_INVALID_RETVAL) {
     146  
     147  		/* This may be a problem condition. It implies that
     148  		   the application is running setcred, close_session,
     149  		   chauthtok(2nd) without having first run
     150  		   authenticate, open_session, chauthtok(1st)
     151  		   [respectively]. */
     152  
     153  		D(("use_cached_chain is set to [%d],"
     154  		   " but cached_retval == _PAM_INVALID_RETVAL",
     155  		   use_cached_chain));
     156  
     157  		/* In the case of close_session and setcred there is a
     158  		   backward compatibility reason for allowing this, in
     159  		   the chauthtok case we have encountered a bug in
     160  		   libpam! */
     161  
     162  		if (use_cached_chain == _PAM_MAY_BE_FROZEN) {
     163  		    /* (not ideal) force non-frozen stack control. */
     164  		    cached_retval = retval;
     165  		} else {
     166  		    D(("BUG in libpam -"
     167  		       " chain is required to be frozen but isn't"));
     168  
     169  		    /* cached_retval is already _PAM_INVALID_RETVAL */
     170  		}
     171  	    }
     172  	} else {
     173  	    /* this stack execution is defining the frozen chain */
     174  	    cached_retval = h->cached_retval = retval;
     175  	}
     176  
     177  	/* verify that the return value is a valid one */
     178  	if ((cached_retval < PAM_SUCCESS)
     179  	    || (cached_retval >= _PAM_RETURN_VALUES)) {
     180  
     181  	    retval = PAM_MUST_FAIL_CODE;
     182  	    action = _PAM_ACTION_BAD;
     183  	} else {
     184  	    /* We treat the current retval with some respect. It may
     185  	       (for example, in the case of setcred) have a value that
     186  	       needs to be propagated to the user.  We want to use the
     187  	       cached_retval to determine the modules to be executed
     188  	       in the stacked chain, but we want to treat each
     189  	       non-ignored module in the cached chain as now being
     190  	       'required'. We only need to treat the,
     191  	       _PAM_ACTION_IGNORE, _PAM_ACTION_IS_JUMP and
     192  	       _PAM_ACTION_RESET actions specially. */
     193  
     194  	    action = h->actions[cached_retval];
     195  	}
     196  
     197  	D(("use_cached_chain=%d action=%d cached_retval=%d retval=%d",
     198  	   use_cached_chain, action, cached_retval, retval));
     199  
     200  	/* decide what to do */
     201  	switch (action) {
     202  	case _PAM_ACTION_RESET:
     203  
     204  	    impression = substates[stack_level].impression;
     205  	    status = substates[stack_level].status;
     206  	    break;
     207  
     208  	case _PAM_ACTION_OK:
     209  	case _PAM_ACTION_DONE:
     210  
     211  	    if ( impression == _PAM_UNDEF
     212  		 || (impression == _PAM_POSITIVE && status == PAM_SUCCESS) ) {
     213                  /* in case of using cached chain
     214                     we could get here with PAM_IGNORE - don't return it */
     215                  if ( retval != PAM_IGNORE || cached_retval == retval ) {
     216  		    impression = _PAM_POSITIVE;
     217                      status = retval;
     218                  }
     219  	    }
     220  	    if ( impression == _PAM_POSITIVE ) {
     221  		if ( retval == PAM_SUCCESS ) {
     222  		    h->grantor = 1;
     223  		}
     224  
     225  		if ( action == _PAM_ACTION_DONE ) {
     226  		    goto decision_made;
     227  		}
     228  	    }
     229  	    break;
     230  
     231  	case _PAM_ACTION_BAD:
     232  	case _PAM_ACTION_DIE:
     233  #ifdef PAM_FAIL_NOW_ON
     234  	    if ( cached_retval == PAM_ABORT ) {
     235  		impression = _PAM_NEGATIVE;
     236  		status = PAM_PERM_DENIED;
     237  		goto decision_made;
     238  	    }
     239  #endif /* PAM_FAIL_NOW_ON */
     240  	    if ( impression != _PAM_NEGATIVE ) {
     241  		impression = _PAM_NEGATIVE;
     242  	        /* Don't return with PAM_IGNORE as status */
     243  	        if ( retval == PAM_IGNORE )
     244  		    status = PAM_MUST_FAIL_CODE;
     245  		else
     246  		    status = retval;
     247  	    }
     248  	    if ( action == _PAM_ACTION_DIE ) {
     249  		goto decision_made;
     250  	    }
     251  	    break;
     252  
     253  	case _PAM_ACTION_IGNORE:
     254  	    break;
     255  
     256          /* if we get here, we expect action is a positive number --
     257             this is what the ...JUMP macro checks. */
     258  
     259  	default:
     260  	    if ( _PAM_ACTION_IS_JUMP(action) ) {
     261  
     262  		/* If we are evaluating a cached chain, we treat this
     263  		   module as required (aka _PAM_ACTION_OK) as well as
     264  		   executing the jump. */
     265  
     266  		if (use_cached_chain) {
     267  		    if (impression == _PAM_UNDEF
     268  			|| (impression == _PAM_POSITIVE
     269  			    && status == PAM_SUCCESS) ) {
     270  			if ( retval != PAM_IGNORE || cached_retval == retval ) {
     271  			    if ( impression == _PAM_UNDEF && retval == PAM_SUCCESS ) {
     272  				h->grantor = 1;
     273  			    }
     274  			    impression = _PAM_POSITIVE;
     275  			    status = retval;
     276  			}
     277  		    }
     278  		}
     279  
     280  		/* this means that we need to skip #action stacked modules */
     281  		while (h->next != NULL && h->next->stack_level >= stack_level && action > 0) {
     282  		    do {
     283  			h = h->next;
     284  			++depth;
     285  		    } while (h->next != NULL && h->next->stack_level > stack_level);
     286  		    --action;
     287  		}
     288  
     289  		/* note if we try to skip too many modules action is
     290                     still non-zero and we snag the next if. */
     291  	    }
     292  
     293  	    /* this case is a syntax error: we can't succeed */
     294  	    if (action) {
     295  		pam_syslog(pamh, LOG_ERR, "bad jump in stack");
     296  		impression = _PAM_NEGATIVE;
     297  		status = PAM_MUST_FAIL_CODE;
     298  	    }
     299  	}
     300  	continue;
     301  
     302  decision_made:     /* by getting  here we have made a decision */
     303  	while (h->next != NULL && h->next->stack_level >= stack_level) {
     304  	    h = h->next;
     305  	    ++depth;
     306  	}
     307      }
     308  
     309      /* Sanity check */
     310      if ( status == PAM_SUCCESS && impression != _PAM_POSITIVE ) {
     311  	D(("caught on sanity check -- this is probably a config error!"));
     312  	status = PAM_MUST_FAIL_CODE;
     313      }
     314  
     315      free(substates);
     316      /* We have made a decision about the modules executed */
     317      return status;
     318  }
     319  
     320  static void _pam_clear_grantors(struct handler *h)
     321  {
     322      for (; h != NULL; h = h->next) {
     323  	h->grantor = 0;
     324      }
     325  }
     326  
     327  /*
     328   * This function translates the module dispatch request into a pointer
     329   * to the stack of modules that will actually be run.  the
     330   * _pam_dispatch_aux() function (above) is responsible for walking the
     331   * module stack.
     332   */
     333  
     334  int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
     335  {
     336      struct handler *h = NULL;
     337      int retval = PAM_SYSTEM_ERR, use_cached_chain;
     338      _pam_boolean resumed;
     339  
     340      IF_NO_PAMH("_pam_dispatch", pamh, PAM_SYSTEM_ERR);
     341  
     342      if (__PAM_FROM_MODULE(pamh)) {
     343  	D(("called from a module!?"));
     344  	goto end;
     345      }
     346  
     347      /* Load all modules, resolve all symbols */
     348  
     349      if ((retval = _pam_init_handlers(pamh)) != PAM_SUCCESS) {
     350  	pam_syslog(pamh, LOG_ERR, "unable to dispatch function");
     351  	goto end;
     352      }
     353  
     354      use_cached_chain = _PAM_PLEASE_FREEZE;
     355  
     356      switch (choice) {
     357      case PAM_AUTHENTICATE:
     358  	h = pamh->handlers.conf.authenticate;
     359  	break;
     360      case PAM_SETCRED:
     361  	h = pamh->handlers.conf.setcred;
     362  	use_cached_chain = _PAM_MAY_BE_FROZEN;
     363  	break;
     364      case PAM_ACCOUNT:
     365  	h = pamh->handlers.conf.acct_mgmt;
     366  	break;
     367      case PAM_OPEN_SESSION:
     368  	h = pamh->handlers.conf.open_session;
     369  	break;
     370      case PAM_CLOSE_SESSION:
     371  	h = pamh->handlers.conf.close_session;
     372  	use_cached_chain = _PAM_MAY_BE_FROZEN;
     373  	break;
     374      case PAM_CHAUTHTOK:
     375  	h = pamh->handlers.conf.chauthtok;
     376  	break;
     377      default:
     378  	pam_syslog(pamh, LOG_ERR, "undefined fn choice; %d", choice);
     379  	retval = PAM_ABORT;
     380  	goto end;
     381      }
     382  
     383      if (h == NULL) {     /* there was no handlers.conf... entry; will use
     384  			  * handlers.other... */
     385  	switch (choice) {
     386  	case PAM_AUTHENTICATE:
     387  	    h = pamh->handlers.other.authenticate;
     388  	    break;
     389  	case PAM_SETCRED:
     390  	    h = pamh->handlers.other.setcred;
     391  	    break;
     392  	case PAM_ACCOUNT:
     393  	    h = pamh->handlers.other.acct_mgmt;
     394  	    break;
     395  	case PAM_OPEN_SESSION:
     396  	    h = pamh->handlers.other.open_session;
     397  	    break;
     398  	case PAM_CLOSE_SESSION:
     399  	    h = pamh->handlers.other.close_session;
     400  	    break;
     401  	case PAM_CHAUTHTOK:
     402  	    h = pamh->handlers.other.chauthtok;
     403  	    break;
     404  	}
     405      }
     406  
     407      /* Did a module return an "incomplete state" last time? */
     408      if (pamh->former.choice != PAM_NOT_STACKED) {
     409  	if (pamh->former.choice != choice) {
     410  	    pam_syslog(pamh, LOG_ERR,
     411  			    "application failed to re-exec stack [%d:%d]",
     412  			    pamh->former.choice, choice);
     413  	    retval = PAM_ABORT;
     414  	    goto end;
     415  	}
     416  	resumed = PAM_TRUE;
     417      } else {
     418  	resumed = PAM_FALSE;
     419  	_pam_clear_grantors(h);
     420      }
     421  
     422      __PAM_TO_MODULE(pamh);
     423  
     424      /* call the list of module functions */
     425      pamh->choice = choice;
     426      retval = _pam_dispatch_aux(pamh, flags, h, resumed, use_cached_chain);
     427  
     428      __PAM_TO_APP(pamh);
     429  
     430      /* Should we recall where to resume next time? */
     431      if (retval == PAM_INCOMPLETE) {
     432  	D(("module [%d] returned PAM_INCOMPLETE"));
     433  	pamh->former.choice = choice;
     434      } else {
     435  	pamh->former.choice = PAM_NOT_STACKED;
     436      }
     437  
     438  end:
     439  
     440  #ifdef HAVE_LIBAUDIT
     441      if (choice != PAM_CHAUTHTOK || flags & PAM_UPDATE_AUTHTOK || retval != PAM_SUCCESS) {
     442  	retval = _pam_auditlog(pamh, choice, retval, flags, h);
     443      }
     444  #endif
     445  
     446      return retval;
     447  }