2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief One time password implementation.
21 * @copyright 2013 Network RADIUS SARL
22 * @copyright 2000,2001,2002,2013 The FreeRADIUS server project
23 * @copyright 2005-2007 TRI-D Systems, Inc.
24 * @copyright 2001,2002 Google, Inc.
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
35 static int ninstance = 0; //!< Number of instances, for global init.
37 /* A mapping of configuration file names to internal variables. */
38 static const CONF_PARSER module_config[] = {
39 { "otpd_rp", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_otp_t, otpd_rp), OTP_OTPD_RP },
40 { "challenge_prompt", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_otp_t, chal_prompt), OTP_CHALLENGE_PROMPT },
41 { "challenge_length", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, challenge_len), "6" },
42 { "challenge_delay", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, challenge_delay), "30" },
43 { "allow_sync", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_otp_t, allow_sync), "yes" },
44 { "allow_async", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_otp_t, allow_async), "no" },
46 { "mschapv2_mppe", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, mschapv2_mppe_policy), "2" },
47 { "mschapv2_mppe_bits", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, mschapv2_mppe_types), "2" },
48 { "mschap_mppe", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, mschap_mppe_policy), "2" },
49 { "mschap_mppe_bits", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_otp_t, mschap_mppe_types), "2" },
51 { NULL, -1, 0, NULL, NULL } /* end the list */
56 * Per-instance initialization
58 static int mod_instantiate(CONF_SECTION *conf, void *instance)
60 rlm_otp_t *inst = instance;
62 /* Onetime initialization. */
64 /* Generate a random key, used to protect the State attribute. */
65 otp_get_random(inst->hmac_key, sizeof(inst->hmac_key));
67 /* Initialize the passcode encoding/checking functions. */
71 * Don't do this again.
72 * Only the main thread instantiates and detaches instances,
73 * so this does not need mutex protection.
78 /* Verify ranges for those vars that are limited. */
79 if ((inst->challenge_len < 5) ||
80 (inst->challenge_len > OTP_MAX_CHALLENGE_LEN)) {
81 inst->challenge_len = 6;
83 WARN("invalid challenge_length %d, "
84 "range 5-%d, using default of 6",
85 inst->challenge_len, OTP_MAX_CHALLENGE_LEN);
88 if (!inst->allow_sync && !inst->allow_async) {
89 cf_log_err_cs(conf, "at least one of {allow_async, "
90 "allow_sync} must be set");
94 if (inst->mschapv2_mppe_policy > 2) {
95 inst->mschapv2_mppe_policy = 2;
96 WARN("Invalid value for mschapv2_mppe, using default of 2");
99 if (inst->mschapv2_mppe_types > 2) {
100 inst->mschapv2_mppe_types = 2;
101 WARN("Invalid value for mschapv2_mppe_bits, using default of 2");
104 if (inst->mschap_mppe_policy > 2) {
105 inst->mschap_mppe_policy = 2;
106 WARN("Invalid value for mschap_mppe, using default of 2");
109 if (inst->mschap_mppe_types != 2) {
110 inst->mschap_mppe_types = 2;
111 WARN("Invalid value for "
112 "mschap_mppe_bits, using default of 2");
115 /* set the instance name (for use with authorize()) */
116 inst->name = cf_section_name2(conf);
117 if (!inst->name) inst->name = cf_section_name1(conf);
123 * Generate a challenge to be presented to the user.
125 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
127 rlm_otp_t *inst = (rlm_otp_t *) instance;
129 char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
132 /* Early exit if Auth-Type != inst->name */
137 vp = pairfind(request->config_items, PW_AUTHTYPE, 0, TAG_ANY);
140 if (strcmp(vp->vp_strvalue, inst->name)) {
141 return RLM_MODULE_NOOP;
146 /* The State attribute will be present if this is a response. */
147 if (pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY) != NULL) {
148 DEBUG("rlm_otp: autz: Found response to Access-Challenge");
150 return RLM_MODULE_OK;
153 /* User-Name attribute required. */
154 if (!request->username) {
155 RWDEBUG("Attribute \"User-Name\" "
156 "required for authentication");
158 return RLM_MODULE_INVALID;
161 if (otp_pwe_present(request) == 0) {
163 "\"User-Password\" or equivalent required "
164 "for authentication");
166 return RLM_MODULE_INVALID;
170 * We used to check for special "challenge" and "resync" passcodes
171 * here, but these are complicated to explain and application is
172 * limited. More importantly, since we've removed all actual OTP
173 * code (now we ask otpd), it's awkward for us to support them.
174 * Should the need arise to reinstate these options, the most
175 * likely choice is to duplicate some otpd code here.
177 if (inst->allow_sync && !inst->allow_async) {
178 /* This is the token sync response. */
179 if (!auth_type_found) {
180 pairmake_config("Auth-Type", inst->name, T_OP_EQ);
183 return RLM_MODULE_OK;
187 * Generate a random challenge.
189 otp_async_challenge(challenge, inst->challenge_len);
192 * Create the State attribute, which will be returned to
193 * us along with the response.
195 * We will need this to verify the response.
197 * It must be hmac protected to prevent insertion of arbitrary
198 * State by an inside attacker.
200 * If we won't actually use the State (server config doesn't
201 * allow async), we just use a trivial State.
203 * We always create at least a trivial State, so mod_authorize()
204 * can quickly pass on to mod_authenticate().
207 int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64.
209 char gen_state[OTP_MAX_RADSTATE_LEN];
213 len = otp_gen_state(gen_state, challenge, inst->challenge_len,
214 0, now, inst->hmac_key);
216 vp = paircreate(request->reply, PW_STATE, 0);
218 return RLM_MODULE_FAIL;
221 pairmemcpy(vp, (uint8_t const *) gen_state, len);
222 pairadd(&request->reply->vps, vp);
226 * Add the challenge to the reply.
231 char *expanded = NULL;
235 * First add the internal OTP challenge attribute to
238 vp = paircreate(request->reply, PW_OTP_CHALLENGE, 0);
240 return RLM_MODULE_FAIL;
243 pairstrcpy(vp, challenge);
246 pairadd(&request->reply->vps, vp);
249 * Then add the message to the user to they known
250 * what the challenge value is.
253 len = radius_axlat(&expanded, request, inst->chal_prompt, NULL, NULL);
255 return RLM_MODULE_FAIL;
258 vp = paircreate(request->reply, PW_REPLY_MESSAGE, 0);
260 talloc_free(expanded);
261 return RLM_MODULE_FAIL;
264 (void) talloc_steal(vp, expanded);
265 vp->vp_strvalue = expanded;
270 pairadd(&request->reply->vps, vp);
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_CODE_ACCESS_CHALLENGE;
279 DEBUG("rlm_otp: Sending Access-Challenge");
281 if (!auth_type_found) {
282 pairmake_config("Auth-Type", inst->name, T_OP_EQ);
285 return RLM_MODULE_HANDLED;
290 * Verify the response entered by the user.
292 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
294 rlm_otp_t *inst = instance;
296 char const *username;
301 char challenge[OTP_MAX_CHALLENGE_LEN]; /* cf. authorize() */
302 char passcode[OTP_MAX_PASSCODE_LEN + 1];
304 challenge[0] = '\0'; /* initialize for otp_pw_valid() */
306 /* User-Name attribute required. */
307 if (!request->username) {
308 RWDEBUG("Attribute \"User-Name\" required "
309 "for authentication");
311 return RLM_MODULE_INVALID;
314 username = request->username->vp_strvalue;
316 pwe = otp_pwe_present(request);
318 RWDEBUG("Attribute \"User-Password\" "
319 "or equivalent required for authentication");
321 return RLM_MODULE_INVALID;
325 * Retrieve the challenge (from State attribute).
327 vp = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
329 char gen_state[OTP_MAX_RADSTATE_LEN]; //!< State as hexits
330 uint8_t bin_state[OTP_MAX_RADSTATE_LEN];
332 int32_t then; //!< State timestamp.
333 size_t elen; //!< Expected State length.
337 * Set expected State length (see otp_gen_state())
339 elen = (inst->challenge_len * 2) + 8 + 8 + 32;
341 if (vp->length != elen) {
342 REDEBUG("Bad radstate for [%s]: length", username);
343 return RLM_MODULE_INVALID;
351 * Convert vp state (ASCII encoded hexits in opaque bin
352 * string) back to binary.
354 * There are notes in otp_radstate as to why the state
355 * value is encoded as hexits.
357 len = fr_hex2bin(bin_state, vp->vp_strvalue, vp->length);
358 if (len != (vp->length / 2)) {
359 REDEBUG("bad radstate for [%s]: not hex", username);
361 return RLM_MODULE_INVALID;
365 * Extract data from State
367 memcpy(challenge, bin_state, inst->challenge_len);
372 memcpy(&then, bin_state + inst->challenge_len + 4, 4);
375 * Generate new state from returned input data
377 otp_gen_state(gen_state, challenge, inst->challenge_len, 0,
378 then, inst->hmac_key);
381 * Compare generated state (in hex form)
382 * against generated state (in hex form)
385 if (memcmp(gen_state, vp->vp_octets, vp->length)) {
386 REDEBUG("bad radstate for [%s]: hmac", username);
388 return RLM_MODULE_REJECT;
392 * State is valid, but check expiry.
395 if ((time(NULL) - then) > (int)inst->challenge_delay) {
396 REDEBUG("bad radstate for [%s]: expired",username);
398 return RLM_MODULE_REJECT;
403 rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
405 /* Add MPPE data as needed. */
406 if (rc == RLM_MODULE_OK) {
407 otp_mppe(request, pwe, inst, passcode);
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.
422 RLM_TYPE_THREAD_SAFE, /* type */
425 mod_instantiate, /* instantiation */
428 mod_authenticate, /* authentication */
429 mod_authorize, /* authorization */
430 NULL, /* preaccounting */
431 NULL, /* accounting */
432 NULL, /* checksimul */
433 NULL, /* pre-proxy */
434 NULL, /* post-proxy */