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.
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.
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
18 * Copyright 2000,2001,2002 The FreeRADIUS server project
19 * Copyright 2001,2002 Google, Inc.
20 * Copyright 2005,2006 TRI-D Systems, Inc.
23 #include <freeradius-devel/ident.h>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
33 static unsigned char hmac_key[16]; /* to protect State attribute */
34 static int ninstance = 0; /* #instances, for global init */
36 /* A mapping of configuration file names to internal variables. */
37 static const CONF_PARSER module_config[] = {
38 { "otpd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, otpd_rp),
40 { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
41 NULL, OTP_CHALLENGE_PROMPT },
42 { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_len),
44 { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
46 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
48 { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
51 { "mschapv2_mppe", PW_TYPE_INTEGER,
52 offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
53 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
54 offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
55 { "mschap_mppe", PW_TYPE_INTEGER,
56 offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
57 { "mschap_mppe_bits", PW_TYPE_INTEGER,
58 offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
60 { NULL, -1, 0, NULL, NULL } /* end the list */
64 /* per-instance initialization */
66 otp_instantiate(CONF_SECTION *conf, void **instance)
71 /* Set up a storage area for instance data. */
72 opt = rad_malloc(sizeof(*opt));
73 (void) memset(opt, 0, sizeof(*opt));
75 /* If the configuration parameters can't be parsed, then fail. */
76 if (cf_section_parse(conf, opt, module_config) < 0) {
81 /* Onetime initialization. */
83 /* Generate a random key, used to protect the State attribute. */
84 otp_get_random(hmac_key, sizeof(hmac_key));
86 /* Initialize the passcode encoding/checking functions. */
90 * Don't do this again.
91 * Only the main thread instantiates and detaches instances,
92 * so this does not need mutex protection.
97 /* Verify ranges for those vars that are limited. */
98 if ((opt->challenge_len < 5) ||
99 (opt->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
100 opt->challenge_len = 6;
101 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_length, range 5-%d, "
102 "using default of 6",
103 __func__, OTP_MAX_CHALLENGE_LEN);
106 /* Enforce a single "%" sequence, which must be "%s" */
107 p = strchr(opt->chal_prompt, '%');
108 if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
110 free(opt->chal_prompt);
111 opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
112 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_prompt, "
113 "using default of \"%s\"",
114 __func__, OTP_CHALLENGE_PROMPT);
117 if (!opt->allow_sync && !opt->allow_async) {
118 (void) radlog(L_ERR, "rlm_otp: %s: at least one of "
119 "{allow_async, allow_sync} must be set",
125 if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
126 opt->mschapv2_mppe_policy = 2;
127 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe, "
128 "using default of 2",
132 if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
133 opt->mschapv2_mppe_types = 2;
134 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe_bits, "
135 "using default of 2",
139 if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
140 opt->mschap_mppe_policy = 2;
141 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe, "
142 "using default of 2",
146 if (opt->mschap_mppe_types != 2) {
147 opt->mschap_mppe_types = 2;
148 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe_bits, "
149 "using default of 2",
153 /* set the instance name (for use with authorize()) */
154 opt->name = cf_section_name2(conf);
156 opt->name = cf_section_name1(conf);
158 (void) radlog(L_ERR|L_CONS,
159 "rlm_otp: %s: no instance name (this can't happen)",
170 /* Generate a challenge to be presented to the user. */
172 otp_authorize(void *instance, REQUEST *request)
174 otp_option_t *inst = (otp_option_t *) instance;
176 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
180 /* Early exit if Auth-Type != inst->name */
185 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
187 if (strcmp(vp->vp_strvalue, inst->name))
188 return RLM_MODULE_NOOP;
192 /* The State attribute will be present if this is a response. */
193 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
194 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
195 return RLM_MODULE_OK;
198 /* User-Name attribute required. */
199 if (!request->username) {
200 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
201 "for authentication.",
203 return RLM_MODULE_INVALID;
206 if ((pwe = otp_pwe_present(request)) == 0) {
207 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
208 "or equivalent required for authentication.",
210 return RLM_MODULE_INVALID;
214 * We used to check for special "challenge" and "resync" passcodes
215 * here, but these are complicated to explain and application is
216 * limited. More importantly, since we've removed all actual OTP
217 * code (now we ask otpd), it's awkward for us to support them.
218 * Should the need arise to reinstate these options, the most likely
219 * choice is to duplicate some otpd code here.
222 if (inst->allow_sync && !inst->allow_async) {
223 /* This is the token sync response. */
224 if (!auth_type_found)
225 pairadd(&request->config_items,
226 pairmake("Auth-Type", inst->name, T_OP_EQ));
227 return RLM_MODULE_OK;
230 /* Generate a random challenge. */
231 otp_async_challenge(challenge, inst->challenge_len);
234 * Create the State attribute, which will be returned to us along with
235 * the response. We will need this to verify the response. It must
236 * be hmac protected to prevent insertion of arbitrary State by an
237 * inside attacker. If we won't actually use the State (server config
238 * doesn't allow async), we just use a trivial State. We always create
239 * at least a trivial State, so otp_authorize() can quickly pass on to
240 * otp_authenticate().
243 int32_t now = htonl(time(NULL)); /* low-order 32 bits on LP64 */
244 char state[OTP_MAX_RADSTATE_LEN];
246 if (otp_gen_state(state, NULL, challenge, inst->challenge_len, 0,
247 now, hmac_key) != 0) {
248 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",__func__);
249 return RLM_MODULE_FAIL;
251 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
254 /* Add the challenge to the reply. */
256 char *u_challenge; /* challenge with addt'l presentation text */
258 u_challenge = rad_malloc(strlen(inst->chal_prompt) +
259 OTP_MAX_CHALLENGE_LEN + 1);
260 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
261 pairadd(&request->reply->vps,
262 pairmake("Reply-Message", u_challenge, T_OP_EQ));
267 * Mark the packet as an Access-Challenge packet.
268 * The server will take care of sending it to the user.
270 request->reply->code = PW_ACCESS_CHALLENGE;
271 DEBUG("rlm_otp: Sending Access-Challenge.");
273 if (!auth_type_found)
274 pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
275 return RLM_MODULE_HANDLED;
279 /* Verify the response entered by the user. */
281 otp_authenticate(void *instance, REQUEST *request)
283 otp_option_t *inst = (otp_option_t *) instance;
289 unsigned char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
290 char passcode[OTP_MAX_PASSCODE_LEN + 1];
292 challenge[0] = '\0'; /* initialize for otp_pw_valid() */
294 /* User-Name attribute required. */
295 if (!request->username) {
296 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
297 "for authentication.",
299 return RLM_MODULE_INVALID;
301 username = request->username->vp_strvalue;
303 if ((pwe = otp_pwe_present(request)) == 0) {
304 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
305 "or equivalent required for authentication.",
307 return RLM_MODULE_INVALID;
310 /* Add a message to the auth log. */
311 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
312 "rlm_otp", T_OP_EQ));
313 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
314 "rlm_otp", T_OP_EQ));
316 /* Retrieve the challenge (from State attribute). */
317 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
318 unsigned char state[OTP_MAX_RADSTATE_LEN];
319 unsigned char raw_state[OTP_MAX_RADSTATE_LEN];
320 unsigned char rad_state[OTP_MAX_RADSTATE_LEN];
321 int32_t then; /* state timestamp */
322 int e_length; /* expected State length */
324 /* set expected State length */
325 e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
327 if (vp->length != e_length) {
328 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: length",
330 return RLM_MODULE_INVALID;
337 /* ASCII decode; this is why OTP_MAX_RADSTATE_LEN has +1 */
338 (void) memcpy(rad_state, vp->vp_strvalue, vp->length);
339 rad_state[e_length] = '\0';
340 if (otp_a2x(rad_state, raw_state) == -1) {
341 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: not hex",
343 return RLM_MODULE_INVALID;
346 /* extract data from State */
347 (void) memcpy(challenge, raw_state, inst->challenge_len);
349 (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
351 /* generate new state from returned input data */
352 if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
353 then, hmac_key) != 0) {
354 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",
356 return RLM_MODULE_FAIL;
358 /* compare generated state against returned state to verify hmac */
359 if (memcmp(state, vp->vp_strvalue, vp->length)) {
360 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: hmac",
362 return RLM_MODULE_REJECT;
365 /* State is valid, but check expiry. */
367 if (time(NULL) - then > inst->challenge_delay) {
368 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: expired",
370 return RLM_MODULE_REJECT;
372 } /* if (State present) */
375 rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
377 /* Add MPPE data as needed. */
378 if (rc == RLM_MODULE_OK)
379 otp_mppe(request, pwe, inst, passcode);
385 /* per-instance destruction */
387 otp_detach(void *instance)
391 * Only the main thread instantiates and detaches instances,
392 * so this does not need mutex protection.
394 if (--ninstance == 0)
395 (void) memset(hmac_key, 0, sizeof(hmac_key));
402 * If the module needs to temporarily modify it's instantiation
403 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
404 * The server will then take care of ensuring that the module
405 * is single-threaded.
410 RLM_TYPE_THREAD_SAFE, /* type */
411 otp_instantiate, /* instantiation */
412 otp_detach, /* detach */
414 otp_authenticate, /* authentication */
415 otp_authorize, /* authorization */
416 NULL, /* preaccounting */
417 NULL, /* accounting */
418 NULL, /* checksimul */
419 NULL, /* pre-proxy */
420 NULL, /* post-proxy */