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