Change user_state 'challenge' field from string to uchar, which
[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 TRI-D Systems, Inc.
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   { "rwindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_size),
92     NULL, "0" },
93   { "rwindow_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_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   /* Enforce a single "%" sequence, which must be "%s" */
176   p = strchr(opt->chal_prompt, '%');
177   if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
178       strncmp(p,"%s",2)) {
179     free(opt->chal_prompt);
180     opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
181     otp_log(OTP_LOG_ERR, "invalid challenge_prompt, using default of \"%s\"",
182             OTP_CHALLENGE_PROMPT);
183   }
184
185   if (opt->softfail < 0) {
186     opt->softfail = 5;
187     otp_log(OTP_LOG_ERR, "softfail must be at least 1 "
188             "(or 0 == infinite), using default of 5");
189   }
190
191   if (opt->hardfail < 0) {
192     opt->hardfail = 0;
193     otp_log(OTP_LOG_ERR, "hardfail must be at least 1 "
194             "(or 0 == infinite), using default of 0");
195   }
196
197   if (!opt->hardfail && opt->hardfail <= opt->softfail) {
198     /*
199      * This is noise if the admin leaves softfail alone, so it gets
200      * the default value of 5, and sets hardfail <= to that ... but
201      * in practice that will never happen.  Anyway, it is easily
202      * overcome with a softfail setting of 0.
203      *
204      * This is because we can't tell the difference between a default
205      * [softfail] value and an admin-configured one.
206      */
207     otp_log(OTP_LOG_ERR, "hardfail (%d) is less than softfail (%d), "
208                          "effectively disabling softfail",
209             opt->hardfail, opt->softfail);
210   }
211
212   if (opt->fast_sync && !opt->allow_sync) {
213     opt->fast_sync = 0;
214     otp_log(OTP_LOG_ERR,
215             "fast_sync is yes, but allow_sync is no; disabling fast_sync");
216   }
217
218   if (!opt->allow_sync && !opt->allow_async) {
219     otp_log(OTP_LOG_ERR,
220             "at least one of {allow_async, allow_sync} must be set");
221     free(opt);
222     return -1;
223   }
224
225   if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
226     (opt->ewindow_size < 0)) {
227     opt->ewindow_size = 0;
228     otp_log(OTP_LOG_ERR, "max ewindow_size is %d, using default of 0",
229             OTP_MAX_EWINDOW_SIZE);
230   }
231
232   if (opt->rwindow_size && (opt->rwindow_size < opt->ewindow_size)) {
233     opt->rwindow_size = 0;
234     otp_log(OTP_LOG_ERR, "rwindow_size must be at least as large as "
235                          "ewindow_size, using default of 0");
236   }
237
238   if (opt->rwindow_size && !opt->rwindow_delay) {
239     opt->rwindow_size = 0;
240     otp_log(OTP_LOG_ERR, "rwindow_size is non-zero, "
241                          "but rwindow_delay is zero; disabling rwindow");
242   }
243
244   if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
245     opt->mschapv2_mppe_policy = 2;
246     otp_log(OTP_LOG_ERR, "invalid value for mschapv2_mppe, using default of 2");
247   }
248
249   if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
250     opt->mschapv2_mppe_types = 2;
251     otp_log(OTP_LOG_ERR,
252             "invalid value for mschapv2_mppe_bits, using default of 2");
253   }
254
255   if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
256     opt->mschap_mppe_policy = 2;
257     otp_log(OTP_LOG_ERR, "invalid value for mschap_mppe, using default of 2");
258   }
259
260   if (opt->mschap_mppe_types != 2) {
261     opt->mschap_mppe_types = 2;
262     otp_log(OTP_LOG_ERR,
263             "invalid value for mschap_mppe_bits, using default of 2");
264   }
265
266 #if 0
267   if (opt->twindow_max - opt->twindow_min > OTP_MAX_TWINDOW_SIZE) {
268     opt->twindow_min = opt->twindow_max = 0;
269     otp_log(OTP_LOG_ERR, "max time window size is %d, using default of 0",
270             OTP_MAX_TWINDOW_SIZE);
271   }
272   if ((opt->twindow_min > 0) || (opt->twindow_max < 0) ||
273       (opt->twindow_max < opt->twindow_min)) {
274     opt->twindow_min = opt->twindow_max = 0;
275     otp_log(OTP_LOG_ERR, "invalid values for time window, using default of 0");
276   }
277 #endif
278
279   /* Set the instance name (for use with authorize()) */
280   opt->name = cf_section_name2(conf);
281   if (!opt->name)
282     opt->name = cf_section_name1(conf);
283   if (!opt->name) {
284     otp_log(OTP_LOG_CRIT, "no instance name (this can't happen)");
285     free(opt);
286     return -1;
287   }
288
289   *instance = opt;
290   return 0;
291 }
292
293
294 /* Generate a challenge to be presented to the user. */
295 static int
296 otp_authorize(void *instance, REQUEST *request)
297 {
298   otp_option_t *inst = (otp_option_t *) instance;
299
300   char challenge[OTP_MAX_CHALLENGE_LEN + 1];    /* +1 for '\0' terminator */
301   char *state;
302   int auth_type_found;
303   int32_t sflags = 0; /* flags for state */
304
305   struct otp_pwe_cmp_t data = {
306     .request            = request,
307     .inst               = inst,
308     .returned_vps       = NULL
309   };
310
311   /* Early exit if Auth-Type != inst->name */
312   {
313     VALUE_PAIR *vp;
314
315     auth_type_found = 0;
316     if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
317       auth_type_found = 1;
318       if (strcmp(vp->vp_strvalue, inst->name))
319         return RLM_MODULE_NOOP;
320     }
321   }
322
323   /* The State attribute will be present if this is a response. */
324   if (pairfind(request->packet->vps, PW_STATE) != NULL) {
325     DEBUG("rlm_otp: autz: Found response to Access-Challenge");
326     return RLM_MODULE_OK;
327   }
328
329   /* User-Name attribute required. */
330   if (!request->username) {
331     otp_log(OTP_LOG_AUTH,
332             "autz: Attribute \"User-Name\" required for authentication.");
333     return RLM_MODULE_INVALID;
334   }
335
336   if ((data.pwattr = otp_pwe_present(request)) == 0) {
337     otp_log(OTP_LOG_AUTH, "autz: Attribute \"User-Password\" "
338             "or equivalent required for authentication.");
339     return RLM_MODULE_INVALID;
340   }
341
342   /* fast_sync mode (challenge only if requested) */
343   if (inst->fast_sync) {
344     if ((!otp_pwe_cmp(&data, inst->resync_req) &&
345         /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
346         !otp_pwe_cmp(&data, inst->chal_req)) {
347       /*
348        * Generate a challenge if requested.  Note that we do this
349        * even if configuration doesn't allow async mode.
350        */
351       DEBUG("rlm_otp: autz: fast_sync challenge requested");
352       goto gen_challenge;
353
354     } else {
355       /* Otherwise, this is the token sync response. */
356       if (!auth_type_found)
357         pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
358         return RLM_MODULE_OK;
359
360     }
361   } /* if (fast_sync && card supports sync mode) */
362
363 gen_challenge:
364   /* Set the resync bit by default if the user can't choose. */
365   if (!inst->fast_sync)
366     sflags |= htonl(1);
367
368   /* Generate a random challenge. */
369   if (otp_get_challenge(-1, challenge, inst->chal_len) == -1) {
370     otp_log(OTP_LOG_ERR, "autz: failed to obtain random challenge");
371     return RLM_MODULE_FAIL;
372   }
373
374   /*
375    * Create the State attribute, which will be returned to us along with
376    * the response.  We will need this to verify the response.  It must
377    * be hmac protected to prevent insertion of arbitrary State by an
378    * inside attacker.  If we won't actually use the State (server config
379    * doesn't allow async), we just use a trivial State.  We always create
380    * at least a trivial State, so otp_authorize() can quickly pass on to
381    * otp_authenticate().
382    */
383   if (inst->allow_async) {
384     time_t now = time(NULL);
385
386     if (sizeof(now) != 4 || sizeof(long) != 4) {
387       otp_log(OTP_LOG_ERR, "autz: only ILP32 arch is supported");
388       return RLM_MODULE_FAIL;
389     }
390     now = htonl(now);
391
392     if (otp_gen_state(&state, NULL, challenge, inst->chal_len, sflags,
393                       now, hmac_key) != 0) {
394       otp_log(OTP_LOG_ERR, "autz: failed to generate state");
395       return RLM_MODULE_FAIL;
396     }
397   } else {
398     state = rad_malloc(5);
399     (void) sprintf(state, "0x00");
400   }
401   pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
402   free(state);
403
404   /* Add the challenge to the reply. */
405   {
406     char *u_challenge;  /* challenge with addt'l presentation text */
407
408     u_challenge = rad_malloc(strlen(inst->chal_prompt) +
409                              OTP_MAX_CHALLENGE_LEN + 1);
410 /* XXX */
411     (void) sprintf(u_challenge, inst->chal_prompt, challenge);
412     pairadd(&request->reply->vps,
413             pairmake("Reply-Message", u_challenge, T_OP_EQ));
414     free(u_challenge);
415   }
416
417   /*
418    * Mark the packet as an Access-Challenge packet.
419    * The server will take care of sending it to the user.
420    */
421   request->reply->code = PW_ACCESS_CHALLENGE;
422   DEBUG("rlm_otp: Sending Access-Challenge.");
423
424   /* TODO: support config-specific auth-type */
425   if (!auth_type_found)
426     pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
427   return RLM_MODULE_HANDLED;
428 }
429
430
431 /* Verify the response entered by the user. */
432 static int
433 otp_authenticate(void *instance, REQUEST *request)
434 {
435   otp_option_t *inst = (otp_option_t *) instance;
436
437   char *username;
438   int rc;
439   int resync = 0;       /* resync flag for async mode */
440
441   unsigned char challenge[OTP_MAX_CHALLENGE_LEN];       /* cf. authorize() */
442   VALUE_PAIR *add_vps = NULL;
443
444   struct otp_pwe_cmp_t data = {
445     .request            = request,
446     .inst               = inst,
447     .returned_vps       = &add_vps
448   };
449
450   /* User-Name attribute required. */
451   if (!request->username) {
452     otp_log(OTP_LOG_AUTH,
453             "auth: Attribute \"User-Name\" required for authentication.");
454     return RLM_MODULE_INVALID;
455   }
456   username = request->username->vp_strvalue;
457
458   if ((data.pwattr = otp_pwe_present(request)) == 0) {
459     otp_log(OTP_LOG_AUTH, "auth: Attribute \"User-Password\" "
460                           "or equivalent required for authentication.");
461     return RLM_MODULE_INVALID;
462   }
463
464   /* Add a message to the auth log. */
465   pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
466                                           OTP_MODULE_NAME, T_OP_EQ));
467   pairadd(&request->packet->vps, pairmake("Module-Success-Message",
468                                           OTP_MODULE_NAME, T_OP_EQ));
469
470   /* Retrieve the challenge (from State attribute). */
471   {
472     VALUE_PAIR  *vp;
473     unsigned char       *state;
474     int32_t             sflags = 0;     /* state flags */
475     int32_t             then;           /* state timestamp */
476
477     if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
478       int e_length = inst->chal_len;
479
480       /* Extend expected length if state should have been protected. */
481       if (inst->allow_async)
482         e_length += 4 + 4 + 16; /* sflags + time + hmac */
483
484       if (vp->length != e_length) {
485         otp_log(OTP_LOG_AUTH, "auth: bad state for [%s]: length", username);
486         return RLM_MODULE_INVALID;
487       }
488
489       if (inst->allow_async) {
490         /* Verify the state. */
491         (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
492         (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
493         (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
494         if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
495                           sflags, then, hmac_key) != 0) {
496           otp_log(OTP_LOG_ERR, "auth: failed to generate state");
497           return RLM_MODULE_FAIL;
498         }
499         if (memcmp(state, vp->vp_strvalue, vp->length)) {
500           otp_log(OTP_LOG_AUTH, "auth: bad state for [%s]: hmac", username);
501           free(state);
502           return RLM_MODULE_REJECT;
503         }
504         free(state);
505
506         /* State is valid, but check expiry. */
507         then = ntohl(then);
508         if (time(NULL) - then > inst->chal_delay) {
509           otp_log(OTP_LOG_AUTH, "auth: bad state for [%s]: expired", username);
510           return RLM_MODULE_REJECT;
511         }
512         resync = ntohl(sflags) & 1;
513       } /* if (State should have been protected) */
514     } /* if (State present) */
515   } /* code block */
516
517   /* do it */
518   rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
519                                 otp_pwe_cmp, &data, "auth"));
520
521   /* Handle any vps returned from otp_pwe_cmp(). */
522   if (rc == RLM_MODULE_OK) {
523     pairadd(&request->reply->vps, add_vps);
524   } else {
525     pairfree(&add_vps);
526   }
527   return rc;
528 }
529
530
531 /* per-instance destruction */
532 static int
533 otp_detach(void *instance)
534 {
535   otp_option_t *inst = (otp_option_t *) instance;
536
537   free(inst->pwdfile);
538   free(inst->lsmd_rp);
539   free(inst->chal_prompt);
540   free(inst->chal_req);
541   free(inst->resync_req);
542   free(instance);
543   /*
544    * Only the main thread instantiates and detaches instances,
545    * so this does not need mutex protection.
546    */
547   if (--ninstance == 0)
548     memset(hmac_key, 0, sizeof(hmac_key));
549
550   return 0;
551 }
552
553
554 /*
555  *      If the module needs to temporarily modify it's instantiation
556  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
557  *      The server will then take care of ensuring that the module
558  *      is single-threaded.
559  */
560 module_t rlm_otp = {
561   RLM_MODULE_INIT,
562   "otp",
563   RLM_TYPE_THREAD_SAFE,         /* type */
564   otp_instantiate,              /* instantiation */
565   otp_detach,                   /* detach */
566   {
567     otp_authenticate,           /* authentication */
568     otp_authorize,              /* authorization */
569     NULL,                       /* preaccounting */
570     NULL,                       /* accounting */
571     NULL,                       /* checksimul */
572     NULL,                       /* pre-proxy */
573     NULL,                       /* post-proxy */
574     NULL                        /* post-auth */
575   },
576 };