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.
36 #include <sys/types.h>
38 #include <netinet/in.h> /* htonl(), ntohl() */
41 static unsigned char hmac_key[16]; /* to protect State attribute */
42 static int ninstance = 0; /* #instances, for global init */
44 /* A mapping of configuration file names to internal variables. */
45 static const CONF_PARSER module_config[] = {
46 { "otpd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, otpd_rp),
48 { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
49 NULL, OTP_CHALLENGE_PROMPT },
50 { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_len),
52 { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
54 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
56 { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
59 { "mschapv2_mppe", PW_TYPE_INTEGER,
60 offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
61 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
62 offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
63 { "mschap_mppe", PW_TYPE_INTEGER,
64 offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
65 { "mschap_mppe_bits", PW_TYPE_INTEGER,
66 offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
68 { NULL, -1, 0, NULL, NULL } /* end the list */
72 /* per-instance initialization */
74 otp_instantiate(CONF_SECTION *conf, void **instance)
79 /* Set up a storage area for instance data. */
80 opt = rad_malloc(sizeof(*opt));
81 (void) memset(opt, 0, sizeof(*opt));
83 /* If the configuration parameters can't be parsed, then fail. */
84 if (cf_section_parse(conf, opt, module_config) < 0) {
89 /* Onetime initialization. */
91 /* Generate a random key, used to protect the State attribute. */
92 otp_get_random(hmac_key, sizeof(hmac_key));
94 /* Initialize the passcode encoding/checking functions. */
98 * Don't do this again.
99 * Only the main thread instantiates and detaches instances,
100 * so this does not need mutex protection.
105 /* Verify ranges for those vars that are limited. */
106 if ((opt->challenge_len < 5) ||
107 (opt->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
108 opt->challenge_len = 6;
109 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_length, range 5-%d, "
110 "using default of 6",
111 __func__, OTP_MAX_CHALLENGE_LEN);
114 /* Enforce a single "%" sequence, which must be "%s" */
115 p = strchr(opt->chal_prompt, '%');
116 if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
118 free(opt->chal_prompt);
119 opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
120 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_prompt, "
121 "using default of \"%s\"",
122 __func__, OTP_CHALLENGE_PROMPT);
125 if (!opt->allow_sync && !opt->allow_async) {
126 (void) radlog(L_ERR, "rlm_otp: %s: at least one of "
127 "{allow_async, allow_sync} must be set",
133 if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
134 opt->mschapv2_mppe_policy = 2;
135 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe, "
136 "using default of 2",
140 if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
141 opt->mschapv2_mppe_types = 2;
142 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe_bits, "
143 "using default of 2",
147 if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
148 opt->mschap_mppe_policy = 2;
149 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe, "
150 "using default of 2",
154 if (opt->mschap_mppe_types != 2) {
155 opt->mschap_mppe_types = 2;
156 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe_bits, "
157 "using default of 2",
161 /* set the instance name (for use with authorize()) */
162 opt->name = cf_section_name2(conf);
164 opt->name = cf_section_name1(conf);
166 (void) radlog(L_ERR|L_CONS,
167 "rlm_otp: %s: no instance name (this can't happen)",
178 /* Generate a challenge to be presented to the user. */
180 otp_authorize(void *instance, REQUEST *request)
182 otp_option_t *inst = (otp_option_t *) instance;
184 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
188 /* Early exit if Auth-Type != inst->name */
193 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
195 if (strcmp(vp->strvalue, inst->name))
196 return RLM_MODULE_NOOP;
200 /* The State attribute will be present if this is a response. */
201 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
202 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
203 return RLM_MODULE_OK;
206 /* User-Name attribute required. */
207 if (!request->username) {
208 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
209 "for authentication.",
211 return RLM_MODULE_INVALID;
214 if ((pwe = otp_pwe_present(request)) == 0) {
215 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
216 "or equivalent required for authentication.",
218 return RLM_MODULE_INVALID;
222 * We used to check for special "challenge" and "resync" passcodes
223 * here, but these are complicated to explain and application is
224 * limited. More importantly, since we've removed all actual OTP
225 * code (now we ask otpd), it's awkward for us to support them.
226 * Should the need arise to reinstate these options, the most likely
227 * choice is to duplicate some otpd code here.
230 if (inst->allow_sync && !inst->allow_async) {
231 /* This is the token sync response. */
232 if (!auth_type_found)
233 pairadd(&request->config_items,
234 pairmake("Auth-Type", inst->name, T_OP_EQ));
235 return RLM_MODULE_OK;
238 /* Generate a random challenge. */
239 otp_async_challenge(challenge, inst->challenge_len);
242 * Create the State attribute, which will be returned to us along with
243 * the response. We will need this to verify the response. It must
244 * be hmac protected to prevent insertion of arbitrary State by an
245 * inside attacker. If we won't actually use the State (server config
246 * doesn't allow async), we just use a trivial State. We always create
247 * at least a trivial State, so otp_authorize() can quickly pass on to
248 * otp_authenticate().
251 int32_t now = htonl(time(NULL)); /* low-order 32 bits on LP64 */
252 char state[OTP_MAX_RADSTATE_LEN];
254 if (otp_gen_state(state, NULL, challenge, inst->challenge_len, 0,
255 now, hmac_key) != 0) {
256 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",__func__);
257 return RLM_MODULE_FAIL;
259 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
262 /* Add the challenge to the reply. */
264 char *u_challenge; /* challenge with addt'l presentation text */
266 u_challenge = rad_malloc(strlen(inst->chal_prompt) +
267 OTP_MAX_CHALLENGE_LEN + 1);
268 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
269 pairadd(&request->reply->vps,
270 pairmake("Reply-Message", u_challenge, T_OP_EQ));
275 * Mark the packet as an Access-Challenge packet.
276 * The server will take care of sending it to the user.
278 request->reply->code = PW_ACCESS_CHALLENGE;
279 DEBUG("rlm_otp: Sending Access-Challenge.");
281 if (!auth_type_found)
282 pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
283 return RLM_MODULE_HANDLED;
287 /* Verify the response entered by the user. */
289 otp_authenticate(void *instance, REQUEST *request)
291 otp_option_t *inst = (otp_option_t *) instance;
297 unsigned char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
298 char passcode[OTP_MAX_PASSCODE_LEN + 1];
300 challenge[0] = '\0'; /* initialize for otp_pw_valid() */
302 /* User-Name attribute required. */
303 if (!request->username) {
304 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
305 "for authentication.",
307 return RLM_MODULE_INVALID;
309 username = request->username->strvalue;
311 if ((pwe = otp_pwe_present(request)) == 0) {
312 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
313 "or equivalent required for authentication.",
315 return RLM_MODULE_INVALID;
318 /* Add a message to the auth log. */
319 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
320 "rlm_otp", T_OP_EQ));
321 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
322 "rlm_otp", T_OP_EQ));
324 /* Retrieve the challenge (from State attribute). */
325 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
326 unsigned char state[OTP_MAX_RADSTATE_LEN];
327 unsigned char raw_state[OTP_MAX_RADSTATE_LEN];
328 unsigned char rad_state[OTP_MAX_RADSTATE_LEN];
329 int32_t then; /* state timestamp */
330 int e_length; /* expected State length */
332 /* set expected State length */
333 e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
335 if (vp->length != e_length) {
336 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: length",
338 return RLM_MODULE_INVALID;
345 /* ASCII decode; this is why OTP_MAX_RADSTATE_LEN has +1 */
346 (void) memcpy(rad_state, vp->strvalue, vp->length);
347 rad_state[e_length] = '\0';
348 if (otp_a2x(rad_state, raw_state) == -1) {
349 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: not hex",
351 return RLM_MODULE_INVALID;
354 /* extract data from State */
355 (void) memcpy(challenge, raw_state, inst->challenge_len);
357 (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
359 /* generate new state from returned input data */
360 if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
361 then, hmac_key) != 0) {
362 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",
364 return RLM_MODULE_FAIL;
366 /* compare generated state against returned state to verify hmac */
367 if (memcmp(state, vp->strvalue, vp->length)) {
368 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: hmac",
370 return RLM_MODULE_REJECT;
373 /* State is valid, but check expiry. */
375 if (time(NULL) - then > inst->challenge_delay) {
376 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: expired",
378 return RLM_MODULE_REJECT;
380 } /* if (State present) */
383 rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
385 /* Add MPPE data as needed. */
386 if (rc == RLM_MODULE_OK)
387 otp_mppe(request, pwe, inst, passcode);
393 /* per-instance destruction */
395 otp_detach(void *instance)
397 otp_option_t *inst = (otp_option_t *) instance;
400 free(inst->chal_prompt);
403 * Only the main thread instantiates and detaches instances,
404 * so this does not need mutex protection.
406 if (--ninstance == 0)
407 (void) memset(hmac_key, 0, sizeof(hmac_key));
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.
421 RLM_TYPE_THREAD_SAFE, /* type */
423 otp_instantiate, /* instantiation */
425 otp_authenticate, /* authentication */
426 otp_authorize, /* authorization */
427 NULL, /* preaccounting */
428 NULL, /* accounting */
429 NULL, /* checksimul */
430 NULL, /* pre-proxy */
431 NULL, /* post-proxy */
434 otp_detach, /* detach */