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 Frank Cusack
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$";
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 { "ewindow2_size", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow2_size),
93 { "ewindow2_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, ewindow2_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 { "twindow_min", PW_TYPE_INTEGER, offsetof(otp_option_t, twindow_min),
106 { "twindow_max", PW_TYPE_INTEGER, offsetof(otp_option_t, twindow_max),
110 { NULL, -1, 0, NULL, NULL } /* end the list */
114 /* transform otp_pw_valid() return code into an rlm return code */
119 case OTP_RC_OK: return RLM_MODULE_OK;
120 case OTP_RC_USER_UNKNOWN: return RLM_MODULE_REJECT;
121 case OTP_RC_AUTHINFO_UNAVAIL: return RLM_MODULE_REJECT;
122 case OTP_RC_AUTH_ERR: return RLM_MODULE_REJECT;
123 case OTP_RC_MAXTRIES: return RLM_MODULE_USERLOCK;
124 case OTP_RC_SERVICE_ERR: return RLM_MODULE_FAIL;
125 default: return RLM_MODULE_FAIL;
130 /* per-instance initialization */
132 otp_instantiate(CONF_SECTION *conf, void **instance)
137 /* Set up a storage area for instance data. */
138 opt = rad_malloc(sizeof(*opt));
139 (void) memset(opt, 0, sizeof(*opt));
141 /* If the configuration parameters can't be parsed, then fail. */
142 if (cf_section_parse(conf, opt, module_config) < 0) {
147 /* Onetime initialization. */
149 /* Generate a random key, used to protect the State attribute. */
150 if (otp_get_random(-1, hmac_key, sizeof(hmac_key)) == -1) {
151 otp_log(OTP_LOG_ERR, "failed to obtain random data for hmac_key");
156 /* Initialize the passcode encoding/checking functions. */
160 * Don't do this again.
161 * Only the main thread instantiates and detaches instances,
162 * so this does not need mutex protection.
167 /* Verify ranges for those vars that are limited. */
168 if ((opt->chal_len < 5) || (opt->chal_len > OTP_MAX_CHALLENGE_LEN)) {
171 "invalid challenge_length, range 5-%d, using default of 6",
172 OTP_MAX_CHALLENGE_LEN);
176 /* Enforce a single "%" sequence, which must be "%s" */
177 p = strchr(opt->chal_prompt, '%');
178 if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
180 free(opt->chal_prompt);
181 opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
183 "invalid challenge_prompt, using default of \"%s\"",
184 OTP_CHALLENGE_PROMPT);
187 if (opt->softfail < 0) {
189 otp_log(OTP_LOG_ERR, "softfail must be at least 1 "
190 "(or 0 == infinite), using default of 5");
193 if (opt->hardfail < 0) {
195 otp_log(OTP_LOG_ERR, "hardfail must be at least 1 "
196 "(or 0 == infinite), using default of 0");
199 if (opt->fast_sync && !opt->allow_sync) {
201 otp_log(OTP_LOG_INFO,
202 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
205 if (!opt->allow_sync && !opt->allow_async) {
207 "at least one of {allow_async, allow_sync} must be set");
212 if ((opt->ewindow_size > OTP_MAX_EWINDOW_SIZE) ||
213 (opt->ewindow_size < 0)) {
214 opt->ewindow_size = 0;
215 otp_log(OTP_LOG_ERR, "max ewindow_size is %d, using default of 0",
216 OTP_MAX_EWINDOW_SIZE);
219 if (opt->ewindow2_size && (opt->ewindow2_size < opt->ewindow_size)) {
220 opt->ewindow2_size = 0;
221 otp_log(OTP_LOG_ERR, "ewindow2_size must be at least as large as "
222 "ewindow_size, using default of 0");
225 if (opt->ewindow2_size && !opt->ewindow2_delay) {
226 opt->ewindow2_size = 0;
227 otp_log(OTP_LOG_ERR, "ewindow2_size is non-zero, "
228 "but ewindow2_delay is zero; disabling ewindow2");
231 if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
232 opt->mschapv2_mppe_policy = 2;
234 "invalid value for mschapv2_mppe, using default of 2");
237 if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
238 opt->mschapv2_mppe_types = 2;
240 "invalid value for mschapv2_mppe_bits, using default of 2");
243 if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
244 opt->mschap_mppe_policy = 2;
246 "invalid value for mschap_mppe, using default of 2");
249 if (opt->mschap_mppe_types != 2) {
250 opt->mschap_mppe_types = 2;
252 "invalid value for mschap_mppe_bits, using default of 2");
256 if (opt->twindow_max - opt->twindow_min > OTP_MAX_TWINDOW_SIZE) {
257 opt->twindow_min = opt->twindow_max = 0;
258 otp_log(OTP_LOG_ERR, "max time window size is %d, using default of 0",
259 OTP_MAX_TWINDOW_SIZE);
261 if ((opt->twindow_min > 0) || (opt->twindow_max < 0) ||
262 (opt->twindow_max < opt->twindow_min)) {
263 opt->twindow_min = opt->twindow_max = 0;
265 "invalid values for time window, using default of 0");
269 /* Set the instance name (for use with authorize()) */
270 opt->name = cf_section_name2(conf);
272 opt->name = cf_section_name1(conf);
274 otp_log(OTP_LOG_CRIT, "no instance name (this can't happen)");
284 /* Generate a challenge to be presented to the user. */
286 otp_authorize(void *instance, REQUEST *request)
288 otp_option_t *inst = (otp_option_t *) instance;
290 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
293 int32_t sflags = 0; /* flags for state */
295 struct otp_pwe_cmp_t data = {
301 /* Early exit if Auth-Type != inst->name */
306 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
308 if (strcmp(vp->vp_strvalue, inst->name)) {
309 return RLM_MODULE_NOOP;
314 /* The State attribute will be present if this is a response. */
315 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
316 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
317 return RLM_MODULE_OK;
320 /* User-Name attribute required. */
321 if (!request->username) {
322 otp_log(OTP_LOG_AUTH,
323 "autz: Attribute \"User-Name\" required for authentication.");
324 return RLM_MODULE_INVALID;
327 if ((data.pwattr = otp_pwe_present(request)) == 0) {
328 otp_log(OTP_LOG_AUTH, "autz: Attribute \"User-Password\" "
329 "or equivalent required for authentication.");
330 return RLM_MODULE_INVALID;
333 /* fast_sync mode (challenge only if requested) */
334 if (inst->fast_sync) {
335 if ((!otp_pwe_cmp(&data, inst->resync_req) &&
336 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
337 !otp_pwe_cmp(&data, inst->chal_req)) {
339 * Generate a challenge if requested. Note that we do this
340 * even if configuration doesn't allow async mode.
342 DEBUG("rlm_otp: autz: fast_sync challenge requested");
346 /* Otherwise, this is the token sync response. */
347 if (!auth_type_found)
348 pairadd(&request->config_items,
349 pairmake("Auth-Type", "otp", T_OP_EQ));
350 return RLM_MODULE_OK;
353 } /* if (fast_sync && card supports sync mode) */
356 /* Set the resync bit by default if the user can't choose. */
357 if (!inst->fast_sync)
360 /* Generate a random challenge. */
361 if (otp_get_challenge(-1, challenge, inst->chal_len) == -1) {
362 otp_log(OTP_LOG_ERR, "autz: failed to obtain random challenge");
363 return RLM_MODULE_FAIL;
367 * Create the State attribute, which will be returned to us along with
368 * the response. We will need this to verify the response. It must
369 * be hmac protected to prevent insertion of arbitrary State by an
370 * inside attacker. If we won't actually use the State (server config
371 * doesn't allow async), we just use a trivial State. We always create
372 * at least a trivial State, so otp_authorize() can quickly pass on to
373 * otp_authenticate().
375 if (inst->allow_async) {
376 time_t now = time(NULL);
378 if (sizeof(now) != 4 || sizeof(long) != 4) {
379 otp_log(OTP_LOG_ERR, "autz: only ILP32 arch is supported");
380 return RLM_MODULE_FAIL;
384 if (otp_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
385 otp_log(OTP_LOG_ERR, "autz: failed to generate state");
386 return RLM_MODULE_FAIL;
389 /* x2 b/c pairmake() string->octet needs even num of digits */
390 state = rad_malloc(3 + inst->chal_len * 2);
391 (void) sprintf(state, "0x%s%s", challenge, challenge);
393 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
396 /* Add the challenge to the reply. */
398 char *u_challenge; /* challenge with addt'l presentation text */
400 u_challenge = rad_malloc(strlen(inst->chal_prompt) +
401 OTP_MAX_CHALLENGE_LEN+1);
402 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
403 pairadd(&request->reply->vps,
404 pairmake("Reply-Message", u_challenge, T_OP_EQ));
409 * Mark the packet as an Access-Challenge packet.
410 * The server will take care of sending it to the user.
412 request->reply->code = PW_ACCESS_CHALLENGE;
413 DEBUG("rlm_otp: Sending Access-Challenge.");
415 /* TODO: support config-specific auth-type */
416 if (!auth_type_found)
417 pairadd(&request->config_items,
418 pairmake("Auth-Type", "otp", T_OP_EQ));
419 return RLM_MODULE_HANDLED;
423 /* Verify the response entered by the user. */
425 otp_authenticate(void *instance, REQUEST *request)
427 otp_option_t *inst = (otp_option_t *) instance;
431 int resync = 0; /* resync flag for async mode */
433 char challenge[OTP_MAX_CHALLENGE_LEN + 1];
434 VALUE_PAIR *add_vps = NULL;
436 struct otp_pwe_cmp_t data = {
439 .returned_vps = &add_vps
442 /* User-Name attribute required. */
443 if (!request->username) {
444 otp_log(OTP_LOG_AUTH,
445 "auth: Attribute \"User-Name\" required for authentication.");
446 return RLM_MODULE_INVALID;
448 username = request->username->vp_strvalue;
450 if ((data.pwattr = otp_pwe_present(request)) == 0) {
451 otp_log(OTP_LOG_AUTH, "auth: Attribute \"User-Password\" "
452 "or equivalent required for authentication.");
453 return RLM_MODULE_INVALID;
456 /* Add a message to the auth log. */
457 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
458 OTP_MODULE_NAME, T_OP_EQ));
459 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
460 OTP_MODULE_NAME, T_OP_EQ));
462 /* Retrieve the challenge (from State attribute). */
466 unsigned char *state;
467 int32_t sflags = 0; /* state flags */
468 time_t then; /* state timestamp */
470 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
471 int e_length = inst->chal_len;
473 /* Extend expected length if state should have been protected. */
474 if (inst->allow_async)
475 e_length += 4 + 4 + 16; /* sflags + time + hmac */
477 if (vp->length != e_length) {
478 otp_log(OTP_LOG_AUTH,
479 "auth: bad state for [%s]: length", username);
480 return RLM_MODULE_INVALID;
483 if (inst->allow_async) {
484 /* Verify the state. */
485 (void) memset(challenge, 0, sizeof(challenge));
486 (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
487 (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
488 (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
489 if (otp_gen_state(NULL, &state, challenge,
490 sflags, then, hmac_key) != 0) {
491 otp_log(OTP_LOG_ERR, "auth: failed to generate state");
492 return RLM_MODULE_FAIL;
494 if (memcmp(state, vp->vp_strvalue, vp->length)) {
495 otp_log(OTP_LOG_AUTH,
496 "auth: bad state for [%s]: hmac", username);
498 return RLM_MODULE_REJECT;
502 /* State is valid, but check expiry. */
504 if (time(NULL) - then > inst->chal_delay) {
505 otp_log(OTP_LOG_AUTH,
506 "auth: bad state for [%s]: expired", username);
507 return RLM_MODULE_REJECT;
509 resync = ntohl(sflags) & 1;
510 } /* if (State should have been protected) */
511 } /* if (State present) */
515 rc = otprc2rlmrc(otp_pw_valid(username, challenge, NULL, resync, inst,
516 otp_pwe_cmp, &data, "auth"));
518 /* Handle any vps returned from otp_pwe_cmp(). */
519 if (rc == RLM_MODULE_OK) {
520 pairadd(&request->reply->vps, add_vps);
528 /* per-instance destruction */
530 otp_detach(void *instance)
532 otp_option_t *inst = (otp_option_t *) instance;
536 free(inst->chal_prompt);
537 free(inst->chal_req);
538 free(inst->resync_req);
541 * Only the main thread instantiates and detaches instances,
542 * so this does not need mutex protection.
544 if (--ninstance == 0)
545 memset(hmac_key, 0, sizeof(hmac_key));
552 * If the module needs to temporarily modify it's instantiation
553 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
554 * The server will then take care of ensuring that the module
555 * is single-threaded.
560 RLM_TYPE_THREAD_SAFE, /* type */
561 otp_instantiate, /* instantiation */
563 otp_authenticate, /* authentication */
564 otp_authorize, /* authorization */
565 NULL, /* preaccounting */
566 NULL, /* accounting */
567 NULL, /* checksimul */
568 NULL, /* pre-proxy */
569 NULL, /* post-proxy */
572 otp_detach, /* detach */