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 }