2097b09b736361805593ee5662419686d3ccd35e
[freeradius.git] / src / modules / rlm_x99_token / x99_rlm.c
1 /*
2  * x99_rlm.c
3  * $Id$
4  *
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.
9  *
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.
14  *
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
18  *
19  * Copyright 2000,2001,2002  The FreeRADIUS server project
20  * Copyright 2001,2002  Google, Inc.
21  */
22
23 /*
24  * STRONG WARNING SECTION:
25  *
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>.
30  * 
31  * Please read the accompanying docs.
32  */
33
34 /*
35  * TODO: support setting multiple auth-types in authorize()
36  * TODO: support soft PIN? ???
37  * TODO: support other than ILP32 (for State)
38  */
39
40
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 #include <fcntl.h>
47 #include <time.h>
48 #include <netinet/in.h> /* htonl() */
49
50 #ifdef FREERADIUS
51 #include "radiusd.h"
52 #include "modules.h"
53 #endif
54 #include "x99.h"
55
56 static const char rcsid[] = "$Id$";
57
58 /* Global data */
59 static int rnd_fd;                      /* fd for random device           */
60 static unsigned char hmac_key[16];      /* to protect State attribute     */
61
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),
65       NULL, PWDFILE },
66     { "syncdir", PW_TYPE_STRING_PTR, offsetof(x99_token_t, syncdir),
67       NULL, 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),
71       NULL, "6" },
72     { "challenge_delay", PW_TYPE_INTEGER, offsetof(x99_token_t, chal_delay),
73       NULL, "30" },
74     { "softfail", PW_TYPE_INTEGER, offsetof(x99_token_t, softfail),
75       NULL, "5" },
76     { "hardfail", PW_TYPE_INTEGER, offsetof(x99_token_t, hardfail),
77       NULL, "0" },
78     { "allow_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_sync),
79       NULL, "yes" },
80     { "fast_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, fast_sync),
81       NULL, "yes" },
82     { "allow_async", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_async),
83       NULL, "no" },
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),
87       NULL, RESYNC_REQ },
88     { "ewindow_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow_size),
89       NULL, "0" },
90     { "ewindow2_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow2_size),
91       NULL, "0" },
92     { "ewindow2_delay", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow2_delay),
93       NULL, "60" },
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" },
102 #if 0
103     { "twindow_min", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_min),
104       NULL, "0" },
105     { "twindow_max", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_max),
106       NULL, "0" },
107 #endif
108
109     { NULL, -1, 0, NULL, NULL }         /* end the list */
110 };
111
112
113 /* per-module initialization */
114 static int
115 x99_token_init(void)
116 {
117     if ((rnd_fd = open(DEVURANDOM, O_RDONLY)) == -1) {
118         x99_log(X99_LOG_ERR, "init: error opening %s: %s", DEVURANDOM,
119                 strerror(errno));
120         return -1;
121     }
122
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");
126         return -1;
127     }
128
129     /* Initialize the passcode encoding/checking functions. */
130     x99_pwe_init();
131
132     return 0;
133 }
134
135
136 /* per-instance initialization */
137 static int
138 x99_token_instantiate(CONF_SECTION *conf, void **instance)
139 {
140     x99_token_t *data;
141     char *p;
142     struct stat st;
143
144     /* Set up a storage area for instance data. */
145     data = rad_malloc(sizeof(*data));
146
147     /* If the configuration parameters can't be parsed, then fail. */
148     if (cf_section_parse(conf, data, module_config) < 0) {
149         free(data);
150         return -1;
151     }
152
153     /* Verify ranges for those vars that are limited. */
154     if ((data->chal_len < 5) || (data->chal_len > MAX_CHALLENGE_LEN)) {
155         data->chal_len = 6;
156         x99_log(X99_LOG_ERR,
157                 "invalid challenge_length, range 5-%d, using default of 6",
158                 MAX_CHALLENGE_LEN);
159
160     }
161
162     /* Enforce a single "%" sequence, which must be "%s" */
163     p = strchr(data->chal_prompt, '%');
164     if ((p == NULL) || (p != strrchr(data->chal_prompt, '%')) ||
165         strncmp(p,"%s",2)){
166         free(data->chal_prompt);
167         data->chal_prompt = strdup(CHALLENGE_PROMPT);
168         x99_log(X99_LOG_ERR,
169                 "invalid challenge_prompt, using default of \"%s\"",
170                 CHALLENGE_PROMPT);
171     }
172
173     if (data->softfail < 0) {
174         data->softfail = 5;
175         x99_log(X99_LOG_ERR, "softfail must be at least 1 "
176                 "(or 0 == infinite), using default of 5");
177     }
178
179     if (data->hardfail < 0) {
180         data->hardfail = 0;
181         x99_log(X99_LOG_ERR, "hardfail must be at least 1 "
182                 "(or 0 == infinite), using default of 0");
183     }
184
185     if (data->fast_sync && !data->allow_sync) {
186         data->fast_sync = 0;
187         x99_log(X99_LOG_INFO,
188                 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
189     }
190
191     if (!data->allow_sync && !data->allow_async) {
192         x99_log(X99_LOG_ERR,
193                 "at least one of {allow_async, allow_sync} must be set");
194         free(data);
195         return -1;
196     }
197
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",
201                 MAX_EWINDOW_SIZE);
202     }
203
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");
208     }
209
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");
214     }
215
216     if ((data->mschapv2_mppe_policy > 2) || (data->mschapv2_mppe_policy < 0)) {
217         data->mschapv2_mppe_policy = 2;
218         x99_log(X99_LOG_ERR,
219                 "invalid value for mschapv2_mppe, using default of 2");
220     }
221
222     if ((data->mschapv2_mppe_types > 2) || (data->mschapv2_mppe_types < 0)) {
223         data->mschapv2_mppe_types = 2;
224         x99_log(X99_LOG_ERR,
225                 "invalid value for mschapv2_mppe_bits, using default of 2");
226     }
227
228     if ((data->mschap_mppe_policy > 2) || (data->mschap_mppe_policy < 0)) {
229         data->mschap_mppe_policy = 2;
230         x99_log(X99_LOG_ERR,
231                 "invalid value for mschap_mppe, using default of 2");
232     }
233
234     if (data->mschap_mppe_types != 2) {
235         data->mschap_mppe_types = 2;
236         x99_log(X99_LOG_ERR,
237                 "invalid value for mschap_mppe_bits, using default of 2");
238     }
239
240 #if 0
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",
244                 MAX_TWINDOW_SIZE);
245     }
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;
249         x99_log(X99_LOG_ERR,
250                 "invalid values for time window, using default of 0");
251     }
252 #endif
253
254     if (stat(data->syncdir, &st) != 0) {
255         x99_log(X99_LOG_ERR, "syncdir %s error: %s",
256                 data->syncdir, strerror(errno));
257         free(data);
258         return -1;
259     }
260     if (st.st_mode != (S_IFDIR|S_IRWXU)) {
261         x99_log(X99_LOG_ERR, "syncdir %s has loose permissions", data->syncdir);
262         free(data);
263         return -1;
264     }
265
266     /* Set the instance name (for use with authorize()) */
267     data->name = cf_section_name2(conf);
268     if (!data->name)
269         data->name = cf_section_name1(conf);
270     if (!data->name) {
271         x99_log(X99_LOG_ERR, "no instance name (this can't happen)");
272         free(data);
273         return -1;
274     }
275
276     *instance = data;
277     return 0;
278 }
279
280
281 /* Generate a challenge to be presented to the user. */
282 static int
283 x99_token_authorize(void *instance, REQUEST *request)
284 {
285     x99_token_t *inst = (x99_token_t *) instance;
286
287     char challenge[MAX_CHALLENGE_LEN + 1];      /* +1 for '\0' terminator */
288     char *state;
289     int rc;
290
291     x99_user_info_t user_info;
292     int user_found, auth_type_found;
293     int pwattr;
294     int32_t sflags = 0; /* flags for state */
295     VALUE_PAIR *vp;
296
297     /* Early exit if Auth-Type != inst->name */
298     auth_type_found = 0;
299     if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
300         auth_type_found = 1;
301         if (strcmp(vp->strvalue, inst->name)) {
302             return RLM_MODULE_NOOP;
303         }
304     }
305
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;
310     }
311
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;
317     }
318
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;
323     }
324
325     /* Look up the user's info. */
326     user_found = 1;
327     if ((rc = x99_get_user_info(inst->pwdfile, request->username->strvalue,
328                                 &user_info)) == -2) {
329 #if 0
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);
333 #endif
334         return RLM_MODULE_FAIL;
335     }
336     if (rc == -1) {
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 */
340         user_found = 0;
341     }
342
343     /* fast_sync mode (challenge only if requested) */
344     if (inst->fast_sync &&
345         ((user_info.card_id & X99_CF_SM) || !user_found)) {
346
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)) {
350             /*
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.
359              */
360             DEBUG("rlm_x99_token: autz: fast_sync challenge requested");
361             goto gen_challenge;
362
363         } else {
364             /*
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.
368              */
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;
372             }
373             pairadd(&request->config_items, vp);
374             DEBUG("rlm_x99_token: autz: using fast_sync");
375
376             if (!auth_type_found)
377                 pairadd(&request->config_items,
378                         pairmake("Auth-Type", "x99_token", T_OP_EQ));
379             return RLM_MODULE_OK;
380
381         }
382     } /* if (fast_sync && card supports sync mode) */
383
384 gen_challenge:
385     /* Set the resync bit by default if the user can't request it. */
386     if (!inst->fast_sync)
387         sflags |= htonl(1);
388
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;
393     }
394
395     /*
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().
403      */
404     if (user_info.card_id & X99_CF_AM) {
405         time_t now = time(NULL);
406
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;
410         }
411         now = htonl(now);
412
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;
416         }
417     } else {
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);
421     }
422     pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
423     free(state);
424
425     /* Add the challenge to the reply. */
426     {
427         char *u_challenge;      /* challenge with addt'l presentation text */
428
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));
433         free(u_challenge);
434     }
435
436     /*
437      * Mark the packet as an Access-Challenge packet.
438      * The server will take care of sending it to the user.
439      */
440     request->reply->code = PW_ACCESS_CHALLENGE;
441     DEBUG("rlm_x99_token: Sending Access-Challenge.");
442
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;
448 }
449
450
451 /* Verify the response entered by the user. */
452 static int
453 x99_token_authenticate(void *instance, REQUEST *request)
454 {
455     x99_token_t *inst = (x99_token_t *) instance;
456
457     x99_user_info_t user_info;
458     char *username;
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 */
463
464     char challenge[MAX_CHALLENGE_LEN + 1];
465     char e_response[9];         /* expected response */
466     VALUE_PAIR *add_vps = NULL;
467
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;
473     }
474     username = request->username->strvalue;
475
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;
480     }
481
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));
487
488     /* Look up the user's info. */
489     if (x99_get_user_info(inst->pwdfile, username, &user_info) != 0) {
490 #if 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);
493 #endif
494         return RLM_MODULE_REJECT;
495     }
496
497     /* Retrieve the challenge (from State attribute), unless (fast_sync). */
498     if (pairfind(request->config_items, PW_X99_FAST) == NULL) {
499         VALUE_PAIR      *vp;
500         unsigned char   *state;
501         time_t          then;
502
503         if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
504             int e_length = inst->chal_len;
505
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 */
509
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;
514             }
515
516             /* Fast path if we didn't protect the state. */
517             if (!(user_info.card_id & X99_CF_AM))
518                 goto good_state;
519
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;
528             }
529             if (memcmp(state, vp->strvalue, vp->length)) {
530                 x99_log(X99_LOG_AUTH,
531                         "auth: bad state for [%s]: hmac", username);
532                 free(state);
533                 return RLM_MODULE_REJECT;
534             }
535             free(state);
536
537             /* State is valid, but check expiry. */
538             then = ntohl(then);
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;
543             }
544 good_state:
545             /* State is good! */
546
547         } else {
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?)",
551                     username);
552             return RLM_MODULE_FAIL;
553         }
554     } /* if (!fast_sync) */
555
556     /* Get the time of the last authentication. */
557     if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
558         x99_log(X99_LOG_ERR,
559                 "auth: unable to get last auth time for [%s]", username);
560         return RLM_MODULE_FAIL;
561     }
562
563     /* Check failure count. */
564     fc = x99_check_failcount(username, inst);
565     if ((fc == FAIL_ERR) || (fc == FAIL_HARD))
566         return RLM_MODULE_USERLOCK;
567
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;
572
573         if (!pairfind(request->config_items, PW_X99_FAST)) {
574             /*
575              * ewindow2 softfail override requires two consecutive sync
576              * responses.  Fail, and record that this was async.
577              */
578             if (x99_set_last_auth_pos(inst->syncdir, username, 0))
579                 x99_log(X99_LOG_ERR,
580                         "auth: failed to record last auth pos for [%s]",
581                         username);
582             return RLM_MODULE_USERLOCK;
583         }
584
585         /* We're now in "ewindow2 mode" ... subsequent logic must test fc */
586         goto sync_response;
587     }
588
589     /*
590      * Don't bother to check async response if either
591      * - the card doesn't support it, or
592      * - we're doing fast_sync.
593      */
594     if (!(user_info.card_id & X99_CF_AM) ||
595         pairfind(request->config_items, PW_X99_FAST)) {
596         goto sync_response;
597     }
598
599     /* Perform any site-specific transforms of the challenge. */
600     if (x99_challenge_transform(username, challenge) != 0) {
601         x99_log(X99_LOG_ERR,
602                 "auth: challenge transform failed for [%s]", username);
603         return RLM_MODULE_FAIL;
604         /* NB: last_auth, failcount not updated. */
605     }
606
607     /* Calculate and test the async response. */
608     if (x99_response(challenge, e_response, user_info.card_id,
609                      user_info.keyblock) != 0) {
610         x99_log(X99_LOG_ERR,
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. */
615     }
616     DEBUG("rlm_x99_token: auth: [%s], async challenge %s, "
617           "expecting response %s", username, challenge, e_response);
618
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. */
627         }
628
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. */
636         }
637
638         if (user_info.card_id & X99_CF_SM) {
639             x99_log(X99_LOG_INFO,
640                     "auth: [%s] authenticated in async mode", username);
641         }
642
643         rc = RLM_MODULE_OK;
644         if (ntohl(sflags) & 1) {
645             /*
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).
652              */
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) {
660                 x99_log(X99_LOG_ERR,
661                         "auth: unable to set sync data for [%s] (for resync)",
662                         username);
663                 rc = RLM_MODULE_FAIL;
664             }
665         } else {
666             /* Just update failcount, last_auth, auth_pos. */
667             if (x99_reset_failcount(inst->syncdir, username) != 0) {
668                 x99_log(X99_LOG_ERR,
669                         "auth: unable to reset failcount for [%s]", username);
670                 rc = RLM_MODULE_FAIL;
671             }
672         }
673         goto return_pw_valid;
674     } /* if (user authenticated async) */
675
676 sync_response:
677     /*
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.
681      */
682     if ((user_info.card_id & X99_CF_SM) && inst->allow_sync) {
683         int start = 0, end = inst->ewindow_size;
684
685         /*
686          * Tweak start,end for ewindow2_size logic.
687          *
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
695          * sequence).
696          *
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.
700          *
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).
707          */
708         if (fc == FAIL_SOFT) {
709             start = x99_get_last_auth_pos(inst->syncdir, username);
710             end = inst->ewindow2_size;
711         }
712
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) {
718                 x99_log(X99_LOG_ERR,
719                         "auth: unable to get sync data e:%d t:%d for [%s]",
720                         i, 0, username);
721                 rc = RLM_MODULE_FAIL;
722                 goto return_pw_valid;
723                 /* NB: last_auth, failcount not updated. */
724             }
725
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. */
735             }
736             DEBUG("rlm_x99_token: auth: [%s], sync challenge %d %s, "
737                   "expecting response %s", username, i, challenge, e_response);
738
739             /* Test user-supplied passcode. */
740             if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
741                 /*
742                  * Yay!  User authenticated via sync mode.  Resync.
743                  */
744                 rc = RLM_MODULE_OK;
745
746                 /*
747                  * ewindow2_size logic
748                  */
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);
758                     } else {
759                         /* correct, but not consecutive or not soon enough */
760                         DEBUG("rlm_x99_token: auth: [%s] ewindow2 candidate "
761                               "at position %i", username, i);
762                         auth_pos = i;
763                         rc = RLM_MODULE_REJECT;
764                         break;
765                     }
766                 }
767
768                 /*
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)
773                  */
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) {
781                     x99_log(X99_LOG_ERR,
782                             "auth: unable to set sync data for [%s] "
783                             "(for resync)", username);
784                     rc = RLM_MODULE_FAIL;
785                 }
786                 goto return_pw_valid;
787
788             } /* if (passcode is valid) */
789         } /* for (each slot in the window) */
790     } /* if (card is in sync mode and sync mode allowed) */
791
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)) {
795         x99_log(X99_LOG_ERR,
796                 "auth: unable to increment failure count for user [%s]",
797                 username);
798     }
799     if (x99_set_last_auth_pos(inst->syncdir, username, auth_pos)) {
800         x99_log(X99_LOG_ERR,
801                 "auth: unable to set ewindow2 position for user [%s]",
802                 username);
803     }
804     return RLM_MODULE_REJECT;
805
806     /* Must exit here after a successful return from x99_pw_valid(). */
807 return_pw_valid:
808
809     /* Handle any vps returned from x99_pw_valid(). */
810     if (rc == RLM_MODULE_OK) {
811         pairadd(&request->reply->vps, add_vps);
812     } else {
813         pairfree(&add_vps);
814     }
815     return rc;
816 }
817
818
819 /* per-instance destruction */
820 static int
821 x99_token_detach(void *instance)
822 {
823     x99_token_t *inst = (x99_token_t *) instance;
824
825     free(inst->pwdfile);
826     free(inst->syncdir);
827     free(inst->chal_prompt);
828     free(inst->chal_req);
829     free(inst->resync_req);
830     free(instance);
831     return 0;
832 }
833
834
835 /* per-module destruction */
836 static int
837 x99_token_destroy(void)
838 {
839     (void) memset(hmac_key, 0, sizeof(hmac_key));
840     (void) close(rnd_fd);
841     return 0;
842 }
843
844 /*
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.
849  */
850 module_t rlm_x99_token = {
851         "x99_token",    
852         RLM_TYPE_THREAD_SAFE,           /* type */
853         x99_token_init,                 /* initialization */
854         x99_token_instantiate,          /* instantiation */
855         {
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 */
863                 NULL                    /* post-auth */
864         },
865         x99_token_detach,               /* detach */
866         x99_token_destroy,              /* destroy */
867 };