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,2006 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.
38 #include <sys/types.h>
43 #include <netinet/in.h> /* htonl(), ntohl() */
50 static const char rcsid[] = "$Id$";
53 static unsigned char hmac_key[16]; /* to protect State attribute */
54 static int ninstance = 0; /* #instances, for global init */
56 /* A mapping of configuration file names to internal variables. */
57 static const CONF_PARSER module_config[] = {
58 { "pwdfile", PW_TYPE_STRING_PTR, offsetof(otp_option_t, pwdfile),
60 { "lsmd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, lsmd_rp),
62 { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
63 NULL, OTP_CHALLENGE_PROMPT },
64 { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_len),
66 { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, chal_delay),
68 { "softfail", PW_TYPE_INTEGER, offsetof(otp_option_t, softfail),
70 { "hardfail", PW_TYPE_INTEGER, offsetof(otp_option_t, hardfail),
72 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
74 { "fast_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, fast_sync),
76 { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
78 { "challenge_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, chal_req),
79 NULL, OTP_CHALLENGE_REQ },
80 { "resync_req", PW_TYPE_STRING_PTR, offsetof(otp_option_t, resync_req),
81 NULL, OTP_RESYNC_REQ },
82 { "prepend_pin", PW_TYPE_BOOLEAN, offsetof(otp_option_t, prepend_pin),
84 { "ewindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow_size),
86 { "rwindow_size", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_size),
88 { "rwindow_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, rwindow_delay),
90 { "mschapv2_mppe", PW_TYPE_INTEGER,
91 offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
92 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
93 offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
94 { "mschap_mppe", PW_TYPE_INTEGER,
95 offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
96 { "mschap_mppe_bits", PW_TYPE_INTEGER,
97 offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
99 { "site_transform", PW_TYPE_BOOLEAN, offsetof(otp_option_t, site_transform),
101 { "debug", PW_TYPE_BOOLEAN, offsetof(otp_option_t, debug),
104 { NULL, -1, 0, NULL, NULL } /* end the list */
108 /* transform otp_pw_valid() return code into an rlm return code */
113 case OTP_RC_OK: return RLM_MODULE_OK;
114 case OTP_RC_USER_UNKNOWN: return RLM_MODULE_REJECT;
115 case OTP_RC_AUTHINFO_UNAVAIL: return RLM_MODULE_REJECT;
116 case OTP_RC_AUTH_ERR: return RLM_MODULE_REJECT;
117 case OTP_RC_MAXTRIES: return RLM_MODULE_USERLOCK;
118 case OTP_RC_SERVICE_ERR: return RLM_MODULE_FAIL;
119 default: return RLM_MODULE_FAIL;
124 /* per-instance initialization */
126 otp_instantiate(CONF_SECTION *conf, void **instance)
128 const char *log_prefix = OTP_MODULE_NAME;
132 /* Set up a storage area for instance data. */
133 opt = rad_malloc(sizeof(*opt));
134 (void) memset(opt, 0, sizeof(*opt));
136 /* If the configuration parameters can't be parsed, then fail. */
137 if (cf_section_parse(conf, opt, module_config) < 0) {
142 /* Onetime initialization. */
144 /* Generate a random key, used to protect the State attribute. */
145 if (otp_get_random(-1, hmac_key, sizeof(hmac_key), log_prefix) == -1) {
146 otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random data for hmac_key",
147 log_prefix, __func__);
152 /* Initialize the passcode encoding/checking functions. */
156 * Don't do this again.
157 * Only the main thread instantiates and detaches instances,
158 * so this does not need mutex protection.
163 /* Verify ranges for those vars that are limited. */
164 if ((opt->chal_len < 5) || (opt->chal_len > OTP_MAX_CHALLENGE_LEN)) {
167 "%s: %s: invalid challenge_length, range 5-%d, using default of 6",
168 log_prefix, __func__, OTP_MAX_CHALLENGE_LEN);
171 /* Enforce a single "%" sequence, which must be "%s" */
172 p = strchr(opt->chal_prompt, '%');
173 if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
175 free(opt->chal_prompt);
176 opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
178 "%s: %s: invalid challenge_prompt, using default of \"%s\"",
179 log_prefix, __func__, OTP_CHALLENGE_PROMPT);
182 if (opt->softfail < 0) {
184 otp_log(OTP_LOG_ERR, "%s: %s: softfail must be at least 1 "
185 "(or 0 == infinite), using default of 5",
186 log_prefix, __func__);
189 if (opt->hardfail < 0) {
191 otp_log(OTP_LOG_ERR, "%s: %s: hardfail must be at least 1 "
192 "(or 0 == infinite), using default of 0",
193 log_prefix, __func__);
196 if (!opt->hardfail && opt->hardfail <= opt->softfail) {
198 * This is noise if the admin leaves softfail alone, so it gets
199 * the default value of 5, and sets hardfail <= to that ... but
200 * in practice that will never happen. Anyway, it is easily
201 * overcome with a softfail setting of 0.
203 * This is because we can't tell the difference between a default
204 * [softfail] value and an admin-configured one.
206 otp_log(OTP_LOG_ERR, "%s: %s: hardfail (%d) is less than softfail (%d), "
207 "effectively disabling softfail",
208 log_prefix, __func__, opt->hardfail, opt->softfail);
211 if (opt->fast_sync && !opt->allow_sync) {
213 otp_log(OTP_LOG_ERR, "%s: %s: fast_sync is yes, but allow_sync is no; "
214 "disabling fast_sync",
215 log_prefix, __func__);
218 if (!opt->allow_sync && !opt->allow_async) {
220 "%s: %s: at least one of {allow_async, allow_sync} must be set",
221 log_prefix, __func__);
226 if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
227 (opt->ewindow_size < 0)) {
228 opt->ewindow_size = 0;
229 otp_log(OTP_LOG_ERR, "%s: %s: max ewindow_size is %d, using default of 0",
230 log_prefix, __func__, OTP_MAX_EWINDOW_SIZE);
233 if (opt->rwindow_size && (opt->rwindow_size < opt->ewindow_size)) {
234 opt->rwindow_size = 0;
235 otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size must be at least as large as "
236 "ewindow_size, using default of 0",
237 log_prefix, __func__);
240 if (opt->rwindow_size && !opt->rwindow_delay) {
241 opt->rwindow_size = 0;
242 otp_log(OTP_LOG_ERR, "%s: %s: rwindow_size is non-zero, "
243 "but rwindow_delay is zero; disabling rwindow",
244 log_prefix, __func__);
247 if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
248 opt->mschapv2_mppe_policy = 2;
250 "%s: %s: invalid value for mschapv2_mppe, using default of 2",
251 log_prefix, __func__);
254 if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
255 opt->mschapv2_mppe_types = 2;
257 "%s: %s: invalid value for mschapv2_mppe_bits, using default of 2",
258 log_prefix, __func__);
261 if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
262 opt->mschap_mppe_policy = 2;
264 "%s: %s: invalid value for mschap_mppe, using default of 2",
265 log_prefix, __func__);
268 if (opt->mschap_mppe_types != 2) {
269 opt->mschap_mppe_types = 2;
271 "%s: %s: invalid value for mschap_mppe_bits, using default of 2",
272 log_prefix, __func__);
275 /* set the instance name (for use with authorize()) */
276 opt->name = cf_section_name2(conf);
278 opt->name = cf_section_name1(conf);
280 otp_log(OTP_LOG_CRIT, "%s: %s: no instance name (this can't happen)",
281 log_prefix, __func__);
286 /* set debug opt for portable debug output (see DEBUG definition) */
295 /* Generate a challenge to be presented to the user. */
297 otp_authorize(void *instance, REQUEST *request)
299 otp_option_t *inst = (otp_option_t *) instance;
300 const char *log_prefix = OTP_MODULE_NAME;
302 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
305 int32_t sflags = 0; /* flags for state */
307 struct otp_pwe_cmp_t data = {
313 /* Early exit if Auth-Type != inst->name */
318 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
320 if (strcmp(vp->strvalue, inst->name))
321 return RLM_MODULE_NOOP;
325 /* The State attribute will be present if this is a response. */
326 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
327 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
328 return RLM_MODULE_OK;
331 /* User-Name attribute required. */
332 if (!request->username) {
333 otp_log(OTP_LOG_AUTH,
334 "%s: %s: Attribute \"User-Name\" required for authentication.",
335 log_prefix, __func__);
336 return RLM_MODULE_INVALID;
339 if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
340 otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
341 "or equivalent required for authentication.",
342 log_prefix, __func__);
343 return RLM_MODULE_INVALID;
346 /* fast_sync mode (challenge only if requested) */
347 if (inst->fast_sync) {
348 if ((!otp_pwe_cmp(&data, inst->resync_req, log_prefix) &&
349 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
350 !otp_pwe_cmp(&data, inst->chal_req, log_prefix)) {
352 * Generate a challenge if requested. Note that we do this
353 * even if configuration doesn't allow async mode.
355 DEBUG("rlm_otp: autz: fast_sync challenge requested");
359 /* Otherwise, this is the token sync response. */
360 if (!auth_type_found)
361 pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
362 return RLM_MODULE_OK;
365 } /* if (fast_sync && card supports sync mode) */
368 /* Set the resync bit by default if the user can't choose. */
369 if (!inst->fast_sync)
372 /* Generate a random challenge. */
373 if (otp_async_challenge(-1, challenge, inst->chal_len, log_prefix) == -1) {
374 otp_log(OTP_LOG_ERR, "%s: %s: failed to obtain random challenge",
375 log_prefix, __func__);
376 return RLM_MODULE_FAIL;
380 * Create the State attribute, which will be returned to us along with
381 * the response. We will need this to verify the response. It must
382 * be hmac protected to prevent insertion of arbitrary State by an
383 * inside attacker. If we won't actually use the State (server config
384 * doesn't allow async), we just use a trivial State. We always create
385 * at least a trivial State, so otp_authorize() can quickly pass on to
386 * otp_authenticate().
388 if (inst->allow_async) {
389 int32_t now = htonl(time(NULL)); /* low-order 32 bits on LP64 */
391 if (otp_gen_state(&state, NULL, challenge, inst->chal_len, sflags,
392 now, hmac_key) != 0) {
393 otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
394 log_prefix, __func__);
395 return RLM_MODULE_FAIL;
398 state = rad_malloc(5);
399 /* a non-NUL byte, so that Cisco (see otp_gen_state()) returns it */
400 (void) sprintf(state, "0x01");
402 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
405 /* Add the challenge to the reply. */
407 char *u_challenge; /* challenge with addt'l presentation text */
409 u_challenge = rad_malloc(strlen(inst->chal_prompt) +
410 OTP_MAX_CHALLENGE_LEN + 1);
411 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
412 pairadd(&request->reply->vps,
413 pairmake("Reply-Message", u_challenge, T_OP_EQ));
418 * Mark the packet as an Access-Challenge packet.
419 * The server will take care of sending it to the user.
421 request->reply->code = PW_ACCESS_CHALLENGE;
422 DEBUG("rlm_otp: Sending Access-Challenge.");
424 /* TODO: support config-specific auth-type */
425 if (!auth_type_found)
426 pairadd(&request->config_items, pairmake("Auth-Type", "otp", T_OP_EQ));
427 return RLM_MODULE_HANDLED;
431 /* Verify the response entered by the user. */
433 otp_authenticate(void *instance, REQUEST *request)
435 otp_option_t *inst = (otp_option_t *) instance;
436 const char *log_prefix = OTP_MODULE_NAME;
440 int resync = 0; /* resync flag for async mode */
442 unsigned char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
443 VALUE_PAIR *add_vps = NULL;
445 struct otp_pwe_cmp_t data = {
448 .returned_vps = &add_vps
451 challenge[0] = '\0'; /* initialize for otp_pw_valid() */
453 /* User-Name attribute required. */
454 if (!request->username) {
455 otp_log(OTP_LOG_AUTH,
456 "%s: %s: Attribute \"User-Name\" required for authentication.",
457 log_prefix, __func__);
458 return RLM_MODULE_INVALID;
460 username = request->username->strvalue;
462 if ((data.pwattr = otp_pwe_present(request, log_prefix)) == 0) {
463 otp_log(OTP_LOG_AUTH, "%s: %s: Attribute \"User-Password\" "
464 "or equivalent required for authentication.",
465 log_prefix, __func__);
466 return RLM_MODULE_INVALID;
469 /* Add a message to the auth log. */
470 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
471 OTP_MODULE_NAME, T_OP_EQ));
472 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
473 OTP_MODULE_NAME, T_OP_EQ));
475 /* Retrieve the challenge (from State attribute). */
478 unsigned char *state;
479 unsigned char *raw_state;
480 unsigned char *rad_state;
481 int32_t sflags = 0; /* state flags */
482 int32_t then; /* state timestamp */
483 int e_length; /* expected State length */
485 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
486 /* set expected State length */
487 if (inst->allow_async)
488 e_length = inst->chal_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
492 if (vp->length != e_length) {
493 otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: length",
494 log_prefix, __func__, username);
495 return RLM_MODULE_INVALID;
498 if (inst->allow_async) {
503 rad_state = rad_malloc(e_length + 1);
504 raw_state = rad_malloc(e_length / 2);
507 (void) memcpy(rad_state, vp->strvalue, vp->length);
508 rad_state[e_length] = '\0';
509 (void) otp_keystring2keyblock(rad_state, raw_state);
512 /* extract data from State */
513 (void) memcpy(challenge, raw_state, inst->chal_len);
514 (void) memcpy(&sflags, raw_state + inst->chal_len, 4);
515 (void) memcpy(&then, raw_state + inst->chal_len + 4, 4);
518 /* generate new state from returned input data */
519 if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
520 sflags, then, hmac_key) != 0) {
521 otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
522 log_prefix, __func__);
523 return RLM_MODULE_FAIL;
525 /* compare generated state against returned state to verify hmac */
526 if (memcmp(state, vp->strvalue, vp->length)) {
527 otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: hmac",
528 log_prefix, __func__, username);
530 return RLM_MODULE_REJECT;
534 /* State is valid, but check expiry. */
536 if (time(NULL) - then > inst->chal_delay) {
537 otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: expired",
538 log_prefix, __func__, username);
539 return RLM_MODULE_REJECT;
541 resync = ntohl(sflags) & 1;
542 } /* if (State should have been protected) */
543 } /* if (State present) */
547 rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
548 otp_pwe_cmp, &data, log_prefix));
550 /* Handle any vps returned from otp_pwe_cmp(). */
551 if (rc == RLM_MODULE_OK) {
552 pairadd(&request->reply->vps, add_vps);
560 /* per-instance destruction */
562 otp_detach(void *instance)
564 otp_option_t *inst = (otp_option_t *) instance;
568 free(inst->chal_prompt);
569 free(inst->chal_req);
570 free(inst->resync_req);
573 * Only the main thread instantiates and detaches instances,
574 * so this does not need mutex protection.
576 if (--ninstance == 0)
577 memset(hmac_key, 0, sizeof(hmac_key));
584 * If the module needs to temporarily modify it's instantiation
585 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
586 * The server will then take care of ensuring that the module
587 * is single-threaded.
591 RLM_TYPE_THREAD_SAFE, /* type */
592 NULL, /* initialization */
593 otp_instantiate, /* instantiation */
595 otp_authenticate, /* authentication */
596 otp_authorize, /* authorization */
597 NULL, /* preaccounting */
598 NULL, /* accounting */
599 NULL, /* checksimul */
600 NULL, /* pre-proxy */
601 NULL, /* post-proxy */
604 otp_detach, /* detach */