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 static const char rcsid[] = "$Id$";
35 #include <sys/types.h>
37 #include <netinet/in.h> /* htonl(), ntohl() */
40 static unsigned char hmac_key[16]; /* to protect State attribute */
41 static int ninstance = 0; /* #instances, for global init */
43 /* A mapping of configuration file names to internal variables. */
44 static const CONF_PARSER module_config[] = {
45 { "otpd_rp", PW_TYPE_STRING_PTR, offsetof(otp_option_t, otpd_rp),
47 { "challenge_prompt", PW_TYPE_STRING_PTR,offsetof(otp_option_t, chal_prompt),
48 NULL, OTP_CHALLENGE_PROMPT },
49 { "challenge_length", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_len),
51 { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
53 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
55 { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
58 { "mschapv2_mppe", PW_TYPE_INTEGER,
59 offsetof(otp_option_t, mschapv2_mppe_policy), NULL, "2" },
60 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
61 offsetof(otp_option_t, mschapv2_mppe_types), NULL, "2" },
62 { "mschap_mppe", PW_TYPE_INTEGER,
63 offsetof(otp_option_t, mschap_mppe_policy), NULL, "2" },
64 { "mschap_mppe_bits", PW_TYPE_INTEGER,
65 offsetof(otp_option_t, mschap_mppe_types), NULL, "2" },
67 { NULL, -1, 0, NULL, NULL } /* end the list */
71 /* per-instance initialization */
73 otp_instantiate(CONF_SECTION *conf, void **instance)
78 /* Set up a storage area for instance data. */
79 opt = rad_malloc(sizeof(*opt));
80 (void) memset(opt, 0, sizeof(*opt));
82 /* If the configuration parameters can't be parsed, then fail. */
83 if (cf_section_parse(conf, opt, module_config) < 0) {
88 /* Onetime initialization. */
90 /* Generate a random key, used to protect the State attribute. */
91 otp_get_random(hmac_key, sizeof(hmac_key));
93 /* Initialize the passcode encoding/checking functions. */
97 * Don't do this again.
98 * Only the main thread instantiates and detaches instances,
99 * so this does not need mutex protection.
104 /* Verify ranges for those vars that are limited. */
105 if ((opt->challenge_len < 5) ||
106 (opt->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
107 opt->challenge_len = 6;
108 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_length, range 5-%d, "
109 "using default of 6",
110 __func__, OTP_MAX_CHALLENGE_LEN);
113 /* Enforce a single "%" sequence, which must be "%s" */
114 p = strchr(opt->chal_prompt, '%');
115 if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
117 free(opt->chal_prompt);
118 opt->chal_prompt = strdup(OTP_CHALLENGE_PROMPT);
119 (void) radlog(L_ERR, "rlm_otp: %s: invalid challenge_prompt, "
120 "using default of \"%s\"",
121 __func__, OTP_CHALLENGE_PROMPT);
124 if (!opt->allow_sync && !opt->allow_async) {
125 (void) radlog(L_ERR, "rlm_otp: %s: at least one of "
126 "{allow_async, allow_sync} must be set",
132 if ((opt->mschapv2_mppe_policy > 2) || (opt->mschapv2_mppe_policy < 0)) {
133 opt->mschapv2_mppe_policy = 2;
134 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe, "
135 "using default of 2",
139 if ((opt->mschapv2_mppe_types > 2) || (opt->mschapv2_mppe_types < 0)) {
140 opt->mschapv2_mppe_types = 2;
141 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschapv2_mppe_bits, "
142 "using default of 2",
146 if ((opt->mschap_mppe_policy > 2) || (opt->mschap_mppe_policy < 0)) {
147 opt->mschap_mppe_policy = 2;
148 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe, "
149 "using default of 2",
153 if (opt->mschap_mppe_types != 2) {
154 opt->mschap_mppe_types = 2;
155 (void) radlog(L_ERR, "rlm_otp: %s: invalid value for mschap_mppe_bits, "
156 "using default of 2",
160 /* set the instance name (for use with authorize()) */
161 opt->name = cf_section_name2(conf);
163 opt->name = cf_section_name1(conf);
165 (void) radlog(L_ERR|L_CONS,
166 "rlm_otp: %s: no instance name (this can't happen)",
177 /* Generate a challenge to be presented to the user. */
179 otp_authorize(void *instance, REQUEST *request)
181 otp_option_t *inst = (otp_option_t *) instance;
183 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
187 /* Early exit if Auth-Type != inst->name */
192 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
194 if (strcmp(vp->strvalue, inst->name))
195 return RLM_MODULE_NOOP;
199 /* The State attribute will be present if this is a response. */
200 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
201 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
202 return RLM_MODULE_OK;
205 /* User-Name attribute required. */
206 if (!request->username) {
207 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
208 "for authentication.",
210 return RLM_MODULE_INVALID;
213 if ((pwe = otp_pwe_present(request)) == 0) {
214 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
215 "or equivalent required for authentication.",
217 return RLM_MODULE_INVALID;
221 * We used to check for special "challenge" and "resync" passcodes
222 * here, but these are complicated to explain and application is
223 * limited. More importantly, since we've removed all actual OTP
224 * code (now we ask otpd), it's awkward for us to support them.
225 * Should the need arise to reinstate these options, the most likely
226 * choice is to duplicate some otpd code here.
229 if (inst->allow_sync && !inst->allow_async) {
230 /* This is the token sync response. */
231 if (!auth_type_found)
232 pairadd(&request->config_items,
233 pairmake("Auth-Type", inst->name, T_OP_EQ));
234 return RLM_MODULE_OK;
237 /* Generate a random challenge. */
238 otp_async_challenge(challenge, inst->challenge_len);
241 * Create the State attribute, which will be returned to us along with
242 * the response. We will need this to verify the response. It must
243 * be hmac protected to prevent insertion of arbitrary State by an
244 * inside attacker. If we won't actually use the State (server config
245 * doesn't allow async), we just use a trivial State. We always create
246 * at least a trivial State, so otp_authorize() can quickly pass on to
247 * otp_authenticate().
250 int32_t now = htonl(time(NULL)); /* low-order 32 bits on LP64 */
251 char state[OTP_MAX_RADSTATE_LEN];
253 if (otp_gen_state(state, NULL, challenge, inst->challenge_len, 0,
254 now, hmac_key) != 0) {
255 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",__func__);
256 return RLM_MODULE_FAIL;
258 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
261 /* Add the challenge to the reply. */
263 char *u_challenge; /* challenge with addt'l presentation text */
265 u_challenge = rad_malloc(strlen(inst->chal_prompt) +
266 OTP_MAX_CHALLENGE_LEN + 1);
267 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
268 pairadd(&request->reply->vps,
269 pairmake("Reply-Message", u_challenge, T_OP_EQ));
274 * Mark the packet as an Access-Challenge packet.
275 * The server will take care of sending it to the user.
277 request->reply->code = PW_ACCESS_CHALLENGE;
278 DEBUG("rlm_otp: Sending Access-Challenge.");
280 if (!auth_type_found)
281 pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
282 return RLM_MODULE_HANDLED;
286 /* Verify the response entered by the user. */
288 otp_authenticate(void *instance, REQUEST *request)
290 otp_option_t *inst = (otp_option_t *) instance;
296 unsigned char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
297 char passcode[OTP_MAX_PASSCODE_LEN + 1];
299 challenge[0] = '\0'; /* initialize for otp_pw_valid() */
301 /* User-Name attribute required. */
302 if (!request->username) {
303 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
304 "for authentication.",
306 return RLM_MODULE_INVALID;
308 username = request->username->strvalue;
310 if ((pwe = otp_pwe_present(request)) == 0) {
311 (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Password\" "
312 "or equivalent required for authentication.",
314 return RLM_MODULE_INVALID;
317 /* Add a message to the auth log. */
318 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
319 "rlm_otp", T_OP_EQ));
320 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
321 "rlm_otp", T_OP_EQ));
323 /* Retrieve the challenge (from State attribute). */
324 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
325 unsigned char state[OTP_MAX_RADSTATE_LEN];
326 unsigned char raw_state[OTP_MAX_RADSTATE_LEN];
327 unsigned char rad_state[OTP_MAX_RADSTATE_LEN];
328 int32_t then; /* state timestamp */
329 int e_length; /* expected State length */
331 /* set expected State length */
332 e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
334 if (vp->length != e_length) {
335 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: length",
337 return RLM_MODULE_INVALID;
344 /* ASCII decode; this is why OTP_MAX_RADSTATE_LEN has +1 */
345 (void) memcpy(rad_state, vp->strvalue, vp->length);
346 rad_state[e_length] = '\0';
347 if (otp_a2x(rad_state, raw_state) == -1) {
348 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: not hex",
350 return RLM_MODULE_INVALID;
353 /* extract data from State */
354 (void) memcpy(challenge, raw_state, inst->challenge_len);
356 (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
358 /* generate new state from returned input data */
359 if (otp_gen_state(NULL, state, challenge, inst->challenge_len, 0,
360 then, hmac_key) != 0) {
361 (void) radlog(L_ERR, "rlm_otp: %s: failed to generate radstate",
363 return RLM_MODULE_FAIL;
365 /* compare generated state against returned state to verify hmac */
366 if (memcmp(state, vp->strvalue, vp->length)) {
367 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: hmac",
369 return RLM_MODULE_REJECT;
372 /* State is valid, but check expiry. */
374 if (time(NULL) - then > inst->challenge_delay) {
375 (void) radlog(L_AUTH, "rlm_otp: %s: bad radstate for [%s]: expired",
377 return RLM_MODULE_REJECT;
379 } /* if (State present) */
382 rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
384 /* Add MPPE data as needed. */
385 if (rc == RLM_MODULE_OK)
386 otp_mppe(request, pwe, inst, passcode);
392 /* per-instance destruction */
394 otp_detach(void *instance)
396 otp_option_t *inst = (otp_option_t *) instance;
399 free(inst->chal_prompt);
402 * Only the main thread instantiates and detaches instances,
403 * so this does not need mutex protection.
405 if (--ninstance == 0)
406 (void) memset(hmac_key, 0, sizeof(hmac_key));
413 * If the module needs to temporarily modify it's instantiation
414 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
415 * The server will then take care of ensuring that the module
416 * is single-threaded.
420 RLM_TYPE_THREAD_SAFE, /* type */
422 otp_instantiate, /* instantiation */
424 otp_authenticate, /* authentication */
425 otp_authorize, /* authorization */
426 NULL, /* preaccounting */
427 NULL, /* accounting */
428 NULL, /* checksimul */
429 NULL, /* pre-proxy */
430 NULL, /* post-proxy */
433 otp_detach, /* detach */