a2870efde05c0010054fdf0daed23c6eeda41ecd
[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 soft PIN? ???
36  * TODO: support other than ILP32 (for State)
37  */
38
39
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <time.h>
47 #include <netinet/in.h> /* htonl() */
48
49 #ifdef FREERADIUS
50 #include "radiusd.h"
51 #include "modules.h"
52 #endif
53 #include "x99.h"
54
55 static const char rcsid[] = "$Id$";
56
57 /* Global data */
58 static int rnd_fd;                      /* fd for random device           */
59 static unsigned char hmac_key[16];      /* to protect State attribute     */
60
61 /* A mapping of configuration file names to internal variables. */
62 static CONF_PARSER module_config[] = {
63     { "pwdfile", PW_TYPE_STRING_PTR, offsetof(x99_token_t, pwdfile),
64       NULL, PWDFILE },
65     { "syncdir", PW_TYPE_STRING_PTR, offsetof(x99_token_t, syncdir),
66       NULL, SYNCDIR },
67     { "challenge_prompt", PW_TYPE_STRING_PTR, offsetof(x99_token_t,chal_prompt),
68       NULL, CHALLENGE_PROMPT },
69     { "challenge_length", PW_TYPE_INTEGER, offsetof(x99_token_t, chal_len),
70       NULL, "6" },
71     { "softfail", PW_TYPE_INTEGER, offsetof(x99_token_t, softfail),
72       NULL, "5" },
73     { "hardfail", PW_TYPE_INTEGER, offsetof(x99_token_t, hardfail),
74       NULL, "0" },
75     { "allow_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_sync),
76       NULL, "yes" },
77     { "fast_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, fast_sync),
78       NULL, "yes" },
79     { "allow_async", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_async),
80       NULL, "no" },
81     { "challenge_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, chal_req),
82       NULL, CHALLENGE_REQ },
83     { "resync_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, resync_req),
84       NULL, RESYNC_REQ },
85     { "ewindow_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow_size),
86       NULL, "0" },
87     { "maxdelay", PW_TYPE_INTEGER, offsetof(x99_token_t, maxdelay),
88       NULL, "30" },
89     { "mschapv2_mppe", PW_TYPE_INTEGER,
90       offsetof(x99_token_t, mschapv2_mppe_policy), NULL, "2" },
91     { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
92       offsetof(x99_token_t, mschapv2_mppe_types), NULL, "2" },
93     { "mschap_mppe", PW_TYPE_INTEGER,
94       offsetof(x99_token_t, mschap_mppe_policy), NULL, "2" },
95     { "mschap_mppe_bits", PW_TYPE_INTEGER,
96       offsetof(x99_token_t, mschap_mppe_types), NULL, "2" },
97 #if 0
98     { "twindow_min", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_min),
99       NULL, "0" },
100     { "twindow_max", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_max),
101       NULL, "0" },
102 #endif
103
104     { NULL, -1, 0, NULL, NULL }         /* end the list */
105 };
106
107
108 /* per-module initialization */
109 static int
110 x99_token_init(void)
111 {
112     if ((rnd_fd = open(DEVURANDOM, O_RDONLY)) == -1) {
113         x99_log(X99_LOG_ERR, "init: error opening %s: %s", DEVURANDOM,
114                 strerror(errno));
115         return -1;
116     }
117
118     /* Generate a random key, used to protect the State attribute. */
119     if (x99_get_random(rnd_fd, hmac_key, sizeof(hmac_key)) == -1) {
120         x99_log(X99_LOG_ERR, "init: failed to obtain random data for hmac_key");
121         return -1;
122     }
123
124     /* Initialize the password encoding/checking functions. */
125     x99_pwe_init();
126
127     return 0;
128 }
129
130
131 /* per-instance initialization */
132 static int
133 x99_token_instantiate(CONF_SECTION *conf, void **instance)
134 {
135     x99_token_t *data;
136     char *p;
137     struct stat st;
138
139     /* Set up a storage area for instance data. */
140     data = rad_malloc(sizeof(*data));
141
142     /* If the configuration parameters can't be parsed, then fail. */
143     if (cf_section_parse(conf, data, module_config) < 0) {
144         free(data);
145         return -1;
146     }
147
148     /* Verify ranges for those vars that are limited. */
149     if (data->chal_len < 5 || data->chal_len > MAX_CHALLENGE_LEN) {
150         data->chal_len = 6;
151         x99_log(X99_LOG_ERR,
152                 "invalid challenge length, range 5-%d, using default of 6",
153                 MAX_CHALLENGE_LEN);
154
155     }
156
157     /* Enforce a single "%" sequence, which must be "%s" */
158     p = strchr(data->chal_prompt, '%');
159     if (p == NULL || p != strrchr(data->chal_prompt, '%') || strncmp(p,"%s",2)){
160         free(data->chal_prompt);
161         data->chal_prompt = strdup(CHALLENGE_PROMPT);
162         x99_log(X99_LOG_ERR,
163                 "invalid challenge prompt, using default of \"%s\"",
164                 CHALLENGE_PROMPT);
165     }
166
167     if (data->softfail < 0 ) {
168         data->softfail = 5;
169         x99_log(X99_LOG_ERR, "softfail must be at least 1 "
170                 "(or 0 == infinite), using default of 5");
171     }
172
173     if (data->hardfail < 0 ) {
174         data->hardfail = 0;
175         x99_log(X99_LOG_ERR, "hardfail must be at least 1 "
176                 "(or 0 == infinite), using default of 0");
177     }
178
179     if (data->fast_sync && !data->allow_sync) {
180         data->fast_sync = 0;
181         x99_log(X99_LOG_INFO,
182                 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
183     }
184
185     if (!data->allow_sync && !data->allow_async) {
186         x99_log(X99_LOG_ERR,
187                 "at least one of {allow_async, allow_sync} must be set");
188         free(data);
189         return -1;
190     }
191
192     if (data->ewindow_size > MAX_EWINDOW_SIZE || data->ewindow_size < 0) {
193         data->ewindow_size = 0;
194         x99_log(X99_LOG_ERR, "max event window size is %d, using default of 0",
195                 MAX_EWINDOW_SIZE);
196     }
197
198     if (data->mschapv2_mppe_policy > 2 || data->mschapv2_mppe_policy < 0) {
199         data->mschapv2_mppe_policy = 2;
200         x99_log(X99_LOG_ERR,
201                 "invalid value for mschapv2_mppe, using default of 2");
202     }
203
204     if (data->mschapv2_mppe_types > 2 || data->mschapv2_mppe_types < 0) {
205         data->mschapv2_mppe_types = 2;
206         x99_log(X99_LOG_ERR,
207                 "invalid value for mschapv2_mppe_bits, using default of 2");
208     }
209
210     if (data->mschap_mppe_policy > 2 || data->mschap_mppe_policy < 0) {
211         data->mschap_mppe_policy = 2;
212         x99_log(X99_LOG_ERR,
213                 "invalid value for mschap_mppe, using default of 2");
214     }
215
216     if (data->mschap_mppe_types != 2) {
217         data->mschap_mppe_types = 2;
218         x99_log(X99_LOG_ERR,
219                 "invalid value for mschap_mppe_bits, using default of 2");
220     }
221
222 #if 0
223     if (data->twindow_max - data->twindow_min > MAX_TWINDOW_SIZE) {
224         data->twindow_min = data->twindow_max = 0;
225         x99_log(X99_LOG_ERR, "max time window size is %d, using default of 0",
226                 MAX_TWINDOW_SIZE);
227     }
228     if (data->twindow_min > 0 || data->twindow_max < 0 ||
229         data->twindow_max < data->twindow_min) {
230         data->twindow_min = data->twindow_max = 0;
231         x99_log(X99_LOG_ERR,
232                 "invalid values for time window, using default of 0");
233     }
234 #endif
235
236     if (stat(data->syncdir, &st) != 0) {
237         x99_log(X99_LOG_ERR, "syncdir %s error: %s",
238                 data->syncdir, strerror(errno));
239         free(data);
240         return -1;
241     }
242     if (st.st_mode != (S_IFDIR|S_IRWXU)) {
243         x99_log(X99_LOG_ERR, "syncdir %s has loose permissions", data->syncdir);
244         free(data);
245         return -1;
246     }
247
248     *instance = data;
249     return 0;
250 }
251
252
253 /* Generate a challenge to be presented to the user. */
254 static int
255 x99_token_authorize(void *instance, REQUEST *request)
256 {
257     x99_token_t *inst = (x99_token_t *) instance;
258
259     char challenge[MAX_CHALLENGE_LEN + 1];      /* +1 for '\0' terminator */
260     char *state;
261     int rc;
262
263     x99_user_info_t user_info;
264     int user_found, auth_type_found;
265     int pwattr;
266     int32_t sflags = 0; /* flags for state */
267     VALUE_PAIR *vp;
268
269     /* Early exit if Auth-Type != x99_token */
270     auth_type_found = 0;
271     if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
272         auth_type_found = 1;
273         if (strcmp(vp->strvalue, "x99_token")) {
274             return RLM_MODULE_NOOP;
275         }
276     }
277
278     /* The State attribute will be present if this is a response. */
279     if (pairfind(request->packet->vps, PW_STATE) != NULL) {
280         DEBUG("rlm_x99_token: autz: Found response to access challenge");
281         return RLM_MODULE_OK;
282     }
283
284     /* User-Name attribute required. */
285     if (!request->username) {
286         x99_log(X99_LOG_AUTH,
287                 "autz: Attribute \"User-Name\" required for authentication.");
288         return RLM_MODULE_INVALID;
289     }
290
291     if ((pwattr = x99_pw_present(request)) == 0) {
292         x99_log(X99_LOG_AUTH, "autz: Attribute \"User-Password\" "
293                 "or equivalent required for authentication.");
294         return RLM_MODULE_INVALID;
295     }
296
297     /* Look up the user's info. */
298     user_found = 1;
299     if ((rc = x99_get_user_info(inst->pwdfile, request->username->strvalue,
300                                 &user_info)) == -2) {
301         x99_log(X99_LOG_ERR, "autz: error reading user [%s] info",
302                 request->username->strvalue);
303         return RLM_MODULE_FAIL;
304     }
305     if (rc == -1) {
306         /* x99_get_user_info() also logs, but we want to record the autz bit */
307         x99_log(X99_LOG_AUTH, "autz: user [%s] not found",
308                 request->username->strvalue);
309         memset(&user_info, 0, sizeof(user_info)); /* X99_CF_NONE */
310         user_found = 0;
311     }
312
313     /* fast_sync mode (challenge only if requested) */
314     if (inst->fast_sync &&
315         ((user_info.card_id & X99_CF_SM) || !user_found)) {
316
317         if ((x99_pw_valid(request, inst, pwattr, inst->resync_req, NULL) &&
318                 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
319             x99_pw_valid(request, inst, pwattr, inst->chal_req, NULL)) {
320             /*
321              * Generate a challenge if requested.  We don't test for card
322              * support [for async] because it's tricky for unknown users.
323              * Some configurations would have a problem where known users
324              * cannot request a challenge, but unknown users can.  This
325              * reveals information.  The easiest fix seems to be to always
326              * hand out a challenge on request.
327              * We also don't test if the server allows async mode, this
328              * would also reveal information.
329              */
330             DEBUG("rlm_x99_token: autz: fast_sync challenge requested");
331             goto gen_challenge;
332
333         } else {
334             /*
335              * Otherwise, this is the token sync response.  Signal
336              * the authenticate code to ignore State.  We don't need
337              * to set a value, /existence/ of the vp is the signal.
338              */
339             if ((vp = paircreate(PW_X99_FAST, PW_TYPE_INTEGER)) == NULL) {
340                 x99_log(X99_LOG_CRIT, "autz: no memory");
341                 return RLM_MODULE_FAIL;
342             }
343             pairadd(&request->config_items, vp);
344             DEBUG("rlm_x99_token: autz: using fast_sync");
345
346             if (!auth_type_found)
347                 pairadd(&request->config_items,
348                         pairmake("Auth-Type", "x99_token", T_OP_EQ));
349             return RLM_MODULE_OK;
350
351         }
352     } /* if (fast_sync && card supports sync mode) */
353
354 gen_challenge:
355     /* Set the resync bit by default if the user can't request it. */
356     if (!inst->fast_sync)
357         sflags |= htonl(1);
358
359     /* Generate a random challenge. */
360     if (x99_get_challenge(rnd_fd, challenge, inst->chal_len) == -1) {
361         x99_log(X99_LOG_ERR, "autz: failed to obtain random challenge");
362         return RLM_MODULE_FAIL;
363     }
364
365     /*
366      * Create the State attribute, which will be returned to us along with
367      * the response.  We will need this to verify the response.  Create
368      * a strong state if the user will be able use this with their token.
369      * Otherwise, we discard it anyway, so don't "waste" time with hmac.
370      * We also don't do the hmac if the user wasn't found (mask won't match).
371      * We always create at least a trivial state, so x99_token_authorize()
372      * can easily pass on to x99_token_authenticate().
373      */
374     if (user_info.card_id & X99_CF_AM) {
375         time_t now = time(NULL);
376
377         if (sizeof(now) != 4 || sizeof(long) != 4) {
378             x99_log(X99_LOG_ERR, "autz: only ILP32 arch is supported");
379             return RLM_MODULE_FAIL;
380         }
381         now = htonl(now);
382
383         if (x99_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
384             x99_log(X99_LOG_ERR, "autz: failed to generate state");
385             return RLM_MODULE_FAIL;
386         }
387     } else {
388         /* x2 b/c pairmake() string->octet needs even num of digits */
389         state = rad_malloc(3 + inst->chal_len * 2);
390         (void) sprintf(state, "0x%s%s", challenge, challenge);
391     }
392     pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
393     free(state);
394
395     /* Add the challenge to the reply. */
396     {
397         char *u_challenge;      /* challenge with addt'l presentation text */
398
399         u_challenge = rad_malloc(strlen(inst->chal_prompt)+MAX_CHALLENGE_LEN+1);
400         (void) sprintf(u_challenge, inst->chal_prompt, challenge);
401         pairadd(&request->reply->vps,
402                 pairmake("Reply-Message", u_challenge, T_OP_EQ));
403         free(u_challenge);
404     }
405
406     /*
407      * Mark the packet as an Access-Challenge packet.
408      * The server will take care of sending it to the user.
409      */
410     request->reply->code = PW_ACCESS_CHALLENGE;
411     DEBUG("rlm_x99_token: Sending Access-Challenge.");
412
413     if (!auth_type_found)
414         pairadd(&request->config_items,
415                 pairmake("Auth-Type", "x99_token", T_OP_EQ));
416     return RLM_MODULE_HANDLED;
417 }
418
419
420 /* Verify the response entered by the user. */
421 static int
422 x99_token_authenticate(void *instance, REQUEST *request)
423 {
424     x99_token_t *inst = (x99_token_t *) instance;
425
426     x99_user_info_t user_info;
427     char *username;
428     int i, pwattr, rc;
429     int32_t sflags = 0; /* flags from state */
430     time_t last_auth;
431
432     char challenge[MAX_CHALLENGE_LEN + 1];
433     char e_response[9];         /* expected response */
434     VALUE_PAIR *add_vps = NULL;
435
436     /* User-Name attribute required. */
437     if (!request->username) {
438         x99_log(X99_LOG_AUTH,
439                 "auth: Attribute \"User-Name\" required for authentication.");
440         return RLM_MODULE_INVALID;
441     }
442     username = request->username->strvalue;
443
444     if ((pwattr = x99_pw_present(request)) == 0) {
445         x99_log(X99_LOG_AUTH, "auth: Attribute \"User-Password\" "
446                 "or equivalent required for authentication.");
447         return RLM_MODULE_INVALID;
448     }
449
450     /* Add a message to the auth log. */
451     pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
452                                             X99_MODULE_NAME, T_OP_EQ));
453     pairadd(&request->packet->vps, pairmake("Module-Success-Message",
454                                             X99_MODULE_NAME, T_OP_EQ));
455
456     /* Look up the user's info. */
457     if (x99_get_user_info(inst->pwdfile, username, &user_info) != 0) {
458         x99_log(X99_LOG_AUTH, "auth: error reading user [%s] info", username);
459         return RLM_MODULE_REJECT;
460     }
461
462     /* Retrieve the challenge (from State attribute), unless (fast_sync). */
463     if (pairfind(request->config_items, PW_X99_FAST) == NULL) {
464         VALUE_PAIR      *vp;
465         unsigned char   *state;
466         time_t          then;
467
468         if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
469             int e_length = inst->chal_len;
470
471             /* Extend expected length if state should have been protected. */
472             if (user_info.card_id & X99_CF_AM)
473                 e_length += 4 + 4 + 16; /* sflags + time + hmac */
474
475             if (vp->length != e_length) {
476                 x99_log(X99_LOG_AUTH,
477                         "auth: bad state for [%s]: length", username);
478                 return RLM_MODULE_INVALID;
479             }
480
481             /* Fast path if we didn't protect the state. */
482             if (!(user_info.card_id & X99_CF_AM))
483                 goto good_state;
484
485             /* Verify the state. */
486             (void) memset(challenge, 0, sizeof(challenge));
487             (void) memcpy(challenge, vp->strvalue, inst->chal_len);
488             (void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
489             (void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
490             if (x99_gen_state(NULL,&state,challenge,sflags,then,hmac_key) != 0){
491                 x99_log(X99_LOG_ERR, "auth: failed to generate state");
492                 return RLM_MODULE_FAIL;
493             }
494             if (memcmp(state, vp->strvalue, vp->length)) {
495                 x99_log(X99_LOG_AUTH,
496                         "auth: bad state for [%s]: hmac", username);
497                 free(state);
498                 return RLM_MODULE_REJECT;
499             }
500             free(state);
501
502             /* State is valid, but check expiry. */
503             then = ntohl(then);
504             if (then + inst->maxdelay < time(NULL)) {
505                 x99_log(X99_LOG_AUTH,
506                         "auth: bad state for [%s]: expired", username);
507                 return RLM_MODULE_REJECT;
508             }
509 good_state:
510             /* State is good! */
511
512         } else {
513             /* This should only happen if the authorize code didn't run. */
514             x99_log(X99_LOG_ERR, "auth: bad state for [%s]: missing "
515                     "(is x99_token listed in radiusd.conf's authorize stanza?)",
516                     username);
517             return RLM_MODULE_FAIL;
518         }
519     } /* if (!fast_sync) */
520
521     /* Check failure count. */
522     if (x99_check_failcount(username, inst))
523         return RLM_MODULE_USERLOCK;
524
525     /*
526      * Don't bother to check async response if either
527      * - the card doesn't support it, or
528      * - we're doing fast_sync.
529      */
530     if (!(user_info.card_id & X99_CF_AM) ||
531         pairfind(request->config_items, PW_X99_FAST)) {
532         goto sync_response;
533     }
534
535     /* Perform any site-specific transforms of the challenge. */
536     if (x99_challenge_transform(username, challenge) != 0) {
537         x99_log(X99_LOG_ERR,
538                 "auth: challenge transform failed for [%s]", username);
539         return RLM_MODULE_FAIL;
540         /* NB: last_auth, failcount not updated. */
541     }
542
543     /* Calculate and test the async response. */
544     if (x99_response(challenge, e_response, user_info.card_id,
545                      user_info.keyblock) != 0) {
546         x99_log(X99_LOG_ERR,
547                 "auth: unable to calculate async response for [%s], "
548                 "to challenge %s", username, challenge);
549         return RLM_MODULE_FAIL;
550         /* NB: last_auth, failcount not updated. */
551     }
552     DEBUG("rlm_x99_token: auth: [%s], async challenge %s, "
553           "expecting response %s", username, challenge, e_response);
554
555     if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
556         /* Password matches.  Is this allowed? */
557         if (!inst->allow_async) {
558             x99_log(X99_LOG_AUTH,
559                     "auth: bad async for [%s]: disallowed by config", username);
560             rc = RLM_MODULE_REJECT;
561             goto return_pw_valid;
562             /* NB: last_auth, failcount not updated. */
563         }
564
565         /* Make sure this isn't a replay by forcing a delay. */
566         if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
567             x99_log(X99_LOG_ERR,
568                     "auth: unable to get last auth time for [%s]", username);
569             return RLM_MODULE_FAIL;
570         }
571         if (last_auth + inst->maxdelay > time(NULL)) {
572             x99_log(X99_LOG_AUTH,
573                     "auth: bad async for [%s]: too soon", username);
574             rc = RLM_MODULE_REJECT;
575             goto return_pw_valid;
576             /* NB: last_auth, failcount not updated. */
577         }
578
579         if (user_info.card_id & X99_CF_SM) {
580             x99_log(X99_LOG_INFO,
581                     "auth: [%s] authenticated in async mode", username);
582         }
583
584         rc = RLM_MODULE_OK;
585         if (ntohl(sflags) & 1) {
586             /*
587              * Resync the card.  The sync data doesn't mean anything for
588              * async-only cards, but we want the side effects of resetting
589              * the failcount and the last auth time.  We "fail-out" if we
590              * can't do this, because if we can't update the last auth time,
591              * we will be open to replay attacks over the lifetime of the
592              * State attribute (inst->maxdelay).
593              */
594             if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
595                                   1, 0, challenge, user_info.keyblock) != 0) {
596                 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
597                         "e:%d t:%d for [%s] (for resync)", 1, 0, username);
598                 rc = RLM_MODULE_FAIL;
599             } else if (x99_set_sync_data(inst->syncdir, username, challenge,
600                                          user_info.keyblock) != 0) {
601                 x99_log(X99_LOG_ERR,
602                         "auth: unable to set sync data for [%s] (for resync)",
603                         username);
604                 rc = RLM_MODULE_FAIL;
605             }
606         } else {
607             /* Just update last_auth, failcount. */
608             if (x99_reset_failcount(inst->syncdir, username) != 0) {
609                 x99_log(X99_LOG_ERR,
610                         "auth: unable to reset failcount for [%s]", username);
611                 rc = RLM_MODULE_FAIL;
612             }
613         }
614         goto return_pw_valid;
615     } /* if (user authenticated async) */
616
617 sync_response:
618     /*
619      * Calculate and test sync responses in the window.
620      * Note that we always accept a sync response, even
621      * if a challenge or resync was explicitly requested.
622      */
623     if ((user_info.card_id & X99_CF_SM) && inst->allow_sync) {
624         for (i = 0; i <= inst->ewindow_size; ++i) {
625             /* Get sync challenge and key. */
626             if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
627                                   i, 0, challenge, user_info.keyblock) != 0) {
628                 x99_log(X99_LOG_ERR,
629                         "auth: unable to get sync data e:%d t:%d for [%s]",
630                         i, 0, username);
631                 rc = RLM_MODULE_FAIL;
632                 goto return_pw_valid;
633                 /* NB: last_auth, failcount not updated. */
634             }
635
636             /* Calculate sync response. */
637             if (x99_response(challenge, e_response, user_info.card_id,
638                              user_info.keyblock) != 0) {
639                 x99_log(X99_LOG_ERR, "auth: unable to calculate sync response "
640                         "e:%d t:%d for [%s], to challenge %s",
641                         i, 0, username, challenge);
642                 rc = RLM_MODULE_FAIL;
643                 goto return_pw_valid;
644                 /* NB: last_auth, failcount not updated. */
645             }
646             DEBUG("rlm_x99_token: auth: [%s], sync challenge %d %s, "
647                   "expecting response %s", username, i, challenge, e_response);
648
649             /* Test user-supplied password. */
650             if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
651                 /*
652                  * Yay!  User authenticated via sync mode.  Resync.
653                  *
654                  * The same failure/replay issue applies here as in the
655                  * identical code block in the async section above, with
656                  * the additional problem that a response can be reused
657                  * indefinitely!  (until the sync data is updated)
658                  */
659                 rc = RLM_MODULE_OK;
660                 if (x99_get_sync_data(inst->syncdir,username,user_info.card_id,
661                                       1, 0, challenge,user_info.keyblock) != 0){
662                     x99_log(X99_LOG_ERR, "auth: unable to get sync data "
663                             "e:%d t:%d for [%s] (for resync)", 1, 0, username);
664                     rc = RLM_MODULE_FAIL;
665                 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
666                                              user_info.keyblock) != 0) {
667                     x99_log(X99_LOG_ERR,
668                             "auth: unable to set sync data for [%s] "
669                             "(for resync)", username);
670                     rc = RLM_MODULE_FAIL;
671                 }
672                 goto return_pw_valid;
673             }
674
675         } /* for (each slot in the window) */
676     } /* if (card is in sync mode and sync mode allowed) */
677
678     /* Both async and sync mode failed. */
679     if (x99_incr_failcount(inst->syncdir, username) != 0) {
680         x99_log(X99_LOG_ERR,
681                 "auth: unable to increment failure count for user [%s]",
682                 username);
683     }
684     return RLM_MODULE_REJECT;
685
686     /* Must exit here after a successful return from x99_pw_valid(). */
687 return_pw_valid:
688
689     /* Handle any vps returned from x99_pw_valid(). */
690     if (rc == RLM_MODULE_OK) {
691         pairadd(&request->reply->vps, add_vps);
692     } else {
693         pairfree(&add_vps);
694     }
695     return rc;
696 }
697
698
699 /* per-instance destruction */
700 static int
701 x99_token_detach(void *instance)
702 {
703     x99_token_t *inst = (x99_token_t *) instance;
704
705     free(inst->pwdfile);
706     free(inst->syncdir);
707     free(inst->chal_prompt);
708     free(inst->chal_req);
709     free(inst->resync_req);
710     free(instance);
711     return 0;
712 }
713
714
715 /* per-module destruction */
716 static int
717 x99_token_destroy(void)
718 {
719     (void) memset(hmac_key, 0, sizeof(hmac_key));
720     (void) close(rnd_fd);
721     return 0;
722 }
723
724 /*
725  *      If the module needs to temporarily modify it's instantiation
726  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
727  *      The server will then take care of ensuring that the module
728  *      is single-threaded.
729  */
730 module_t rlm_x99_token = {
731         "x99_token",    
732         RLM_TYPE_THREAD_SAFE,           /* type */
733         x99_token_init,                 /* initialization */
734         x99_token_instantiate,          /* instantiation */
735         {
736                 x99_token_authenticate, /* authentication */
737                 x99_token_authorize,    /* authorization */
738                 NULL,                   /* preaccounting */
739                 NULL,                   /* accounting */
740                 NULL                    /* checksimul */
741         },
742         x99_token_detach,               /* detach */
743         x99_token_destroy,              /* destroy */
744 };