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