5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 * Copyright 2000,2001,2002 The FreeRADIUS server project
20 * Copyright 2001,2002 Google, Inc.
24 * STRONG WARNING SECTION:
26 * ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
27 * An attacker can learn the token's secret by observing two
28 * challenge/response pairs. See ANSI document X9 TG-24-1999
29 * <URL:http://www.x9.org/TG24_1999.pdf>.
31 * Please read the accompanying docs.
35 * TODO: support setting multiple auth-types in authorize()
36 * TODO: support soft PIN? ???
37 * TODO: support other than ILP32 (for State)
43 #include <sys/types.h>
48 #include <netinet/in.h> /* htonl() */
56 static const char rcsid[] = "$Id$";
59 static int rnd_fd; /* fd for random device */
60 static unsigned char hmac_key[16]; /* to protect State attribute */
62 /* A mapping of configuration file names to internal variables. */
63 static CONF_PARSER module_config[] = {
64 { "pwdfile", PW_TYPE_STRING_PTR, offsetof(x99_token_t, pwdfile),
66 { "syncdir", PW_TYPE_STRING_PTR, offsetof(x99_token_t, syncdir),
68 { "challenge_prompt", PW_TYPE_STRING_PTR, offsetof(x99_token_t,chal_prompt),
69 NULL, CHALLENGE_PROMPT },
70 { "challenge_length", PW_TYPE_INTEGER, offsetof(x99_token_t, chal_len),
72 { "challenge_delay", PW_TYPE_INTEGER, offsetof(x99_token_t, chal_delay),
74 { "softfail", PW_TYPE_INTEGER, offsetof(x99_token_t, softfail),
76 { "hardfail", PW_TYPE_INTEGER, offsetof(x99_token_t, hardfail),
78 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_sync),
80 { "fast_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, fast_sync),
82 { "allow_async", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_async),
84 { "challenge_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, chal_req),
85 NULL, CHALLENGE_REQ },
86 { "resync_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, resync_req),
88 { "ewindow_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow_size),
90 { "ewindow2_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow2_size),
92 { "ewindow2_delay", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow2_delay),
94 { "mschapv2_mppe", PW_TYPE_INTEGER,
95 offsetof(x99_token_t, mschapv2_mppe_policy), NULL, "2" },
96 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
97 offsetof(x99_token_t, mschapv2_mppe_types), NULL, "2" },
98 { "mschap_mppe", PW_TYPE_INTEGER,
99 offsetof(x99_token_t, mschap_mppe_policy), NULL, "2" },
100 { "mschap_mppe_bits", PW_TYPE_INTEGER,
101 offsetof(x99_token_t, mschap_mppe_types), NULL, "2" },
103 { "twindow_min", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_min),
105 { "twindow_max", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_max),
109 { NULL, -1, 0, NULL, NULL } /* end the list */
113 /* per-module initialization */
117 if ((rnd_fd = open(DEVURANDOM, O_RDONLY)) == -1) {
118 x99_log(X99_LOG_ERR, "init: error opening %s: %s", DEVURANDOM,
123 /* Generate a random key, used to protect the State attribute. */
124 if (x99_get_random(rnd_fd, hmac_key, sizeof(hmac_key)) == -1) {
125 x99_log(X99_LOG_ERR, "init: failed to obtain random data for hmac_key");
129 /* Initialize the passcode encoding/checking functions. */
136 /* per-instance initialization */
138 x99_token_instantiate(CONF_SECTION *conf, void **instance)
144 /* Set up a storage area for instance data. */
145 data = rad_malloc(sizeof(*data));
147 /* If the configuration parameters can't be parsed, then fail. */
148 if (cf_section_parse(conf, data, module_config) < 0) {
153 /* Verify ranges for those vars that are limited. */
154 if ((data->chal_len < 5) || (data->chal_len > MAX_CHALLENGE_LEN)) {
157 "invalid challenge_length, range 5-%d, using default of 6",
162 /* Enforce a single "%" sequence, which must be "%s" */
163 p = strchr(data->chal_prompt, '%');
164 if ((p == NULL) || (p != strrchr(data->chal_prompt, '%')) ||
166 free(data->chal_prompt);
167 data->chal_prompt = strdup(CHALLENGE_PROMPT);
169 "invalid challenge_prompt, using default of \"%s\"",
173 if (data->softfail < 0) {
175 x99_log(X99_LOG_ERR, "softfail must be at least 1 "
176 "(or 0 == infinite), using default of 5");
179 if (data->hardfail < 0) {
181 x99_log(X99_LOG_ERR, "hardfail must be at least 1 "
182 "(or 0 == infinite), using default of 0");
185 if (data->fast_sync && !data->allow_sync) {
187 x99_log(X99_LOG_INFO,
188 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
191 if (!data->allow_sync && !data->allow_async) {
193 "at least one of {allow_async, allow_sync} must be set");
198 if ((data->ewindow_size > MAX_EWINDOW_SIZE) || (data->ewindow_size < 0)) {
199 data->ewindow_size = 0;
200 x99_log(X99_LOG_ERR, "max ewindow_size is %d, using default of 0",
204 if (data->ewindow2_size && (data->ewindow2_size < data->ewindow_size)) {
205 data->ewindow2_size = 0;
206 x99_log(X99_LOG_ERR, "ewindow2_size must be at least as large as "
207 "ewindow_size, using default of 0");
210 if (data->ewindow2_size && !data->ewindow2_delay) {
211 data->ewindow2_size = 0;
212 x99_log(X99_LOG_ERR, "ewindow2_size is non-zero, "
213 "but ewindow2_delay is zero; disabling ewindow2");
216 if ((data->mschapv2_mppe_policy > 2) || (data->mschapv2_mppe_policy < 0)) {
217 data->mschapv2_mppe_policy = 2;
219 "invalid value for mschapv2_mppe, using default of 2");
222 if ((data->mschapv2_mppe_types > 2) || (data->mschapv2_mppe_types < 0)) {
223 data->mschapv2_mppe_types = 2;
225 "invalid value for mschapv2_mppe_bits, using default of 2");
228 if ((data->mschap_mppe_policy > 2) || (data->mschap_mppe_policy < 0)) {
229 data->mschap_mppe_policy = 2;
231 "invalid value for mschap_mppe, using default of 2");
234 if (data->mschap_mppe_types != 2) {
235 data->mschap_mppe_types = 2;
237 "invalid value for mschap_mppe_bits, using default of 2");
241 if (data->twindow_max - data->twindow_min > MAX_TWINDOW_SIZE) {
242 data->twindow_min = data->twindow_max = 0;
243 x99_log(X99_LOG_ERR, "max time window size is %d, using default of 0",
246 if ((data->twindow_min > 0) || (data->twindow_max < 0) ||
247 (data->twindow_max < data->twindow_min)) {
248 data->twindow_min = data->twindow_max = 0;
250 "invalid values for time window, using default of 0");
254 if (stat(data->syncdir, &st) != 0) {
255 x99_log(X99_LOG_ERR, "syncdir %s error: %s",
256 data->syncdir, strerror(errno));
260 if (st.st_mode != (S_IFDIR|S_IRWXU)) {
261 x99_log(X99_LOG_ERR, "syncdir %s has loose permissions", data->syncdir);
266 /* Set the instance name (for use with authorize()) */
267 data->name = cf_section_name2(conf);
269 data->name = cf_section_name1(conf);
271 x99_log(X99_LOG_ERR, "no instance name (this can't happen)");
281 /* Generate a challenge to be presented to the user. */
283 x99_token_authorize(void *instance, REQUEST *request)
285 x99_token_t *inst = (x99_token_t *) instance;
287 char challenge[MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
291 x99_user_info_t user_info;
292 int user_found, auth_type_found;
294 int32_t sflags = 0; /* flags for state */
297 /* Early exit if Auth-Type != inst->name */
299 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
301 if (strcmp(vp->strvalue, inst->name)) {
302 return RLM_MODULE_NOOP;
306 /* The State attribute will be present if this is a response. */
307 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
308 DEBUG("rlm_x99_token: autz: Found response to access challenge");
309 return RLM_MODULE_OK;
312 /* User-Name attribute required. */
313 if (!request->username) {
314 x99_log(X99_LOG_AUTH,
315 "autz: Attribute \"User-Name\" required for authentication.");
316 return RLM_MODULE_INVALID;
319 if ((pwattr = x99_pw_present(request)) == 0) {
320 x99_log(X99_LOG_AUTH, "autz: Attribute \"User-Password\" "
321 "or equivalent required for authentication.");
322 return RLM_MODULE_INVALID;
325 /* Look up the user's info. */
327 if ((rc = x99_get_user_info(inst->pwdfile, request->username->strvalue,
328 &user_info)) == -2) {
330 /* x99_get_user_info() logs a more useful message, this is noisy. */
331 x99_log(X99_LOG_ERR, "autz: error reading user [%s] info",
332 request->username->strvalue);
334 return RLM_MODULE_FAIL;
337 x99_log(X99_LOG_AUTH, "autz: user [%s] not found in %s",
338 request->username->strvalue, inst->pwdfile);
339 memset(&user_info, 0, sizeof(user_info)); /* X99_CF_NONE */
343 /* fast_sync mode (challenge only if requested) */
344 if (inst->fast_sync &&
345 ((user_info.card_id & X99_CF_SM) || !user_found)) {
347 if ((x99_pw_valid(request, inst, pwattr, inst->resync_req, NULL) &&
348 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
349 x99_pw_valid(request, inst, pwattr, inst->chal_req, NULL)) {
351 * Generate a challenge if requested. We don't test for card
352 * support [for async] because it's tricky for unknown users.
353 * Some configurations would have a problem where known users
354 * cannot request a challenge, but unknown users can. This
355 * reveals information. The easiest fix seems to be to always
356 * hand out a challenge on request.
357 * We also don't test if the server allows async mode, this
358 * would also reveal information.
360 DEBUG("rlm_x99_token: autz: fast_sync challenge requested");
365 * Otherwise, this is the token sync response. Signal
366 * the authenticate code to ignore State. We don't need
367 * to set a value, /existence/ of the vp is the signal.
369 if ((vp = paircreate(PW_X99_FAST, PW_TYPE_INTEGER)) == NULL) {
370 x99_log(X99_LOG_CRIT, "autz: no memory");
371 return RLM_MODULE_FAIL;
373 pairadd(&request->config_items, vp);
374 DEBUG("rlm_x99_token: autz: using fast_sync");
376 if (!auth_type_found)
377 pairadd(&request->config_items,
378 pairmake("Auth-Type", "x99_token", T_OP_EQ));
379 return RLM_MODULE_OK;
382 } /* if (fast_sync && card supports sync mode) */
385 /* Set the resync bit by default if the user can't request it. */
386 if (!inst->fast_sync)
389 /* Generate a random challenge. */
390 if (x99_get_challenge(rnd_fd, challenge, inst->chal_len) == -1) {
391 x99_log(X99_LOG_ERR, "autz: failed to obtain random challenge");
392 return RLM_MODULE_FAIL;
396 * Create the State attribute, which will be returned to us along with
397 * the response. We will need this to verify the response. Create
398 * a strong state if the user will be able use this with their token.
399 * Otherwise, we discard it anyway, so don't "waste" time with hmac.
400 * We also don't do the hmac if the user wasn't found (mask won't match).
401 * We always create at least a trivial state, so x99_token_authorize()
402 * can easily pass on to x99_token_authenticate().
404 if (user_info.card_id & X99_CF_AM) {
405 time_t now = time(NULL);
407 if (sizeof(now) != 4 || sizeof(long) != 4) {
408 x99_log(X99_LOG_ERR, "autz: only ILP32 arch is supported");
409 return RLM_MODULE_FAIL;
413 if (x99_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
414 x99_log(X99_LOG_ERR, "autz: failed to generate state");
415 return RLM_MODULE_FAIL;
418 /* x2 b/c pairmake() string->octet needs even num of digits */
419 state = rad_malloc(3 + inst->chal_len * 2);
420 (void) sprintf(state, "0x%s%s", challenge, challenge);
422 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
425 /* Add the challenge to the reply. */
427 char *u_challenge; /* challenge with addt'l presentation text */
429 u_challenge = rad_malloc(strlen(inst->chal_prompt)+MAX_CHALLENGE_LEN+1);
430 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
431 pairadd(&request->reply->vps,
432 pairmake("Reply-Message", u_challenge, T_OP_EQ));
437 * Mark the packet as an Access-Challenge packet.
438 * The server will take care of sending it to the user.
440 request->reply->code = PW_ACCESS_CHALLENGE;
441 DEBUG("rlm_x99_token: Sending Access-Challenge.");
443 /* TODO: support config-specific auth-type */
444 if (!auth_type_found)
445 pairadd(&request->config_items,
446 pairmake("Auth-Type", "x99_token", T_OP_EQ));
447 return RLM_MODULE_HANDLED;
451 /* Verify the response entered by the user. */
453 x99_token_authenticate(void *instance, REQUEST *request)
455 x99_token_t *inst = (x99_token_t *) instance;
457 x99_user_info_t user_info;
459 int i, pwattr, rc, fc;
460 int32_t sflags = 0; /* flags from state */
461 time_t last_auth; /* time of last authentication */
462 unsigned auth_pos = 0; /* window position of last authentication */
464 char challenge[MAX_CHALLENGE_LEN + 1];
465 char e_response[9]; /* expected response */
466 VALUE_PAIR *add_vps = NULL;
468 /* User-Name attribute required. */
469 if (!request->username) {
470 x99_log(X99_LOG_AUTH,
471 "auth: Attribute \"User-Name\" required for authentication.");
472 return RLM_MODULE_INVALID;
474 username = request->username->strvalue;
476 if ((pwattr = x99_pw_present(request)) == 0) {
477 x99_log(X99_LOG_AUTH, "auth: Attribute \"User-Password\" "
478 "or equivalent required for authentication.");
479 return RLM_MODULE_INVALID;
482 /* Add a message to the auth log. */
483 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
484 X99_MODULE_NAME, T_OP_EQ));
485 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
486 X99_MODULE_NAME, T_OP_EQ));
488 /* Look up the user's info. */
489 if (x99_get_user_info(inst->pwdfile, username, &user_info) != 0) {
491 /* x99_get_user_info() logs a more useful message, this is noisy. */
492 x99_log(X99_LOG_AUTH, "auth: error reading user [%s] info", username);
494 return RLM_MODULE_REJECT;
497 /* Retrieve the challenge (from State attribute), unless (fast_sync). */
498 if (pairfind(request->config_items, PW_X99_FAST) == NULL) {
500 unsigned char *state;
503 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
504 int e_length = inst->chal_len;
506 /* Extend expected length if state should have been protected. */
507 if (user_info.card_id & X99_CF_AM)
508 e_length += 4 + 4 + 16; /* sflags + time + hmac */
510 if (vp->length != e_length) {
511 x99_log(X99_LOG_AUTH,
512 "auth: bad state for [%s]: length", username);
513 return RLM_MODULE_INVALID;
516 /* Fast path if we didn't protect the state. */
517 if (!(user_info.card_id & X99_CF_AM))
520 /* Verify the state. */
521 (void) memset(challenge, 0, sizeof(challenge));
522 (void) memcpy(challenge, vp->strvalue, inst->chal_len);
523 (void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
524 (void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
525 if (x99_gen_state(NULL,&state,challenge,sflags,then,hmac_key) != 0){
526 x99_log(X99_LOG_ERR, "auth: failed to generate state");
527 return RLM_MODULE_FAIL;
529 if (memcmp(state, vp->strvalue, vp->length)) {
530 x99_log(X99_LOG_AUTH,
531 "auth: bad state for [%s]: hmac", username);
533 return RLM_MODULE_REJECT;
537 /* State is valid, but check expiry. */
539 if (time(NULL) - then > inst->chal_delay) {
540 x99_log(X99_LOG_AUTH,
541 "auth: bad state for [%s]: expired", username);
542 return RLM_MODULE_REJECT;
548 /* This should only happen if the authorize code didn't run. */
549 x99_log(X99_LOG_ERR, "auth: bad state for [%s]: missing "
550 "(is x99_token listed in radiusd.conf's authorize stanza?)",
552 return RLM_MODULE_FAIL;
554 } /* if (!fast_sync) */
556 /* Get the time of the last authentication. */
557 if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
559 "auth: unable to get last auth time for [%s]", username);
560 return RLM_MODULE_FAIL;
563 /* Check failure count. */
564 fc = x99_check_failcount(username, inst);
565 if ((fc == FAIL_ERR) || (fc == FAIL_HARD))
566 return RLM_MODULE_USERLOCK;
568 /* Some checks for ewindow2_size logic. */
569 if (fc == FAIL_SOFT) {
570 if (!inst->ewindow2_size) /* no auto-resync */
571 return RLM_MODULE_USERLOCK;
573 if (!pairfind(request->config_items, PW_X99_FAST)) {
575 * ewindow2 softfail override requires two consecutive sync
576 * responses. Fail, and record that this was async.
578 if (x99_set_last_auth_pos(inst->syncdir, username, 0))
580 "auth: failed to record last auth pos for [%s]",
582 return RLM_MODULE_USERLOCK;
585 /* We're now in "ewindow2 mode" ... subsequent logic must test fc */
590 * Don't bother to check async response if either
591 * - the card doesn't support it, or
592 * - we're doing fast_sync.
594 if (!(user_info.card_id & X99_CF_AM) ||
595 pairfind(request->config_items, PW_X99_FAST)) {
599 /* Perform any site-specific transforms of the challenge. */
600 if (x99_challenge_transform(username, challenge) != 0) {
602 "auth: challenge transform failed for [%s]", username);
603 return RLM_MODULE_FAIL;
604 /* NB: last_auth, failcount not updated. */
607 /* Calculate and test the async response. */
608 if (x99_response(challenge, e_response, user_info.card_id,
609 user_info.keyblock) != 0) {
611 "auth: unable to calculate async response for [%s], "
612 "to challenge %s", username, challenge);
613 return RLM_MODULE_FAIL;
614 /* NB: last_auth, failcount not updated. */
616 DEBUG("rlm_x99_token: auth: [%s], async challenge %s, "
617 "expecting response %s", username, challenge, e_response);
619 if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
620 /* Password matches. Is this allowed? */
621 if (!inst->allow_async) {
622 x99_log(X99_LOG_AUTH,
623 "auth: bad async for [%s]: disallowed by config", username);
624 rc = RLM_MODULE_REJECT;
625 goto return_pw_valid;
626 /* NB: last_auth, failcount not updated. */
629 /* Make sure this isn't a replay by forcing a delay. */
630 if (time(NULL) - last_auth < inst->chal_delay) {
631 x99_log(X99_LOG_AUTH,
632 "auth: bad async for [%s]: too soon", username);
633 rc = RLM_MODULE_REJECT;
634 goto return_pw_valid;
635 /* NB: last_auth, failcount not updated. */
638 if (user_info.card_id & X99_CF_SM) {
639 x99_log(X99_LOG_INFO,
640 "auth: [%s] authenticated in async mode", username);
644 if (ntohl(sflags) & 1) {
646 * Resync the card. The sync data doesn't mean anything for
647 * async-only cards, but we want the side effects of resetting
648 * the failcount and the last auth time. We "fail-out" if we
649 * can't do this, because if we can't update the last auth time,
650 * we will be open to replay attacks over the lifetime of the
651 * State attribute (inst->chal_delay).
653 if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
654 1, 0, challenge, user_info.keyblock) != 0) {
655 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
656 "e:%d t:%d for [%s] (for resync)", 1, 0, username);
657 rc = RLM_MODULE_FAIL;
658 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
659 user_info.keyblock) != 0) {
661 "auth: unable to set sync data for [%s] (for resync)",
663 rc = RLM_MODULE_FAIL;
666 /* Just update failcount, last_auth, auth_pos. */
667 if (x99_reset_failcount(inst->syncdir, username) != 0) {
669 "auth: unable to reset failcount for [%s]", username);
670 rc = RLM_MODULE_FAIL;
673 goto return_pw_valid;
674 } /* if (user authenticated async) */
678 * Calculate and test sync responses in the window.
679 * Note that we always accept a sync response, even
680 * if a challenge or resync was explicitly requested.
682 if ((user_info.card_id & X99_CF_SM) && inst->allow_sync) {
683 int start = 0, end = inst->ewindow_size;
686 * Tweak start,end for ewindow2_size logic.
688 * If user is in softfail, and their last response was correct,
689 * start at that response. We used to start at the NEXT
690 * response (the one that will let them in), but the MS Windows
691 * "incorrect password" dialog is confusing and users end up
692 * reusing the same password twice; this has the effect that
693 * ewindow2 doesn't work at all for them (they enter 1,1,2,2,3,3;
694 * the 1,2 or 2,3 wouldn't work since the repeat would reset the
697 * The response sequence 6,5,6 won't work (but 6,5,6,7 will).
698 * That's OK; we want to optimize for the 6,7 sequence. The user
699 * can't generate the 6,5 sequence from the token anyway.
701 * If the user starts at the left edge of the window (0,1,2) they
702 * have to enter three responses. We don't accept the zeroeth
703 * response as part of the sequence because we can't differentiate
704 * between a correct entry of the zeroeth response (which stores
705 * 0 as the last_auth_pos) and an incorrect entry (which "resets"
706 * the last_auth_pos to 0).
708 if (fc == FAIL_SOFT) {
709 start = x99_get_last_auth_pos(inst->syncdir, username);
710 end = inst->ewindow2_size;
713 challenge[0] = '\0'; /* initialize for x99_get_sync_data() */
714 for (i = start; i <= end; ++i) {
715 /* Get sync challenge and key. */
716 if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
717 i, 0, challenge, user_info.keyblock) != 0) {
719 "auth: unable to get sync data e:%d t:%d for [%s]",
721 rc = RLM_MODULE_FAIL;
722 goto return_pw_valid;
723 /* NB: last_auth, failcount not updated. */
726 /* Calculate sync response. */
727 if (x99_response(challenge, e_response, user_info.card_id,
728 user_info.keyblock) != 0) {
729 x99_log(X99_LOG_ERR, "auth: unable to calculate sync response "
730 "e:%d t:%d for [%s], to challenge %s",
731 i, 0, username, challenge);
732 rc = RLM_MODULE_FAIL;
733 goto return_pw_valid;
734 /* NB: last_auth, failcount not updated. */
736 DEBUG("rlm_x99_token: auth: [%s], sync challenge %d %s, "
737 "expecting response %s", username, i, challenge, e_response);
739 /* Test user-supplied passcode. */
740 if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
742 * Yay! User authenticated via sync mode. Resync.
747 * ewindow2_size logic
749 if (fc == FAIL_SOFT) {
750 /* User must authenticate twice in a row, ... */
751 if (start && (i == start + 1) &&
752 /* ... within ewindow2_delay seconds. */
753 (time(NULL) - last_auth < inst->ewindow2_delay)) {
754 /* This is the 2nd of two consecutive responses. */
755 x99_log(X99_LOG_AUTH,
756 "auth: ewindow2 softfail override for [%s] at "
757 "window position %d", username, i);
759 /* correct, but not consecutive or not soon enough */
760 DEBUG("rlm_x99_token: auth: [%s] ewindow2 candidate "
761 "at position %i", username, i);
763 rc = RLM_MODULE_REJECT;
769 * The same failure/replay issue applies here as in the
770 * identical code block in the async section above, with
771 * the additional problem that a response can be reused
772 * indefinitely! (until the sync data is updated)
774 if (x99_get_sync_data(inst->syncdir,username,user_info.card_id,
775 1, 0, challenge,user_info.keyblock) != 0){
776 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
777 "e:%d t:%d for [%s] (for resync)", 1, 0, username);
778 rc = RLM_MODULE_FAIL;
779 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
780 user_info.keyblock) != 0) {
782 "auth: unable to set sync data for [%s] "
783 "(for resync)", username);
784 rc = RLM_MODULE_FAIL;
786 goto return_pw_valid;
788 } /* if (passcode is valid) */
789 } /* for (each slot in the window) */
790 } /* if (card is in sync mode and sync mode allowed) */
792 /* Both async and sync mode failed. */
793 if ((fc != FAIL_SOFT) /* !already incremented by x99_check_failcount() */ &&
794 (x99_incr_failcount(inst->syncdir, username) != 0)) {
796 "auth: unable to increment failure count for user [%s]",
799 if (x99_set_last_auth_pos(inst->syncdir, username, auth_pos)) {
801 "auth: unable to set ewindow2 position for user [%s]",
804 return RLM_MODULE_REJECT;
806 /* Must exit here after a successful return from x99_pw_valid(). */
809 /* Handle any vps returned from x99_pw_valid(). */
810 if (rc == RLM_MODULE_OK) {
811 pairadd(&request->reply->vps, add_vps);
819 /* per-instance destruction */
821 x99_token_detach(void *instance)
823 x99_token_t *inst = (x99_token_t *) instance;
827 free(inst->chal_prompt);
828 free(inst->chal_req);
829 free(inst->resync_req);
835 /* per-module destruction */
837 x99_token_destroy(void)
839 (void) memset(hmac_key, 0, sizeof(hmac_key));
840 (void) close(rnd_fd);
845 * If the module needs to temporarily modify it's instantiation
846 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
847 * The server will then take care of ensuring that the module
848 * is single-threaded.
850 module_t rlm_x99_token = {
852 RLM_TYPE_THREAD_SAFE, /* type */
853 x99_token_init, /* initialization */
854 x99_token_instantiate, /* instantiation */
856 x99_token_authenticate, /* authentication */
857 x99_token_authorize, /* authorization */
858 NULL, /* preaccounting */
859 NULL, /* accounting */
860 NULL, /* checksimul */
861 NULL, /* pre-proxy */
862 NULL, /* post-proxy */
865 x99_token_detach, /* detach */
866 x99_token_destroy, /* destroy */