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