4bdfda409391d9a3a33d16920352ca47601db72a
[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     if (!data)
147         return -1;
148     memset(data, 0, sizeof(*data));
149
150     /* If the configuration parameters can't be parsed, then fail. */
151     if (cf_section_parse(conf, data, module_config) < 0) {
152         free(data);
153         return -1;
154     }
155
156     /* Verify ranges for those vars that are limited. */
157     if ((data->chal_len < 5) || (data->chal_len > MAX_CHALLENGE_LEN)) {
158         data->chal_len = 6;
159         x99_log(X99_LOG_ERR,
160                 "invalid challenge_length, range 5-%d, using default of 6",
161                 MAX_CHALLENGE_LEN);
162
163     }
164
165     /* Enforce a single "%" sequence, which must be "%s" */
166     p = strchr(data->chal_prompt, '%');
167     if ((p == NULL) || (p != strrchr(data->chal_prompt, '%')) ||
168         strncmp(p,"%s",2)){
169         free(data->chal_prompt);
170         data->chal_prompt = strdup(CHALLENGE_PROMPT);
171         x99_log(X99_LOG_ERR,
172                 "invalid challenge_prompt, using default of \"%s\"",
173                 CHALLENGE_PROMPT);
174     }
175
176     if (data->softfail < 0) {
177         data->softfail = 5;
178         x99_log(X99_LOG_ERR, "softfail must be at least 1 "
179                 "(or 0 == infinite), using default of 5");
180     }
181
182     if (data->hardfail < 0) {
183         data->hardfail = 0;
184         x99_log(X99_LOG_ERR, "hardfail must be at least 1 "
185                 "(or 0 == infinite), using default of 0");
186     }
187
188     if (data->fast_sync && !data->allow_sync) {
189         data->fast_sync = 0;
190         x99_log(X99_LOG_INFO,
191                 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
192     }
193
194     if (!data->allow_sync && !data->allow_async) {
195         x99_log(X99_LOG_ERR,
196                 "at least one of {allow_async, allow_sync} must be set");
197         free(data);
198         return -1;
199     }
200
201     if ((data->ewindow_size > MAX_EWINDOW_SIZE) || (data->ewindow_size < 0)) {
202         data->ewindow_size = 0;
203         x99_log(X99_LOG_ERR, "max ewindow_size is %d, using default of 0",
204                 MAX_EWINDOW_SIZE);
205     }
206
207     if (data->ewindow2_size && (data->ewindow2_size < data->ewindow_size)) {
208         data->ewindow2_size = 0;
209         x99_log(X99_LOG_ERR, "ewindow2_size must be at least as large as "
210                              "ewindow_size, using default of 0");
211     }
212
213     if (data->ewindow2_size && !data->ewindow2_delay) {
214         data->ewindow2_size = 0;
215         x99_log(X99_LOG_ERR, "ewindow2_size is non-zero, "
216                              "but ewindow2_delay is zero; disabling ewindow2");
217     }
218
219     if ((data->mschapv2_mppe_policy > 2) || (data->mschapv2_mppe_policy < 0)) {
220         data->mschapv2_mppe_policy = 2;
221         x99_log(X99_LOG_ERR,
222                 "invalid value for mschapv2_mppe, using default of 2");
223     }
224
225     if ((data->mschapv2_mppe_types > 2) || (data->mschapv2_mppe_types < 0)) {
226         data->mschapv2_mppe_types = 2;
227         x99_log(X99_LOG_ERR,
228                 "invalid value for mschapv2_mppe_bits, using default of 2");
229     }
230
231     if ((data->mschap_mppe_policy > 2) || (data->mschap_mppe_policy < 0)) {
232         data->mschap_mppe_policy = 2;
233         x99_log(X99_LOG_ERR,
234                 "invalid value for mschap_mppe, using default of 2");
235     }
236
237     if (data->mschap_mppe_types != 2) {
238         data->mschap_mppe_types = 2;
239         x99_log(X99_LOG_ERR,
240                 "invalid value for mschap_mppe_bits, using default of 2");
241     }
242
243 #if 0
244     if (data->twindow_max - data->twindow_min > MAX_TWINDOW_SIZE) {
245         data->twindow_min = data->twindow_max = 0;
246         x99_log(X99_LOG_ERR, "max time window size is %d, using default of 0",
247                 MAX_TWINDOW_SIZE);
248     }
249     if ((data->twindow_min > 0) || (data->twindow_max < 0) ||
250         (data->twindow_max < data->twindow_min)) {
251         data->twindow_min = data->twindow_max = 0;
252         x99_log(X99_LOG_ERR,
253                 "invalid values for time window, using default of 0");
254     }
255 #endif
256
257     if (stat(data->syncdir, &st) != 0) {
258         x99_log(X99_LOG_ERR, "syncdir %s error: %s",
259                 data->syncdir, strerror(errno));
260         free(data);
261         return -1;
262     }
263     if (st.st_mode != (S_IFDIR|S_IRWXU)) {
264         x99_log(X99_LOG_ERR, "syncdir %s has loose permissions", data->syncdir);
265         free(data);
266         return -1;
267     }
268
269     /* Set the instance name (for use with authorize()) */
270     data->name = cf_section_name2(conf);
271     if (!data->name)
272         data->name = cf_section_name1(conf);
273     if (!data->name) {
274         x99_log(X99_LOG_ERR, "no instance name (this can't happen)");
275         free(data);
276         return -1;
277     }
278
279     *instance = data;
280     return 0;
281 }
282
283
284 /* Generate a challenge to be presented to the user. */
285 static int
286 x99_token_authorize(void *instance, REQUEST *request)
287 {
288     x99_token_t *inst = (x99_token_t *) instance;
289
290     char challenge[MAX_CHALLENGE_LEN + 1];      /* +1 for '\0' terminator */
291     char *state;
292     int rc;
293
294     x99_user_info_t user_info;
295     int user_found, auth_type_found;
296     int pwattr;
297     int32_t sflags = 0; /* flags for state */
298     VALUE_PAIR *vp;
299
300     /* Early exit if Auth-Type != inst->name */
301     auth_type_found = 0;
302     if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
303         auth_type_found = 1;
304         if (strcmp(vp->strvalue, inst->name)) {
305             return RLM_MODULE_NOOP;
306         }
307     }
308
309     /* The State attribute will be present if this is a response. */
310     if (pairfind(request->packet->vps, PW_STATE) != NULL) {
311         DEBUG("rlm_x99_token: autz: Found response to access challenge");
312         return RLM_MODULE_OK;
313     }
314
315     /* User-Name attribute required. */
316     if (!request->username) {
317         x99_log(X99_LOG_AUTH,
318                 "autz: Attribute \"User-Name\" required for authentication.");
319         return RLM_MODULE_INVALID;
320     }
321
322     if ((pwattr = x99_pw_present(request)) == 0) {
323         x99_log(X99_LOG_AUTH, "autz: Attribute \"User-Password\" "
324                 "or equivalent required for authentication.");
325         return RLM_MODULE_INVALID;
326     }
327
328     /* Look up the user's info. */
329     user_found = 1;
330     if ((rc = x99_get_user_info(inst->pwdfile, request->username->strvalue,
331                                 &user_info)) == -2) {
332 #if 0
333         /* x99_get_user_info() logs a more useful message, this is noisy. */
334         x99_log(X99_LOG_ERR, "autz: error reading user [%s] info",
335                 request->username->strvalue);
336 #endif
337         return RLM_MODULE_FAIL;
338     }
339     if (rc == -1) {
340         x99_log(X99_LOG_AUTH, "autz: user [%s] not found in %s",
341                 request->username->strvalue, inst->pwdfile);
342         memset(&user_info, 0, sizeof(user_info)); /* X99_CF_NONE */
343         user_found = 0;
344     }
345
346     /* fast_sync mode (challenge only if requested) */
347     if (inst->fast_sync &&
348         ((user_info.card_id & X99_CF_SM) || !user_found)) {
349
350         if ((x99_pw_valid(request, inst, pwattr, inst->resync_req, NULL) &&
351                 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
352             x99_pw_valid(request, inst, pwattr, inst->chal_req, NULL)) {
353             /*
354              * Generate a challenge if requested.  We don't test for card
355              * support [for async] because it's tricky for unknown users.
356              * Some configurations would have a problem where known users
357              * cannot request a challenge, but unknown users can.  This
358              * reveals information.  The easiest fix seems to be to always
359              * hand out a challenge on request.
360              * We also don't test if the server allows async mode, this
361              * would also reveal information.
362              */
363             DEBUG("rlm_x99_token: autz: fast_sync challenge requested");
364             goto gen_challenge;
365
366         } else {
367             /*
368              * Otherwise, this is the token sync response.  Signal
369              * the authenticate code to ignore State.  We don't need
370              * to set a value, /existence/ of the vp is the signal.
371              */
372             if ((vp = paircreate(PW_X99_FAST, PW_TYPE_INTEGER)) == NULL) {
373                 x99_log(X99_LOG_CRIT, "autz: no memory");
374                 return RLM_MODULE_FAIL;
375             }
376             pairadd(&request->config_items, vp);
377             DEBUG("rlm_x99_token: autz: using fast_sync");
378
379             if (!auth_type_found)
380                 pairadd(&request->config_items,
381                         pairmake("Auth-Type", "x99_token", T_OP_EQ));
382             return RLM_MODULE_OK;
383
384         }
385     } /* if (fast_sync && card supports sync mode) */
386
387 gen_challenge:
388     /* Set the resync bit by default if the user can't request it. */
389     if (!inst->fast_sync)
390         sflags |= htonl(1);
391
392     /* Generate a random challenge. */
393     if (x99_get_challenge(rnd_fd, challenge, inst->chal_len) == -1) {
394         x99_log(X99_LOG_ERR, "autz: failed to obtain random challenge");
395         return RLM_MODULE_FAIL;
396     }
397
398     /*
399      * Create the State attribute, which will be returned to us along with
400      * the response.  We will need this to verify the response.  Create
401      * a strong state if the user will be able use this with their token.
402      * Otherwise, we discard it anyway, so don't "waste" time with hmac.
403      * We also don't do the hmac if the user wasn't found (mask won't match).
404      * We always create at least a trivial state, so x99_token_authorize()
405      * can easily pass on to x99_token_authenticate().
406      */
407     if (user_info.card_id & X99_CF_AM) {
408         time_t now = time(NULL);
409
410         if (sizeof(now) != 4 || sizeof(long) != 4) {
411             x99_log(X99_LOG_ERR, "autz: only ILP32 arch is supported");
412             return RLM_MODULE_FAIL;
413         }
414         now = htonl(now);
415
416         if (x99_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
417             x99_log(X99_LOG_ERR, "autz: failed to generate state");
418             return RLM_MODULE_FAIL;
419         }
420     } else {
421         /* x2 b/c pairmake() string->octet needs even num of digits */
422         state = rad_malloc(3 + inst->chal_len * 2);
423         (void) sprintf(state, "0x%s%s", challenge, challenge);
424     }
425     pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
426     free(state);
427
428     /* Add the challenge to the reply. */
429     {
430         char *u_challenge;      /* challenge with addt'l presentation text */
431
432         u_challenge = rad_malloc(strlen(inst->chal_prompt)+MAX_CHALLENGE_LEN+1);
433         (void) sprintf(u_challenge, inst->chal_prompt, challenge);
434         pairadd(&request->reply->vps,
435                 pairmake("Reply-Message", u_challenge, T_OP_EQ));
436         free(u_challenge);
437     }
438
439     /*
440      * Mark the packet as an Access-Challenge packet.
441      * The server will take care of sending it to the user.
442      */
443     request->reply->code = PW_ACCESS_CHALLENGE;
444     DEBUG("rlm_x99_token: Sending Access-Challenge.");
445
446     /* TODO: support config-specific auth-type */
447     if (!auth_type_found)
448         pairadd(&request->config_items,
449                 pairmake("Auth-Type", "x99_token", T_OP_EQ));
450     return RLM_MODULE_HANDLED;
451 }
452
453
454 /* Verify the response entered by the user. */
455 static int
456 x99_token_authenticate(void *instance, REQUEST *request)
457 {
458     x99_token_t *inst = (x99_token_t *) instance;
459
460     x99_user_info_t user_info;
461     char *username;
462     int i, pwattr, rc, fc;
463     int32_t sflags = 0;         /* flags from state */
464     time_t last_auth;           /* time of last authentication */
465     unsigned auth_pos = 0;      /* window position of last authentication */
466
467     char challenge[MAX_CHALLENGE_LEN + 1];
468     char e_response[9];         /* expected response */
469     VALUE_PAIR *add_vps = NULL;
470
471     /* User-Name attribute required. */
472     if (!request->username) {
473         x99_log(X99_LOG_AUTH,
474                 "auth: Attribute \"User-Name\" required for authentication.");
475         return RLM_MODULE_INVALID;
476     }
477     username = request->username->strvalue;
478
479     if ((pwattr = x99_pw_present(request)) == 0) {
480         x99_log(X99_LOG_AUTH, "auth: Attribute \"User-Password\" "
481                 "or equivalent required for authentication.");
482         return RLM_MODULE_INVALID;
483     }
484
485     /* Add a message to the auth log. */
486     pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
487                                             X99_MODULE_NAME, T_OP_EQ));
488     pairadd(&request->packet->vps, pairmake("Module-Success-Message",
489                                             X99_MODULE_NAME, T_OP_EQ));
490
491     /* Look up the user's info. */
492     if (x99_get_user_info(inst->pwdfile, username, &user_info) != 0) {
493 #if 0
494         /* x99_get_user_info() logs a more useful message, this is noisy. */
495         x99_log(X99_LOG_AUTH, "auth: error reading user [%s] info", username);
496 #endif
497         return RLM_MODULE_REJECT;
498     }
499
500     /* Retrieve the challenge (from State attribute), unless (fast_sync). */
501     if (pairfind(request->config_items, PW_X99_FAST) == NULL) {
502         VALUE_PAIR      *vp;
503         unsigned char   *state;
504         time_t          then;
505
506         if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
507             int e_length = inst->chal_len;
508
509             /* Extend expected length if state should have been protected. */
510             if (user_info.card_id & X99_CF_AM)
511                 e_length += 4 + 4 + 16; /* sflags + time + hmac */
512
513             if (vp->length != e_length) {
514                 x99_log(X99_LOG_AUTH,
515                         "auth: bad state for [%s]: length", username);
516                 return RLM_MODULE_INVALID;
517             }
518
519             /* Fast path if we didn't protect the state. */
520             if (!(user_info.card_id & X99_CF_AM))
521                 goto good_state;
522
523             /* Verify the state. */
524             (void) memset(challenge, 0, sizeof(challenge));
525             (void) memcpy(challenge, vp->strvalue, inst->chal_len);
526             (void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
527             (void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
528             if (x99_gen_state(NULL,&state,challenge,sflags,then,hmac_key) != 0){
529                 x99_log(X99_LOG_ERR, "auth: failed to generate state");
530                 return RLM_MODULE_FAIL;
531             }
532             if (memcmp(state, vp->strvalue, vp->length)) {
533                 x99_log(X99_LOG_AUTH,
534                         "auth: bad state for [%s]: hmac", username);
535                 free(state);
536                 return RLM_MODULE_REJECT;
537             }
538             free(state);
539
540             /* State is valid, but check expiry. */
541             then = ntohl(then);
542             if (time(NULL) - then > inst->chal_delay) {
543                 x99_log(X99_LOG_AUTH,
544                         "auth: bad state for [%s]: expired", username);
545                 return RLM_MODULE_REJECT;
546             }
547 good_state:
548             /* State is good! */
549
550         } else {
551             /* This should only happen if the authorize code didn't run. */
552             x99_log(X99_LOG_ERR, "auth: bad state for [%s]: missing "
553                     "(is x99_token listed in radiusd.conf's authorize stanza?)",
554                     username);
555             return RLM_MODULE_FAIL;
556         }
557     } /* if (!fast_sync) */
558
559     /* Get the time of the last authentication. */
560     if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
561         x99_log(X99_LOG_ERR,
562                 "auth: unable to get last auth time for [%s]", username);
563         return RLM_MODULE_FAIL;
564     }
565
566     /* Check failure count. */
567     fc = x99_check_failcount(username, inst);
568     if ((fc == FAIL_ERR) || (fc == FAIL_HARD))
569         return RLM_MODULE_USERLOCK;
570
571     /* Some checks for ewindow2_size logic. */
572     if (fc == FAIL_SOFT) {
573         if (!inst->ewindow2_size)       /* no auto-resync */
574             return RLM_MODULE_USERLOCK;
575
576         if (!pairfind(request->config_items, PW_X99_FAST)) {
577             /*
578              * ewindow2 softfail override requires two consecutive sync
579              * responses.  Fail, and record that this was async.
580              */
581             if (x99_set_last_auth_pos(inst->syncdir, username, 0))
582                 x99_log(X99_LOG_ERR,
583                         "auth: failed to record last auth pos for [%s]",
584                         username);
585             return RLM_MODULE_USERLOCK;
586         }
587
588         /* We're now in "ewindow2 mode" ... subsequent logic must test fc */
589         goto sync_response;
590     }
591
592     /*
593      * Don't bother to check async response if either
594      * - the card doesn't support it, or
595      * - we're doing fast_sync.
596      */
597     if (!(user_info.card_id & X99_CF_AM) ||
598         pairfind(request->config_items, PW_X99_FAST)) {
599         goto sync_response;
600     }
601
602     /* Perform any site-specific transforms of the challenge. */
603     if (x99_challenge_transform(username, challenge) != 0) {
604         x99_log(X99_LOG_ERR,
605                 "auth: challenge transform failed for [%s]", username);
606         return RLM_MODULE_FAIL;
607         /* NB: last_auth, failcount not updated. */
608     }
609
610     /* Calculate and test the async response. */
611     if (x99_response(challenge, e_response, user_info.card_id,
612                      user_info.keyblock) != 0) {
613         x99_log(X99_LOG_ERR,
614                 "auth: unable to calculate async response for [%s], "
615                 "to challenge %s", username, challenge);
616         return RLM_MODULE_FAIL;
617         /* NB: last_auth, failcount not updated. */
618     }
619     DEBUG("rlm_x99_token: auth: [%s], async challenge %s, "
620           "expecting response %s", username, challenge, e_response);
621
622     if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
623         /* Password matches.  Is this allowed? */
624         if (!inst->allow_async) {
625             x99_log(X99_LOG_AUTH,
626                     "auth: bad async for [%s]: disallowed by config", username);
627             rc = RLM_MODULE_REJECT;
628             goto return_pw_valid;
629             /* NB: last_auth, failcount not updated. */
630         }
631
632         /* Make sure this isn't a replay by forcing a delay. */
633         if (time(NULL) - last_auth < inst->chal_delay) {
634             x99_log(X99_LOG_AUTH,
635                     "auth: bad async for [%s]: too soon", username);
636             rc = RLM_MODULE_REJECT;
637             goto return_pw_valid;
638             /* NB: last_auth, failcount not updated. */
639         }
640
641         if (user_info.card_id & X99_CF_SM) {
642             x99_log(X99_LOG_INFO,
643                     "auth: [%s] authenticated in async mode", username);
644         }
645
646         rc = RLM_MODULE_OK;
647         if (ntohl(sflags) & 1) {
648             /*
649              * Resync the card.  The sync data doesn't mean anything for
650              * async-only cards, but we want the side effects of resetting
651              * the failcount and the last auth time.  We "fail-out" if we
652              * can't do this, because if we can't update the last auth time,
653              * we will be open to replay attacks over the lifetime of the
654              * State attribute (inst->chal_delay).
655              */
656             if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
657                                   1, 0, challenge, user_info.keyblock) != 0) {
658                 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
659                         "e:%d t:%d for [%s] (for resync)", 1, 0, username);
660                 rc = RLM_MODULE_FAIL;
661             } else if (x99_set_sync_data(inst->syncdir, username, challenge,
662                                          user_info.keyblock) != 0) {
663                 x99_log(X99_LOG_ERR,
664                         "auth: unable to set sync data for [%s] (for resync)",
665                         username);
666                 rc = RLM_MODULE_FAIL;
667             }
668         } else {
669             /* Just update failcount, last_auth, auth_pos. */
670             if (x99_reset_failcount(inst->syncdir, username) != 0) {
671                 x99_log(X99_LOG_ERR,
672                         "auth: unable to reset failcount for [%s]", username);
673                 rc = RLM_MODULE_FAIL;
674             }
675         }
676         goto return_pw_valid;
677     } /* if (user authenticated async) */
678
679 sync_response:
680     /*
681      * Calculate and test sync responses in the window.
682      * Note that we always accept a sync response, even
683      * if a challenge or resync was explicitly requested.
684      */
685     if ((user_info.card_id & X99_CF_SM) && inst->allow_sync) {
686         int start = 0, end = inst->ewindow_size;
687
688         /*
689          * Tweak start,end for ewindow2_size logic.
690          *
691          * If user is in softfail, and their last response was correct,
692          * start at that response.  We used to start at the NEXT
693          * response (the one that will let them in), but the MS Windows
694          * "incorrect password" dialog is confusing and users end up
695          * reusing the same password twice; this has the effect that
696          * ewindow2 doesn't work at all for them (they enter 1,1,2,2,3,3;
697          * the 1,2 or 2,3 wouldn't work since the repeat would reset the
698          * sequence).
699          *
700          * The response sequence 6,5,6 won't work (but 6,5,6,7 will).
701          * That's OK; we want to optimize for the 6,7 sequence.  The user
702          * can't generate the 6,5 sequence from the token anyway.
703          *
704          * If the user starts at the left edge of the window (0,1,2) they
705          * have to enter three responses.  We don't accept the zeroeth
706          * response as part of the sequence because we can't differentiate
707          * between a correct entry of the zeroeth response (which stores
708          * 0 as the last_auth_pos) and an incorrect entry (which "resets"
709          * the last_auth_pos to 0).
710          */
711         if (fc == FAIL_SOFT) {
712             start = x99_get_last_auth_pos(inst->syncdir, username);
713             end = inst->ewindow2_size;
714         }
715
716         challenge[0] = '\0';    /* initialize for x99_get_sync_data() */
717         for (i = start; i <= end; ++i) {
718             /* Get sync challenge and key. */
719             if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
720                                   i, 0, challenge, user_info.keyblock) != 0) {
721                 x99_log(X99_LOG_ERR,
722                         "auth: unable to get sync data e:%d t:%d for [%s]",
723                         i, 0, username);
724                 rc = RLM_MODULE_FAIL;
725                 goto return_pw_valid;
726                 /* NB: last_auth, failcount not updated. */
727             }
728
729             /* Calculate sync response. */
730             if (x99_response(challenge, e_response, user_info.card_id,
731                              user_info.keyblock) != 0) {
732                 x99_log(X99_LOG_ERR, "auth: unable to calculate sync response "
733                         "e:%d t:%d for [%s], to challenge %s",
734                         i, 0, username, challenge);
735                 rc = RLM_MODULE_FAIL;
736                 goto return_pw_valid;
737                 /* NB: last_auth, failcount not updated. */
738             }
739             DEBUG("rlm_x99_token: auth: [%s], sync challenge %d %s, "
740                   "expecting response %s", username, i, challenge, e_response);
741
742             /* Test user-supplied passcode. */
743             if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
744                 /*
745                  * Yay!  User authenticated via sync mode.  Resync.
746                  */
747                 rc = RLM_MODULE_OK;
748
749                 /*
750                  * ewindow2_size logic
751                  */
752                 if (fc == FAIL_SOFT) {
753                     /* User must authenticate twice in a row, ... */
754                     if (start && (i == start + 1) &&
755                         /* ... within ewindow2_delay seconds. */
756                         (time(NULL) - last_auth < inst->ewindow2_delay)) {
757                         /* This is the 2nd of two consecutive responses. */
758                         x99_log(X99_LOG_AUTH,
759                                 "auth: ewindow2 softfail override for [%s] at "
760                                 "window position %d", username, i);
761                     } else {
762                         /* correct, but not consecutive or not soon enough */
763                         DEBUG("rlm_x99_token: auth: [%s] ewindow2 candidate "
764                               "at position %i", username, i);
765                         auth_pos = i;
766                         rc = RLM_MODULE_REJECT;
767                         break;
768                     }
769                 }
770
771                 /*
772                  * The same failure/replay issue applies here as in the
773                  * identical code block in the async section above, with
774                  * the additional problem that a response can be reused
775                  * indefinitely!  (until the sync data is updated)
776                  */
777                 if (x99_get_sync_data(inst->syncdir,username,user_info.card_id,
778                                       1, 0, challenge,user_info.keyblock) != 0){
779                     x99_log(X99_LOG_ERR, "auth: unable to get sync data "
780                             "e:%d t:%d for [%s] (for resync)", 1, 0, username);
781                     rc = RLM_MODULE_FAIL;
782                 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
783                                              user_info.keyblock) != 0) {
784                     x99_log(X99_LOG_ERR,
785                             "auth: unable to set sync data for [%s] "
786                             "(for resync)", username);
787                     rc = RLM_MODULE_FAIL;
788                 }
789                 goto return_pw_valid;
790
791             } /* if (passcode is valid) */
792         } /* for (each slot in the window) */
793     } /* if (card is in sync mode and sync mode allowed) */
794
795     /* Both async and sync mode failed. */
796     if ((fc != FAIL_SOFT) /* !already incremented by x99_check_failcount() */ &&
797         (x99_incr_failcount(inst->syncdir, username) != 0)) {
798         x99_log(X99_LOG_ERR,
799                 "auth: unable to increment failure count for user [%s]",
800                 username);
801     }
802     if (x99_set_last_auth_pos(inst->syncdir, username, auth_pos)) {
803         x99_log(X99_LOG_ERR,
804                 "auth: unable to set ewindow2 position for user [%s]",
805                 username);
806     }
807     return RLM_MODULE_REJECT;
808
809     /* Must exit here after a successful return from x99_pw_valid(). */
810 return_pw_valid:
811
812     /* Handle any vps returned from x99_pw_valid(). */
813     if (rc == RLM_MODULE_OK) {
814         pairadd(&request->reply->vps, add_vps);
815     } else {
816         pairfree(&add_vps);
817     }
818     return rc;
819 }
820
821
822 /* per-instance destruction */
823 static int
824 x99_token_detach(void *instance)
825 {
826     x99_token_t *inst = (x99_token_t *) instance;
827
828     free(inst->pwdfile);
829     free(inst->syncdir);
830     free(inst->chal_prompt);
831     free(inst->chal_req);
832     free(inst->resync_req);
833     free(instance);
834     return 0;
835 }
836
837
838 /* per-module destruction */
839 static int
840 x99_token_destroy(void)
841 {
842     (void) memset(hmac_key, 0, sizeof(hmac_key));
843     (void) close(rnd_fd);
844     return 0;
845 }
846
847 /*
848  *      If the module needs to temporarily modify it's instantiation
849  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
850  *      The server will then take care of ensuring that the module
851  *      is single-threaded.
852  */
853 module_t rlm_x99_token = {
854         "x99_token",    
855         RLM_TYPE_THREAD_SAFE,           /* type */
856         x99_token_init,                 /* initialization */
857         x99_token_instantiate,          /* instantiation */
858         {
859                 x99_token_authenticate, /* authentication */
860                 x99_token_authorize,    /* authorization */
861                 NULL,                   /* preaccounting */
862                 NULL,                   /* accounting */
863                 NULL,                   /* checksimul */
864                 NULL,                   /* pre-proxy */
865                 NULL,                   /* post-proxy */
866                 NULL                    /* post-auth */
867         },
868         x99_token_detach,               /* detach */
869         x99_token_destroy,              /* destroy */
870 };