strvalue -> vp_strvalue (wiped out change in previous round of commits)
[freeradius.git] / src / modules / rlm_otp / otp_rlm.c
1 /*
2  * otp_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  * Copyright 2005 Frank Cusack
22  */
23
24 /*
25  * STRONG WARNING SECTION:
26  *
27  * ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
28  * An attacker can learn the token's secret by observing two
29  * challenge/response pairs.  See ANSI document X9 TG-24-1999
30  * <URL:http://www.x9.org/docs/TG24_1999.pdf>.
31  *
32  * Please read the accompanying docs.
33  */
34
35 /*
36  * TODO: support setting multiple auth-types in authorize()
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(), ntohl() */
49
50 #include "otp.h"
51 #ifdef FREERADIUS
52 #include "modules.h"
53 #endif
54
55 static const char rcsid[] = "$Id$";
56
57 /* Global data */
58 static unsigned char hmac_key[16];      /* to protect State attribute     */
59 static int ninstance = 0;               /* #instances, for global init    */
60
61 /* A mapping of configuration file names to internal variables. */
62 static const CONF_PARSER module_config[] = {
63     { "pwdfile", PW_TYPE_STRING_PTR, offsetof(otp_option_t, pwdfile),
64       NULL, OTP_PWDFILE },
65     { "lsmd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, lsmd_rp),
66       NULL, OTP_LSMD_RP },
67     { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t,chal_prompt),
68       NULL, OTP_CHALLENGE_PROMPT },
69     { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_len),
70       NULL, "6" },
71     { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_delay),
72       NULL, "30" },
73     { "softfail", PW_TYPE_INTEGER, offsetof(otp_option_t, softfail),
74       NULL, "5" },
75     { "hardfail", PW_TYPE_INTEGER, offsetof(otp_option_t, hardfail),
76       NULL, "0" },
77     { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
78       NULL, "yes" },
79     { "fast_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, fast_sync),
80       NULL, "yes" },
81     { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
82       NULL, "no" },
83     { "challenge_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, chal_req),
84       NULL, OTP_CHALLENGE_REQ },
85     { "resync_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, resync_req),
86       NULL, OTP_RESYNC_REQ },
87     { "prepend_pin", PW_TYPE_BOOLEAN, offsetof(otp_option_t, prepend_pin),
88       NULL, "yes" },
89     { "ewindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow_size),
90       NULL, "0" },
91     { "ewindow2_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow2_size),
92       NULL, "0" },
93     { "ewindow2_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow2_delay),
94       NULL, "60" },
95     { "mschapv2_mppe", PW_TYPE_INTEGER,
96       offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
97     { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
98       offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
99     { "mschap_mppe", PW_TYPE_INTEGER,
100       offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
101     { "mschap_mppe_bits", PW_TYPE_INTEGER,
102       offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
103 #if 0
104     { "twindow_min", PW_TYPE_INTEGER, offsetof(otp_option_t, twindow_min),
105       NULL, "0" },
106     { "twindow_max", PW_TYPE_INTEGER, offsetof(otp_option_t, twindow_max),
107       NULL, "0" },
108 #endif
109
110     { NULL, -1, 0, NULL, NULL }         /* end the list */
111 };
112
113
114 /* transform otp_pw_valid() return code into an rlm return code */
115 static int
116 otprc2rlmrc(int rc)
117 {
118     switch (rc) {
119     case OTP_RC_OK:                     return RLM_MODULE_OK;
120     case OTP_RC_USER_UNKNOWN:           return RLM_MODULE_REJECT;
121     case OTP_RC_AUTHINFO_UNAVAIL:       return RLM_MODULE_REJECT;
122     case OTP_RC_AUTH_ERR:               return RLM_MODULE_REJECT;
123     case OTP_RC_MAXTRIES:               return RLM_MODULE_USERLOCK;
124     case OTP_RC_SERVICE_ERR:            return RLM_MODULE_FAIL;
125     default:                            return RLM_MODULE_FAIL;
126     }
127 }
128
129
130 /* per-instance initialization */
131 static int
132 otp_instantiate(CONF_SECTION *conf, void **instance)
133 {
134     otp_option_t *opt;
135     char *p;
136
137     /* Set up a storage area for instance data. */
138     opt = rad_malloc(sizeof(*opt));
139     (void) memset(opt, 0, sizeof(*opt));
140
141     /* If the configuration parameters can't be parsed, then fail. */
142     if (cf_section_parse(conf, opt, module_config) < 0) {
143         free(opt);
144         return -1;
145     }
146
147     /* Onetime initialization. */
148     if (!ninstance) {
149         /* Generate a random key, used to protect the State attribute. */
150         if (otp_get_random(-1, hmac_key, sizeof(hmac_key)) == -1) {
151             otp_log(OTP_LOG_ERR, "failed to obtain random data for hmac_key");
152             free(opt);
153             return -1;
154         }
155
156         /* Initialize the passcode encoding/checking functions. */
157         otp_pwe_init();
158
159         /*
160          * Don't do this again.
161          * Only the main thread instantiates and detaches instances,
162          * so this does not need mutex protection.
163          */
164         ninstance++;
165     }
166
167     /* Verify ranges for those vars that are limited. */
168     if ((opt->chal_len < 5) || (opt->chal_len > OTP_MAX_CHALLENGE_LEN)) {
169         opt->chal_len = 6;
170         otp_log(OTP_LOG_ERR,
171                 "invalid challenge_length, range 5-%d, using default of 6",
172                 OTP_MAX_CHALLENGE_LEN);
173
174     }
175
176     /* Enforce a single "%" sequence, which must be "%s" */
177     p = strchr(opt->chal_prompt, '%');
178     if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
179         strncmp(p,"%s",2)){
180         free(opt->chal_prompt);
181         opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
182         otp_log(OTP_LOG_ERR,
183                 "invalid challenge_prompt, using default of \"%s\"",
184                 OTP_CHALLENGE_PROMPT);
185     }
186
187     if (opt->softfail < 0) {
188         opt->softfail = 5;
189         otp_log(OTP_LOG_ERR, "softfail must be at least 1 "
190                 "(or 0 == infinite), using default of 5");
191     }
192
193     if (opt->hardfail < 0) {
194         opt->hardfail = 0;
195         otp_log(OTP_LOG_ERR, "hardfail must be at least 1 "
196                 "(or 0 == infinite), using default of 0");
197     }
198
199     if (opt->fast_sync && !opt->allow_sync) {
200         opt->fast_sync = 0;
201         otp_log(OTP_LOG_INFO,
202                 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
203     }
204
205     if (!opt->allow_sync && !opt->allow_async) {
206         otp_log(OTP_LOG_ERR,
207                 "at least one of {allow_async, allow_sync} must be set");
208         free(opt);
209         return -1;
210     }
211
212     if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
213         (opt->ewindow_size < 0)) {
214         opt->ewindow_size = 0;
215         otp_log(OTP_LOG_ERR, "max ewindow_size is %d, using default of 0",
216                 OTP_MAX_EWINDOW_SIZE);
217     }
218
219     if (opt->ewindow2_size && (opt->ewindow2_size < opt->ewindow_size)) {
220         opt->ewindow2_size = 0;
221         otp_log(OTP_LOG_ERR, "ewindow2_size must be at least as large as "
222                              "ewindow_size, using default of 0");
223     }
224
225     if (opt->ewindow2_size && !opt->ewindow2_delay) {
226         opt->ewindow2_size = 0;
227         otp_log(OTP_LOG_ERR, "ewindow2_size is non-zero, "
228                              "but ewindow2_delay is zero; disabling ewindow2");
229     }
230
231     if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
232         opt->mschapv2_mppe_policy = 2;
233         otp_log(OTP_LOG_ERR,
234                 "invalid value for mschapv2_mppe, using default of 2");
235     }
236
237     if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
238         opt->mschapv2_mppe_types = 2;
239         otp_log(OTP_LOG_ERR,
240                 "invalid value for mschapv2_mppe_bits, using default of 2");
241     }
242
243     if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
244         opt->mschap_mppe_policy = 2;
245         otp_log(OTP_LOG_ERR,
246                 "invalid value for mschap_mppe, using default of 2");
247     }
248
249     if (opt->mschap_mppe_types != 2) {
250         opt->mschap_mppe_types = 2;
251         otp_log(OTP_LOG_ERR,
252                 "invalid value for mschap_mppe_bits, using default of 2");
253     }
254
255 #if 0
256     if (opt->twindow_max - opt->twindow_min > OTP_MAX_TWINDOW_SIZE) {
257         opt->twindow_min = opt->twindow_max = 0;
258         otp_log(OTP_LOG_ERR, "max time window size is %d, using default of 0",
259                 OTP_MAX_TWINDOW_SIZE);
260     }
261     if ((opt->twindow_min > 0) || (opt->twindow_max < 0) ||
262         (opt->twindow_max < opt->twindow_min)) {
263         opt->twindow_min = opt->twindow_max = 0;
264         otp_log(OTP_LOG_ERR,
265                 "invalid values for time window, using default of 0");
266     }
267 #endif
268
269     /* Set the instance name (for use with authorize()) */
270     opt->name = cf_section_name2(conf);
271     if (!opt->name)
272         opt->name = cf_section_name1(conf);
273     if (!opt->name) {
274         otp_log(OTP_LOG_CRIT, "no instance name (this can't happen)");
275         free(opt);
276         return -1;
277     }
278
279     *instance = opt;
280     return 0;
281 }
282
283
284 /* Generate a challenge to be presented to the user. */
285 static int
286 otp_authorize(void *instance, REQUEST *request)
287 {
288     otp_option_t *inst = (otp_option_t *) instance;
289
290     char challenge[OTP_MAX_CHALLENGE_LEN + 1];  /* +1 for '\0' terminator */
291     char *state;
292     int auth_type_found;
293     int32_t sflags = 0; /* flags for state */
294
295     struct otp_pwe_cmp_t data = {
296         .request = request,
297         .inst = inst,
298         .returned_vps = NULL
299     };
300
301     /* Early exit if Auth-Type != inst->name */
302     {
303         VALUE_PAIR *vp;
304
305         auth_type_found = 0;
306         if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
307             auth_type_found = 1;
308             if (strcmp(vp->vp_strvalue, inst->name)) {
309                 return RLM_MODULE_NOOP;
310             }
311         }
312     }
313
314     /* The State attribute will be present if this is a response. */
315     if (pairfind(request->packet->vps, PW_STATE) != NULL) {
316         DEBUG("rlm_otp: autz: Found response to Access-Challenge");
317         return RLM_MODULE_OK;
318     }
319
320     /* User-Name attribute required. */
321     if (!request->username) {
322         otp_log(OTP_LOG_AUTH,
323                 "autz: Attribute \"User-Name\" required for authentication.");
324         return RLM_MODULE_INVALID;
325     }
326
327     if ((data.pwattr = otp_pwe_present(request)) == 0) {
328         otp_log(OTP_LOG_AUTH, "autz: Attribute \"User-Password\" "
329                 "or equivalent required for authentication.");
330         return RLM_MODULE_INVALID;
331     }
332
333     /* fast_sync mode (challenge only if requested) */
334     if (inst->fast_sync) {
335         if ((!otp_pwe_cmp(&data, inst->resync_req) &&
336                 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
337             !otp_pwe_cmp(&data, inst->chal_req)) {
338             /*
339              * Generate a challenge if requested.  Note that we do this
340              * even if configuration doesn't allow async mode.
341              */
342             DEBUG("rlm_otp: autz: fast_sync challenge requested");
343             goto gen_challenge;
344
345         } else {
346             /* Otherwise, this is the token sync response. */
347             if (!auth_type_found)
348                 pairadd(&request->config_items,
349                         pairmake("Auth-Type", "otp", T_OP_EQ));
350             return RLM_MODULE_OK;
351
352         }
353     } /* if (fast_sync && card supports sync mode) */
354
355 gen_challenge:
356     /* Set the resync bit by default if the user can't choose. */
357     if (!inst->fast_sync)
358         sflags |= htonl(1);
359
360     /* Generate a random challenge. */
361     if (otp_get_challenge(-1, challenge, inst->chal_len) == -1) {
362         otp_log(OTP_LOG_ERR, "autz: failed to obtain random challenge");
363         return RLM_MODULE_FAIL;
364     }
365
366     /*
367      * Create the State attribute, which will be returned to us along with
368      * the response.  We will need this to verify the response.  It must
369      * be hmac protected to prevent insertion of arbitrary State by an
370      * inside attacker.  If we won't actually use the State (server config
371      * doesn't allow async), we just use a trivial State.  We always create
372      * at least a trivial State, so otp_authorize() can quickly pass on to
373      * otp_authenticate().
374      */
375     if (inst->allow_async) {
376         time_t now = time(NULL);
377
378         if (sizeof(now) != 4 || sizeof(long) != 4) {
379             otp_log(OTP_LOG_ERR, "autz: only ILP32 arch is supported");
380             return RLM_MODULE_FAIL;
381         }
382         now = htonl(now);
383
384         if (otp_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
385             otp_log(OTP_LOG_ERR, "autz: failed to generate state");
386             return RLM_MODULE_FAIL;
387         }
388     } else {
389         /* x2 b/c pairmake() string->octet needs even num of digits */
390         state = rad_malloc(3 + inst->chal_len * 2);
391         (void) sprintf(state, "0x%s%s", challenge, challenge);
392     }
393     pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
394     free(state);
395
396     /* Add the challenge to the reply. */
397     {
398         char *u_challenge;      /* challenge with addt'l presentation text */
399
400         u_challenge = rad_malloc(strlen(inst->chal_prompt) +
401                                  OTP_MAX_CHALLENGE_LEN+1);
402         (void) sprintf(u_challenge, inst->chal_prompt, challenge);
403         pairadd(&request->reply->vps,
404                 pairmake("Reply-Message", u_challenge, T_OP_EQ));
405         free(u_challenge);
406     }
407
408     /*
409      * Mark the packet as an Access-Challenge packet.
410      * The server will take care of sending it to the user.
411      */
412     request->reply->code = PW_ACCESS_CHALLENGE;
413     DEBUG("rlm_otp: Sending Access-Challenge.");
414
415     /* TODO: support config-specific auth-type */
416     if (!auth_type_found)
417         pairadd(&request->config_items,
418                 pairmake("Auth-Type", "otp", T_OP_EQ));
419     return RLM_MODULE_HANDLED;
420 }
421
422
423 /* Verify the response entered by the user. */
424 static int
425 otp_authenticate(void *instance, REQUEST *request)
426 {
427     otp_option_t *inst = (otp_option_t *) instance;
428
429     char *username;
430     int rc;
431     int resync = 0;     /* resync flag for async mode */
432
433     char challenge[OTP_MAX_CHALLENGE_LEN + 1];
434     VALUE_PAIR *add_vps = NULL;
435
436     struct otp_pwe_cmp_t data = {
437         .request = request,
438         .inst = inst,
439         .returned_vps = &add_vps
440     };
441
442     /* User-Name attribute required. */
443     if (!request->username) {
444         otp_log(OTP_LOG_AUTH,
445                 "auth: Attribute \"User-Name\" required for authentication.");
446         return RLM_MODULE_INVALID;
447     }
448     username = request->username->vp_strvalue;
449
450     if ((data.pwattr = otp_pwe_present(request)) == 0) {
451         otp_log(OTP_LOG_AUTH, "auth: Attribute \"User-Password\" "
452                               "or equivalent required for authentication.");
453         return RLM_MODULE_INVALID;
454     }
455
456     /* Add a message to the auth log. */
457     pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
458                                             OTP_MODULE_NAME, T_OP_EQ));
459     pairadd(&request->packet->vps, pairmake("Module-Success-Message",
460                                             OTP_MODULE_NAME, T_OP_EQ));
461
462     /* Retrieve the challenge (from State attribute). */
463     challenge[0] = '\0';
464     {
465         VALUE_PAIR      *vp;
466         unsigned char   *state;
467         int32_t         sflags = 0;     /* state flags */
468         time_t          then;           /* state timestamp */
469
470         if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
471             int e_length = inst->chal_len;
472
473             /* Extend expected length if state should have been protected. */
474             if (inst->allow_async)
475                 e_length += 4 + 4 + 16; /* sflags + time + hmac */
476
477             if (vp->length != e_length) {
478                 otp_log(OTP_LOG_AUTH,
479                         "auth: bad state for [%s]: length", username);
480                 return RLM_MODULE_INVALID;
481             }
482
483             if (inst->allow_async) {
484                 /* Verify the state. */
485                 (void) memset(challenge, 0, sizeof(challenge));
486                 (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
487                 (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
488                 (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
489                 if (otp_gen_state(NULL, &state, challenge,
490                                   sflags, then, hmac_key) != 0) {
491                     otp_log(OTP_LOG_ERR, "auth: failed to generate state");
492                     return RLM_MODULE_FAIL;
493                 }
494                 if (memcmp(state, vp->vp_strvalue, vp->length)) {
495                     otp_log(OTP_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 (time(NULL) - then > inst->chal_delay) {
505                     otp_log(OTP_LOG_AUTH,
506                             "auth: bad state for [%s]: expired", username);
507                     return RLM_MODULE_REJECT;
508                 }
509                 resync = ntohl(sflags) & 1;
510             } /* if (State should have been protected) */
511         } /* if (State present) */
512     } /* code block */
513
514     /* do it */
515     rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
516                                   otp_pwe_cmp, &data, "auth"));
517
518     /* Handle any vps returned from otp_pwe_cmp(). */
519     if (rc == RLM_MODULE_OK) {
520         pairadd(&request->reply->vps, add_vps);
521     } else {
522         pairfree(&add_vps);
523     }
524     return rc;
525 }
526
527
528 /* per-instance destruction */
529 static int
530 otp_detach(void *instance)
531 {
532     otp_option_t *inst = (otp_option_t *) instance;
533
534     free(inst->pwdfile);
535     free(inst->syncdir);
536     free(inst->chal_prompt);
537     free(inst->chal_req);
538     free(inst->resync_req);
539     free(instance);
540     /*
541      * Only the main thread instantiates and detaches instances,
542      * so this does not need mutex protection.
543      */
544     if (--ninstance == 0)
545         memset(hmac_key, 0, sizeof(hmac_key));
546
547     return 0;
548 }
549
550
551 /*
552  *      If the module needs to temporarily modify it's instantiation
553  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
554  *      The server will then take care of ensuring that the module
555  *      is single-threaded.
556  */
557 module_t rlm_otp = {
558         RLM_MODULE_INIT,
559         "otp",
560         RLM_TYPE_THREAD_SAFE,           /* type */
561         otp_instantiate,                /* instantiation */
562         {
563                 otp_authenticate,       /* authentication */
564                 otp_authorize,          /* authorization */
565                 NULL,                   /* preaccounting */
566                 NULL,                   /* accounting */
567                 NULL,                   /* checksimul */
568                 NULL,                   /* pre-proxy */
569                 NULL,                   /* post-proxy */
570                 NULL                    /* post-auth */
571         },
572         otp_detach,                     /* detach */
573 };