import from HEAD
[freeradius.git] / src / modules / rlm_otp / otp_rlm.c
1 /*
2  * $Id$
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the Free Software
16  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  * Copyright 2000,2001,2002  The FreeRADIUS server project
19  * Copyright 2001,2002  Google, Inc.
20  * Copyright 2005,2006 TRI-D Systems, Inc.
21  */
22
23 #include "ident.h"
24 RCSID("$Id$")
25
26 #include <autoconf.h>
27 #include <radiusd.h>
28 #include <modules.h>
29
30 #include "extern.h"
31 #include "otp.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include <time.h>
38 #include <netinet/in.h> /* htonl(), ntohl() */
39
40 /* Global data */
41 static unsigned char hmac_key[16];      /* to protect State attribute  */
42 static int ninstance = 0;               /* #instances, for global init */
43
44 /* A mapping of configuration file names to internal variables. */
45 static const CONF_PARSER module_config[] = {
46   { "otpd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, otpd_rp),
47     NULL, OTP_OTPD_RP },
48   { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
49     NULL, OTP_CHALLENGE_PROMPT },
50   { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_len),
51     NULL, "6" },
52   { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
53     NULL, "30" },
54   { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
55     NULL, "yes" },
56   { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
57     NULL, "no" },
58
59   { "mschapv2_mppe", PW_TYPE_INTEGER,
60     offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
61   { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
62     offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
63   { "mschap_mppe", PW_TYPE_INTEGER,
64     offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
65   { "mschap_mppe_bits", PW_TYPE_INTEGER,
66     offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
67
68   { NULL, -1, 0, NULL, NULL }           /* end the list */
69 };
70
71
72 /* per-instance initialization */
73 static int
74 otp_instantiate(CONF_SECTION *conf, void **instance)
75 {
76   otp_option_t *opt;
77   char *p;
78
79   /* Set up a storage area for instance data. */
80   opt = rad_malloc(sizeof(*opt));
81   (void) memset(opt, 0, sizeof(*opt));
82
83   /* If the configuration parameters can't be parsed, then fail. */
84   if (cf_section_parse(conf, opt, module_config) < 0) {
85     free(opt);
86     return -1;
87   }
88
89   /* Onetime initialization. */
90   if (!ninstance) {
91     /* Generate a random key, used to protect the State attribute. */
92     otp_get_random(hmac_key, sizeof(hmac_key));
93
94     /* Initialize the passcode encoding/checking functions. */
95     otp_pwe_init();
96
97     /*
98      * Don't do this again.
99      * Only the main thread instantiates and detaches instances,
100      * so this does not need mutex protection.
101      */
102     ninstance++;
103   }
104
105   /* Verify ranges for those vars that are limited. */
106   if ((opt->challenge_len < 5) ||
107       (opt->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
108     opt->challenge_len = 6;
109     (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_length, range 5-%d, "
110                          "using default of 6",
111                   __func__, OTP_MAX_CHALLENGE_LEN);
112   }
113
114   /* Enforce a single "%" sequence, which must be "%s" */
115   p = strchr(opt->chal_prompt, '%');
116   if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
117       strncmp(p,"%s",2)) {
118     free(opt->chal_prompt);
119     opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
120     (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_prompt, "
121                          "using default of \"%s\"",
122                   __func__, OTP_CHALLENGE_PROMPT);
123   }
124
125   if (!opt->allow_sync && !opt->allow_async) {
126     (void) radlog(L_ERR, "rlm_otp: %s: at least one of "
127                          "{allow_async, allow_sync} must be set",
128                   __func__);
129     free(opt);
130     return -1;
131   }
132
133   if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
134     opt->mschapv2_mppe_policy = 2;
135     (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe, "
136                          "using default of 2",
137                   __func__);
138   }
139
140   if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
141     opt->mschapv2_mppe_types = 2;
142     (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe_bits, "
143                          "using default of 2",
144                   __func__);
145   }
146
147   if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
148     opt->mschap_mppe_policy = 2;
149     (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe, "
150                          "using default of 2",
151                   __func__);
152   }
153
154   if (opt->mschap_mppe_types != 2) {
155     opt->mschap_mppe_types = 2;
156     (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe_bits, "
157                          "using default of 2",
158                   __func__);
159   }
160
161   /* set the instance name (for use with authorize()) */
162   opt->name = cf_section_name2(conf);
163   if (!opt->name)
164     opt->name = cf_section_name1(conf);
165   if (!opt->name) {
166     (void) radlog(L_ERR|L_CONS,
167                   "rlm_otp: %s: no instance name (this can't happen)",
168                   __func__);
169     free(opt);
170     return -1;
171   }
172
173   *instance = opt;
174   return 0;
175 }
176
177
178 /* Generate a challenge to be presented to the user. */
179 static int
180 otp_authorize(void *instance, REQUEST *request)
181 {
182   otp_option_t *inst = (otp_option_t *) instance;
183
184   char challenge[OTP_MAX_CHALLENGE_LEN + 1];    /* +1 for '\0' terminator */
185   int auth_type_found;
186   otp_pwe_t pwe;
187
188   /* Early exit if Auth-Type != inst->name */
189   {
190     VALUE_PAIR *vp;
191
192     auth_type_found = 0;
193     if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
194       auth_type_found = 1;
195       if (strcmp(vp->strvalue, inst->name))
196         return RLM_MODULE_NOOP;
197     }
198   }
199
200   /* The State attribute will be present if this is a response. */
201   if (pairfind(request->packet->vps, PW_STATE) != NULL) {
202     DEBUG("rlm_otp: autz: Found response to Access-Challenge");
203     return RLM_MODULE_OK;
204   }
205
206   /* User-Name attribute required. */
207   if (!request->username) {
208     (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
209                           "for authentication.",
210                   __func__);
211     return RLM_MODULE_INVALID;
212   }
213
214   if ((pwe = otp_pwe_present(request)) == 0) {
215     (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
216                           "or equivalent required for authentication.",
217                   __func__);
218     return RLM_MODULE_INVALID;
219   }
220
221   /*
222    * We used to check for special "challenge" and "resync" passcodes
223    * here, but these are complicated to explain and application is
224    * limited.  More importantly, since we've removed all actual OTP
225    * code (now we ask otpd), it's awkward for us to support them.
226    * Should the need arise to reinstate these options, the most likely
227    * choice is to duplicate some otpd code here.
228    */
229
230   if (inst->allow_sync && !inst->allow_async) {
231     /* This is the token sync response. */
232     if (!auth_type_found)
233       pairadd(&request->config_items,
234               pairmake("Auth-Type", inst->name, T_OP_EQ));
235     return RLM_MODULE_OK;
236   }
237
238   /* Generate a random challenge. */
239   otp_async_challenge(challenge, inst->challenge_len);
240
241   /*
242    * Create the State attribute, which will be returned to us along with
243    * the response.  We will need this to verify the response.  It must
244    * be hmac protected to prevent insertion of arbitrary State by an
245    * inside attacker.  If we won't actually use the State (server config
246    * doesn't allow async), we just use a trivial State.  We always create
247    * at least a trivial State, so otp_authorize() can quickly pass on to
248    * otp_authenticate().
249    */
250   {
251     int32_t now = htonl(time(NULL));    /* low-order 32 bits on LP64 */
252     char state[OTP_MAX_RADSTATE_LEN];
253
254     if (otp_gen_state(state, NULL, challenge, inst->challenge_len, 0,
255                       now, hmac_key) != 0) {
256       (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",__func__);
257       return RLM_MODULE_FAIL;
258     }
259     pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
260   }
261
262   /* Add the challenge to the reply. */
263   {
264     char *u_challenge;  /* challenge with addt'l presentation text */
265
266     u_challenge = rad_malloc(strlen(inst->chal_prompt) +
267                              OTP_MAX_CHALLENGE_LEN + 1);
268     (void) sprintf(u_challenge, inst->chal_prompt, challenge);
269     pairadd(&request->reply->vps,
270             pairmake("Reply-Message", u_challenge, T_OP_EQ));
271     free(u_challenge);
272   }
273
274   /*
275    * Mark the packet as an Access-Challenge packet.
276    * The server will take care of sending it to the user.
277    */
278   request->reply->code = PW_ACCESS_CHALLENGE;
279   DEBUG("rlm_otp: Sending Access-Challenge.");
280
281   if (!auth_type_found)
282     pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
283   return RLM_MODULE_HANDLED;
284 }
285
286
287 /* Verify the response entered by the user. */
288 static int
289 otp_authenticate(void *instance, REQUEST *request)
290 {
291   otp_option_t *inst = (otp_option_t *) instance;
292
293   char *username;
294   int rc;
295   otp_pwe_t pwe;
296   VALUE_PAIR *vp;
297   unsigned char challenge[OTP_MAX_CHALLENGE_LEN];       /* cf. authorize() */
298   char passcode[OTP_MAX_PASSCODE_LEN + 1];
299
300   challenge[0] = '\0';  /* initialize for otp_pw_valid() */
301
302   /* User-Name attribute required. */
303   if (!request->username) {
304     (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
305                           "for authentication.",
306                   __func__);
307     return RLM_MODULE_INVALID;
308   }
309   username = request->username->strvalue;
310
311   if ((pwe = otp_pwe_present(request)) == 0) {
312     (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
313                           "or equivalent required for authentication.",
314                   __func__);
315     return RLM_MODULE_INVALID;
316   }
317
318   /* Add a message to the auth log. */
319   pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
320                                           "rlm_otp", T_OP_EQ));
321   pairadd(&request->packet->vps, pairmake("Module-Success-Message",
322                                           "rlm_otp", T_OP_EQ));
323
324   /* Retrieve the challenge (from State attribute). */
325   if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
326     unsigned char       state[OTP_MAX_RADSTATE_LEN];
327     unsigned char       raw_state[OTP_MAX_RADSTATE_LEN];
328     unsigned char       rad_state[OTP_MAX_RADSTATE_LEN];
329     int32_t             then;           /* state timestamp       */
330     int                 e_length;       /* expected State length */
331
332     /* set expected State length */
333     e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
334
335     if (vp->length != e_length) {
336       (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: length",
337                     __func__, username);
338       return RLM_MODULE_INVALID;
339     }
340
341     /*
342      * Verify the state.
343      */
344
345     /* ASCII decode; this is why OTP_MAX_RADSTATE_LEN has +1 */
346     (void) memcpy(rad_state, vp->strvalue, vp->length);
347     rad_state[e_length] = '\0';
348     if (otp_a2x(rad_state, raw_state) == -1) {
349       (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: not hex",
350                     __func__, username);
351       return RLM_MODULE_INVALID;
352     }
353
354     /* extract data from State */
355     (void) memcpy(challenge, raw_state, inst->challenge_len);
356     /* skip flag data */
357     (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
358
359     /* generate new state from returned input data */
360     if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
361                       then, hmac_key) != 0) {
362       (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",
363                     __func__);
364       return RLM_MODULE_FAIL;
365     }
366     /* compare generated state against returned state to verify hmac */
367     if (memcmp(state, vp->strvalue, vp->length)) {
368       (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: hmac",
369                     __func__, username);
370       return RLM_MODULE_REJECT;
371     }
372
373     /* State is valid, but check expiry. */
374     then = ntohl(then);
375     if (time(NULL) - then > inst->challenge_delay) {
376       (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: expired",
377                     __func__, username);
378       return RLM_MODULE_REJECT;
379     }
380   } /* if (State present) */
381
382   /* do it */
383   rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
384
385   /* Add MPPE data as needed. */
386   if (rc == RLM_MODULE_OK)
387     otp_mppe(request, pwe, inst, passcode);
388
389   return rc;
390 }
391
392
393 /* per-instance destruction */
394 static int
395 otp_detach(void *instance)
396 {
397   otp_option_t *inst = (otp_option_t *) instance;
398
399   free(inst->otpd_rp);
400   free(inst->chal_prompt);
401   free(instance);
402   /*
403    * Only the main thread instantiates and detaches instances,
404    * so this does not need mutex protection.
405    */
406   if (--ninstance == 0)
407     (void) memset(hmac_key, 0, sizeof(hmac_key));
408
409   return 0;
410 }
411
412
413 /*
414  *      If the module needs to temporarily modify it's instantiation
415  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
416  *      The server will then take care of ensuring that the module
417  *      is single-threaded.
418  */
419 module_t rlm_otp = {
420   "otp",
421   RLM_TYPE_THREAD_SAFE,         /* type */
422   NULL,
423   otp_instantiate,              /* instantiation */
424   {
425     otp_authenticate,           /* authentication */
426     otp_authorize,              /* authorization */
427     NULL,                       /* preaccounting */
428     NULL,                       /* accounting */
429     NULL,                       /* checksimul */
430     NULL,                       /* pre-proxy */
431     NULL,                       /* post-proxy */
432     NULL                        /* post-auth */
433   },
434   otp_detach,                   /* detach */
435   NULL,
436 };