2
* $Id: otp_rlm.c,v 1.19.2.10 2006/06/01 19:02:23 fcusack Exp $
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: otp_rlm.c,v 1.19.2.10 2006/06/01 19:02:23 fcusack Exp $";
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 state", __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 state 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 state 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 state",
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 state 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 state 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 */