~ubuntu-branches/ubuntu/hardy/freeradius/hardy-proposed

« back to all changes in this revision

Viewing changes to src/modules/rlm_otp/otp_rlm.c

  • Committer: Bazaar Package Importer
  • Author(s): Mark Hymers
  • Date: 2006-12-16 20:45:11 UTC
  • mfrom: (3.1.10 feisty)
  • Revision ID: james.westby@ubuntu.com-20061216204511-3pbbsu4s8jtehsor
Tags: 1.1.3-3
Fix POSIX compliance problem in init script.  Closes: #403384. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id: otp_rlm.c,v 1.19.2.10 2006/06/01 19:02:23 fcusack Exp $
 
3
 *
 
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.
 
8
 *
 
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.
 
13
 *
 
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
 
17
 *
 
18
 * Copyright 2000,2001,2002  The FreeRADIUS server project
 
19
 * Copyright 2001,2002  Google, Inc.
 
20
 * Copyright 2005,2006 TRI-D Systems, Inc.
 
21
 */
 
22
 
 
23
static const char rcsid[] = "$Id: otp_rlm.c,v 1.19.2.10 2006/06/01 19:02:23 fcusack Exp $";
 
24
 
 
25
#include <autoconf.h>
 
26
#include <radiusd.h>
 
27
#include <modules.h>
 
28
 
 
29
#include "extern.h"
 
30
#include "otp.h"
 
31
 
 
32
#include <stdio.h>
 
33
#include <stdlib.h>
 
34
#include <string.h>
 
35
#include <sys/types.h>
 
36
#include <time.h>
 
37
#include <netinet/in.h> /* htonl(), ntohl() */
 
38
 
 
39
/* Global data */
 
40
static unsigned char hmac_key[16];      /* to protect State attribute  */
 
41
static int ninstance = 0;               /* #instances, for global init */
 
42
 
 
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),
 
46
    NULL, OTP_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),
 
50
    NULL, "6" },
 
51
  { "challenge_delay", PW_TYPE_INTEGER, offsetof(otp_option_t, challenge_delay),
 
52
    NULL, "30" },
 
53
  { "allow_sync", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_sync),
 
54
    NULL, "yes" },
 
55
  { "allow_async", PW_TYPE_BOOLEAN, offsetof(otp_option_t, allow_async),
 
56
    NULL, "no" },
 
57
 
 
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" },
 
66
 
 
67
  { NULL, -1, 0, NULL, NULL }           /* end the list */
 
68
};
 
69
 
 
70
 
 
71
/* per-instance initialization */
 
72
static int
 
73
otp_instantiate(CONF_SECTION *conf, void **instance)
 
74
{
 
75
  otp_option_t *opt;
 
76
  char *p;
 
77
 
 
78
  /* Set up a storage area for instance data. */
 
79
  opt = rad_malloc(sizeof(*opt));
 
80
  (void) memset(opt, 0, sizeof(*opt));
 
81
 
 
82
  /* If the configuration parameters can't be parsed, then fail. */
 
83
  if (cf_section_parse(conf, opt, module_config) < 0) {
 
84
    free(opt);
 
85
    return -1;
 
86
  }
 
87
 
 
88
  /* Onetime initialization. */
 
89
  if (!ninstance) {
 
90
    /* Generate a random key, used to protect the State attribute. */
 
91
    otp_get_random(hmac_key, sizeof(hmac_key));
 
92
 
 
93
    /* Initialize the passcode encoding/checking functions. */
 
94
    otp_pwe_init();
 
95
 
 
96
    /*
 
97
     * Don't do this again.
 
98
     * Only the main thread instantiates and detaches instances,
 
99
     * so this does not need mutex protection.
 
100
     */
 
101
    ninstance++;
 
102
  }
 
103
 
 
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);
 
111
  }
 
112
 
 
113
  /* Enforce a single "%" sequence, which must be "%s" */
 
114
  p = strchr(opt->chal_prompt, '%');
 
115
  if ((p == NULL) || (p != strrchr(opt->chal_prompt, '%')) ||
 
116
      strncmp(p,"%s",2)) {
 
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);
 
122
  }
 
123
 
 
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",
 
127
                  __func__);
 
128
    free(opt);
 
129
    return -1;
 
130
  }
 
131
 
 
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",
 
136
                  __func__);
 
137
  }
 
138
 
 
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",
 
143
                  __func__);
 
144
  }
 
145
 
 
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",
 
150
                  __func__);
 
151
  }
 
152
 
 
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",
 
157
                  __func__);
 
158
  }
 
159
 
 
160
  /* set the instance name (for use with authorize()) */
 
161
  opt->name = cf_section_name2(conf);
 
162
  if (!opt->name)
 
163
    opt->name = cf_section_name1(conf);
 
164
  if (!opt->name) {
 
165
    (void) radlog(L_ERR|L_CONS,
 
166
                  "rlm_otp: %s: no instance name (this can't happen)",
 
167
                  __func__);
 
168
    free(opt);
 
169
    return -1;
 
170
  }
 
171
 
 
172
  *instance = opt;
 
173
  return 0;
 
174
}
 
175
 
 
176
 
 
177
/* Generate a challenge to be presented to the user. */
 
178
static int
 
179
otp_authorize(void *instance, REQUEST *request)
 
180
{
 
181
  otp_option_t *inst = (otp_option_t *) instance;
 
182
 
 
183
  char challenge[OTP_MAX_CHALLENGE_LEN + 1];    /* +1 for '\0' terminator */
 
184
  int auth_type_found;
 
185
  otp_pwe_t pwe;
 
186
 
 
187
  /* Early exit if Auth-Type != inst->name */
 
188
  {
 
189
    VALUE_PAIR *vp;
 
190
 
 
191
    auth_type_found = 0;
 
192
    if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
 
193
      auth_type_found = 1;
 
194
      if (strcmp(vp->strvalue, inst->name))
 
195
        return RLM_MODULE_NOOP;
 
196
    }
 
197
  }
 
198
 
 
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;
 
203
  }
 
204
 
 
205
  /* User-Name attribute required. */
 
206
  if (!request->username) {
 
207
    (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
 
208
                          "for authentication.",
 
209
                  __func__);
 
210
    return RLM_MODULE_INVALID;
 
211
  }
 
212
 
 
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.",
 
216
                  __func__);
 
217
    return RLM_MODULE_INVALID;
 
218
  }
 
219
 
 
220
  /*
 
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.
 
227
   */
 
228
 
 
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;
 
235
  }
 
236
 
 
237
  /* Generate a random challenge. */
 
238
  otp_async_challenge(challenge, inst->challenge_len);
 
239
 
 
240
  /*
 
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().
 
248
   */
 
249
  {
 
250
    int32_t now = htonl(time(NULL));    /* low-order 32 bits on LP64 */
 
251
    char state[OTP_MAX_RADSTATE_LEN];
 
252
 
 
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;
 
257
    }
 
258
    pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
 
259
  }
 
260
 
 
261
  /* Add the challenge to the reply. */
 
262
  {
 
263
    char *u_challenge;  /* challenge with addt'l presentation text */
 
264
 
 
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));
 
270
    free(u_challenge);
 
271
  }
 
272
 
 
273
  /*
 
274
   * Mark the packet as an Access-Challenge packet.
 
275
   * The server will take care of sending it to the user.
 
276
   */
 
277
  request->reply->code = PW_ACCESS_CHALLENGE;
 
278
  DEBUG("rlm_otp: Sending Access-Challenge.");
 
279
 
 
280
  if (!auth_type_found)
 
281
    pairadd(&request->config_items, pairmake("Auth-Type", inst->name, T_OP_EQ));
 
282
  return RLM_MODULE_HANDLED;
 
283
}
 
284
 
 
285
 
 
286
/* Verify the response entered by the user. */
 
287
static int
 
288
otp_authenticate(void *instance, REQUEST *request)
 
289
{
 
290
  otp_option_t *inst = (otp_option_t *) instance;
 
291
 
 
292
  char *username;
 
293
  int rc;
 
294
  otp_pwe_t pwe;
 
295
  VALUE_PAIR *vp;
 
296
  unsigned char challenge[OTP_MAX_CHALLENGE_LEN];       /* cf. authorize() */
 
297
  char passcode[OTP_MAX_PASSCODE_LEN + 1];
 
298
 
 
299
  challenge[0] = '\0';  /* initialize for otp_pw_valid() */
 
300
 
 
301
  /* User-Name attribute required. */
 
302
  if (!request->username) {
 
303
    (void) radlog(L_AUTH, "rlm_otp: %s: Attribute \"User-Name\" required "
 
304
                          "for authentication.",
 
305
                  __func__);
 
306
    return RLM_MODULE_INVALID;
 
307
  }
 
308
  username = request->username->strvalue;
 
309
 
 
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.",
 
313
                  __func__);
 
314
    return RLM_MODULE_INVALID;
 
315
  }
 
316
 
 
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));
 
322
 
 
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 */
 
330
 
 
331
    /* set expected State length */
 
332
    e_length = inst->challenge_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
 
333
 
 
334
    if (vp->length != e_length) {
 
335
      (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: length",
 
336
                    __func__, username);
 
337
      return RLM_MODULE_INVALID;
 
338
    }
 
339
 
 
340
    /*
 
341
     * Verify the state.
 
342
     */
 
343
 
 
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",
 
349
                    __func__, username);
 
350
      return RLM_MODULE_INVALID;
 
351
    }
 
352
 
 
353
    /* extract data from State */
 
354
    (void) memcpy(challenge, raw_state, inst->challenge_len);
 
355
    /* skip flag data */
 
356
    (void) memcpy(&then, raw_state + inst->challenge_len + 4, 4);
 
357
 
 
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",
 
362
                    __func__);
 
363
      return RLM_MODULE_FAIL;
 
364
    }
 
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",
 
368
                    __func__, username);
 
369
      return RLM_MODULE_REJECT;
 
370
    }
 
371
 
 
372
    /* State is valid, but check expiry. */
 
373
    then = ntohl(then);
 
374
    if (time(NULL) - then > inst->challenge_delay) {
 
375
      (void) radlog(L_AUTH, "rlm_otp: %s: bad state for [%s]: expired",
 
376
                    __func__, username);
 
377
      return RLM_MODULE_REJECT;
 
378
    }
 
379
  } /* if (State present) */
 
380
 
 
381
  /* do it */
 
382
  rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
 
383
 
 
384
  /* Add MPPE data as needed. */
 
385
  if (rc == RLM_MODULE_OK)
 
386
    otp_mppe(request, pwe, inst, passcode);
 
387
 
 
388
  return rc;
 
389
}
 
390
 
 
391
 
 
392
/* per-instance destruction */
 
393
static int
 
394
otp_detach(void *instance)
 
395
{
 
396
  otp_option_t *inst = (otp_option_t *) instance;
 
397
 
 
398
  free(inst->otpd_rp);
 
399
  free(inst->chal_prompt);
 
400
  free(instance);
 
401
  /*
 
402
   * Only the main thread instantiates and detaches instances,
 
403
   * so this does not need mutex protection.
 
404
   */
 
405
  if (--ninstance == 0)
 
406
    (void) memset(hmac_key, 0, sizeof(hmac_key));
 
407
 
 
408
  return 0;
 
409
}
 
410
 
 
411
 
 
412
/*
 
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.
 
417
 */
 
418
module_t rlm_otp = {
 
419
  "otp",
 
420
  RLM_TYPE_THREAD_SAFE,         /* type */
 
421
  NULL,
 
422
  otp_instantiate,              /* instantiation */
 
423
  {
 
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 */
 
431
    NULL                        /* post-auth */
 
432
  },
 
433
  otp_detach,                   /* detach */
 
434
  NULL,
 
435
};