3
* $Id: otp_rlm.c,v 1.19.2.2 2005/12/08 02:07:32 fcusack Exp $
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.
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.
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
19
* Copyright 2000,2001,2002 The FreeRADIUS server project
20
* Copyright 2001,2002 Google, Inc.
21
* Copyright 2005 TRI-D Systems, Inc.
25
* STRONG WARNING SECTION:
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>.
32
* Please read the accompanying docs.
36
* TODO: support setting multiple auth-types in authorize()
37
* TODO: support other than ILP32 (for State)
43
#include <sys/types.h>
48
#include <netinet/in.h> /* htonl(), ntohl() */
55
static const char rcsid[] = "$Id: otp_rlm.c,v 1.19.2.2 2005/12/08 02:07:32 fcusack Exp $";
58
static unsigned char hmac_key[16]; /* to protect State attribute */
59
static int ninstance = 0; /* #instances, for global init */
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),
65
{ "lsmd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, 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),
71
{ "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_delay),
73
{ "softfail", PW_TYPE_INTEGER, offsetof(otp_option_t, softfail),
75
{ "hardfail", PW_TYPE_INTEGER, offsetof(otp_option_t, hardfail),
77
{ "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
79
{ "fast_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, fast_sync),
81
{ "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
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),
89
{ "ewindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow_size),
91
{ "rwindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_size),
93
{ "rwindow_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_delay),
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" },
104
{ "debug", PW_TYPE_BOOLEAN, offsetof(otp_option_t, debug),
107
{ NULL, -1, 0, NULL, NULL } /* end the list */
111
/* transform otp_pw_valid() return code into an rlm return code */
116
case OTP_RC_OK: return RLM_MODULE_OK;
117
case OTP_RC_USER_UNKNOWN: return RLM_MODULE_REJECT;
118
case OTP_RC_AUTHINFO_UNAVAIL: return RLM_MODULE_REJECT;
119
case OTP_RC_AUTH_ERR: return RLM_MODULE_REJECT;
120
case OTP_RC_MAXTRIES: return RLM_MODULE_USERLOCK;
121
case OTP_RC_SERVICE_ERR: return RLM_MODULE_FAIL;
122
default: return RLM_MODULE_FAIL;
127
/* per-instance initialization */
129
otp_instantiate(CONF_SECTION *conf, void **instance)
131
const char *log_prefix = OTP_MODULE_NAME;
135
/* Set up a storage area for instance data. */
136
opt = rad_malloc(sizeof(*opt));
137
(void) memset(opt, 0, sizeof(*opt));
139
/* If the configuration parameters can't be parsed, then fail. */
140
if (cf_section_parse(conf, opt, module_config) < 0) {
145
/* Onetime initialization. */
147
/* Generate a random key, used to protect the State attribute. */
148
if (otp_get_random(-1, hmac_key, sizeof(hmac_key), log_prefix) == -1) {
149
otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random data for hmac_key",
150
log_prefix, __func__);
155
/* Initialize the passcode encoding/checking functions. */
159
* Don't do this again.
160
* Only the main thread instantiates and detaches instances,
161
* so this does not need mutex protection.
166
/* Verify ranges for those vars that are limited. */
167
if ((opt->chal_len < 5) || (opt->chal_len > OTP_MAX_CHALLENGE_LEN)) {
170
"%s: %s: invalid challenge_length, range 5-%d, using default of 6",
171
log_prefix, __func__, OTP_MAX_CHALLENGE_LEN);
174
/* Enforce a single "%" sequence, which must be "%s" */
175
p = strchr(opt->chal_prompt, '%');
176
if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
178
free(opt->chal_prompt);
179
opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
181
"%s: %s: invalid challenge_prompt, using default of \"%s\"",
182
log_prefix, __func__, OTP_CHALLENGE_PROMPT);
185
if (opt->softfail < 0) {
187
otp_log(OTP_LOG_ERR, "%s: %s: softfail must be at least 1 "
188
"(or 0 == infinite), using default of 5",
189
log_prefix, __func__);
192
if (opt->hardfail < 0) {
194
otp_log(OTP_LOG_ERR, "%s: %s: hardfail must be at least 1 "
195
"(or 0 == infinite), using default of 0",
196
log_prefix, __func__);
199
if (!opt->hardfail && opt->hardfail <= opt->softfail) {
201
* This is noise if the admin leaves softfail alone, so it gets
202
* the default value of 5, and sets hardfail <= to that ... but
203
* in practice that will never happen. Anyway, it is easily
204
* overcome with a softfail setting of 0.
206
* This is because we can't tell the difference between a default
207
* [softfail] value and an admin-configured one.
209
otp_log(OTP_LOG_ERR, "%s: %s: hardfail (%d) is less than softfail (%d), "
210
"effectively disabling softfail",
211
log_prefix, __func__, opt->hardfail, opt->softfail);
214
if (opt->fast_sync && !opt->allow_sync) {
216
otp_log(OTP_LOG_ERR, "%s: %s: fast_sync is yes, but allow_sync is no; "
217
"disabling fast_sync",
218
log_prefix, __func__);
221
if (!opt->allow_sync && !opt->allow_async) {
223
"%s: %s: at least one of {allow_async, allow_sync} must be set",
224
log_prefix, __func__);
229
if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
230
(opt->ewindow_size < 0)) {
231
opt->ewindow_size = 0;
232
otp_log(OTP_LOG_ERR, "%s: %s: max ewindow_size is %d, using default of 0",
233
log_prefix, __func__, OTP_MAX_EWINDOW_SIZE);
236
if (opt->rwindow_size && (opt->rwindow_size < opt->ewindow_size)) {
237
opt->rwindow_size = 0;
238
otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size must be at least as large as "
239
"ewindow_size, using default of 0",
240
log_prefix, __func__);
243
if (opt->rwindow_size && !opt->rwindow_delay) {
244
opt->rwindow_size = 0;
245
otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size is non-zero, "
246
"but rwindow_delay is zero; disabling rwindow",
247
log_prefix, __func__);
250
if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
251
opt->mschapv2_mppe_policy = 2;
253
"%s: %s: invalid value for mschapv2_mppe, using default of 2",
254
log_prefix, __func__);
257
if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
258
opt->mschapv2_mppe_types = 2;
260
"%s: %s: invalid value for mschapv2_mppe_bits, using default of 2",
261
log_prefix, __func__);
264
if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
265
opt->mschap_mppe_policy = 2;
267
"%s: %s: invalid value for mschap_mppe, using default of 2",
268
log_prefix, __func__);
271
if (opt->mschap_mppe_types != 2) {
272
opt->mschap_mppe_types = 2;
274
"%s: %s: invalid value for mschap_mppe_bits, using default of 2",
275
log_prefix, __func__);
278
/* set the instance name (for use with authorize()) */
279
opt->name = cf_section_name2(conf);
281
opt->name = cf_section_name1(conf);
283
otp_log(OTP_LOG_CRIT, "%s: %s: no instance name (this can't happen)",
284
log_prefix, __func__);
289
/* set debug opt for portable debug output (see DEBUG definition) */
298
/* Generate a challenge to be presented to the user. */
300
otp_authorize(void *instance, REQUEST *request)
302
otp_option_t *inst = (otp_option_t *) instance;
303
const char *log_prefix = OTP_MODULE_NAME;
305
char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
308
int32_t sflags = 0; /* flags for state */
310
struct otp_pwe_cmp_t data = {
316
/* Early exit if Auth-Type != inst->name */
321
if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
323
if (strcmp(vp->strvalue, inst->name))
324
return RLM_MODULE_NOOP;
328
/* The State attribute will be present if this is a response. */
329
if (pairfind(request->packet->vps, PW_STATE) != NULL) {
330
DEBUG("rlm_otp: autz: Found response to Access-Challenge");
331
return RLM_MODULE_OK;
334
/* User-Name attribute required. */
335
if (!request->username) {
336
otp_log(OTP_LOG_AUTH,
337
"%s: %s: Attribute \"User-Name\" required for authentication.",
338
log_prefix, __func__);
339
return RLM_MODULE_INVALID;
342
if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
343
otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
344
"or equivalent required for authentication.",
345
log_prefix, __func__);
346
return RLM_MODULE_INVALID;
349
/* fast_sync mode (challenge only if requested) */
350
if (inst->fast_sync) {
351
if ((!otp_pwe_cmp(&data, inst->resync_req, log_prefix) &&
352
/* Set a bit indicating resync */ (sflags |= htonl(1))) ||
353
!otp_pwe_cmp(&data, inst->chal_req, log_prefix)) {
355
* Generate a challenge if requested. Note that we do this
356
* even if configuration doesn't allow async mode.
358
DEBUG("rlm_otp: autz: fast_sync challenge requested");
362
/* Otherwise, this is the token sync response. */
363
if (!auth_type_found)
364
pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
365
return RLM_MODULE_OK;
368
} /* if (fast_sync && card supports sync mode) */
371
/* Set the resync bit by default if the user can't choose. */
372
if (!inst->fast_sync)
375
/* Generate a random challenge. */
376
if (otp_async_challenge(-1, challenge, inst->chal_len, log_prefix) == -1) {
377
otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random challenge",
378
log_prefix, __func__);
379
return RLM_MODULE_FAIL;
383
* Create the State attribute, which will be returned to us along with
384
* the response. We will need this to verify the response. It must
385
* be hmac protected to prevent insertion of arbitrary State by an
386
* inside attacker. If we won't actually use the State (server config
387
* doesn't allow async), we just use a trivial State. We always create
388
* at least a trivial State, so otp_authorize() can quickly pass on to
389
* otp_authenticate().
391
if (inst->allow_async) {
392
time_t now = time(NULL);
394
if (sizeof(now) != 4 || sizeof(long) != 4) {
395
otp_log(OTP_LOG_ERR, "%s: %s: only ILP32 arch is supported",
396
log_prefix, __func__);
397
return RLM_MODULE_FAIL;
401
if (otp_gen_state(&state, NULL, challenge, inst->chal_len, sflags,
402
now, hmac_key) != 0) {
403
otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
404
log_prefix, __func__);
405
return RLM_MODULE_FAIL;
408
state = rad_malloc(5);
409
(void) sprintf(state, "0x00");
411
pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
414
/* Add the challenge to the reply. */
416
char *u_challenge; /* challenge with addt'l presentation text */
418
u_challenge = rad_malloc(strlen(inst->chal_prompt) +
419
OTP_MAX_CHALLENGE_LEN + 1);
421
(void) sprintf(u_challenge, inst->chal_prompt, challenge);
422
pairadd(&request->reply->vps,
423
pairmake("Reply-Message", u_challenge, T_OP_EQ));
428
* Mark the packet as an Access-Challenge packet.
429
* The server will take care of sending it to the user.
431
request->reply->code = PW_ACCESS_CHALLENGE;
432
DEBUG("rlm_otp: Sending Access-Challenge.");
434
/* TODO: support config-specific auth-type */
435
if (!auth_type_found)
436
pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
437
return RLM_MODULE_HANDLED;
441
/* Verify the response entered by the user. */
443
otp_authenticate(void *instance, REQUEST *request)
445
otp_option_t *inst = (otp_option_t *) instance;
446
const char *log_prefix = OTP_MODULE_NAME;
450
int resync = 0; /* resync flag for async mode */
452
unsigned char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
453
VALUE_PAIR *add_vps = NULL;
455
struct otp_pwe_cmp_t data = {
458
.returned_vps = &add_vps
461
/* User-Name attribute required. */
462
if (!request->username) {
463
otp_log(OTP_LOG_AUTH,
464
"%s: %s: Attribute \"User-Name\" required for authentication.",
465
log_prefix, __func__);
466
return RLM_MODULE_INVALID;
468
username = request->username->strvalue;
470
if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
471
otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
472
"or equivalent required for authentication.",
473
log_prefix, __func__);
474
return RLM_MODULE_INVALID;
477
/* Add a message to the auth log. */
478
pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
479
OTP_MODULE_NAME, T_OP_EQ));
480
pairadd(&request->packet->vps, pairmake("Module-Success-Message",
481
OTP_MODULE_NAME, T_OP_EQ));
483
/* Retrieve the challenge (from State attribute). */
486
unsigned char *state;
487
int32_t sflags = 0; /* state flags */
488
int32_t then; /* state timestamp */
490
if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
491
int e_length = inst->chal_len;
493
/* Extend expected length if state should have been protected. */
494
if (inst->allow_async)
495
e_length += 4 + 4 + 16; /* sflags + time + hmac */
497
if (vp->length != e_length) {
498
otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: length",
499
log_prefix, __func__, username);
500
return RLM_MODULE_INVALID;
503
if (inst->allow_async) {
504
/* Verify the state. */
505
(void) memcpy(challenge, vp->strvalue, inst->chal_len);
506
(void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
507
(void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
508
if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
509
sflags, then, hmac_key) != 0) {
510
otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
511
log_prefix, __func__);
512
return RLM_MODULE_FAIL;
514
if (memcmp(state, vp->strvalue, vp->length)) {
515
otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: hmac",
516
log_prefix, __func__, username);
518
return RLM_MODULE_REJECT;
522
/* State is valid, but check expiry. */
524
if (time(NULL) - then > inst->chal_delay) {
525
otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: expired",
526
log_prefix, __func__, username);
527
return RLM_MODULE_REJECT;
529
resync = ntohl(sflags) & 1;
530
} /* if (State should have been protected) */
531
} /* if (State present) */
535
rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
536
otp_pwe_cmp, &data, log_prefix));
538
/* Handle any vps returned from otp_pwe_cmp(). */
539
if (rc == RLM_MODULE_OK) {
540
pairadd(&request->reply->vps, add_vps);
548
/* per-instance destruction */
550
otp_detach(void *instance)
552
otp_option_t *inst = (otp_option_t *) instance;
556
free(inst->chal_prompt);
557
free(inst->chal_req);
558
free(inst->resync_req);
561
* Only the main thread instantiates and detaches instances,
562
* so this does not need mutex protection.
564
if (--ninstance == 0)
565
memset(hmac_key, 0, sizeof(hmac_key));
572
* If the module needs to temporarily modify it's instantiation
573
* data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
574
* The server will then take care of ensuring that the module
575
* is single-threaded.
579
RLM_TYPE_THREAD_SAFE, /* type */
580
NULL, /* initialization */
581
otp_instantiate, /* instantiation */
583
otp_authenticate, /* authentication */
584
otp_authorize, /* authorization */
585
NULL, /* preaccounting */
586
NULL, /* accounting */
587
NULL, /* checksimul */
588
NULL, /* pre-proxy */
589
NULL, /* post-proxy */
592
otp_detach, /* detach */