All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_otp.c
Go to the documentation of this file.
1 /*
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 as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: bc9a09aebcfff4ac3a0fa239d188017bc2ee3dfb $
19  * @file rlm_otp.c
20  * @brief One time password implementation.
21  *
22  * @copyright 2013 Network RADIUS SARL
23  * @copyright 2000,2001,2002,2013 The FreeRADIUS server project
24  * @copyright 2005-2007 TRI-D Systems, Inc.
25  * @copyright 2001,2002 Google, Inc.
26  */
27 RCSID("$Id: bc9a09aebcfff4ac3a0fa239d188017bc2ee3dfb $")
28 
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/modules.h>
31 
32 #include "extern.h"
33 #include "otp.h"
34 
35 /* Global data */
36 static int ninstance = 0; //!< Number of instances, for global init.
37 
38 /* A mapping of configuration file names to internal variables. */
39 static const CONF_PARSER module_config[] = {
40  { FR_CONF_OFFSET("otpd_rp", PW_TYPE_STRING, rlm_otp_t, otpd_rp), .dflt = OTP_OTPD_RP },
41  { FR_CONF_OFFSET("challenge_prompt", PW_TYPE_STRING, rlm_otp_t, chal_prompt), .dflt = OTP_CHALLENGE_PROMPT },
42  { FR_CONF_OFFSET("challenge_length", PW_TYPE_INTEGER, rlm_otp_t, challenge_len), .dflt = "6" },
43  { FR_CONF_OFFSET("challenge_delay", PW_TYPE_INTEGER, rlm_otp_t, challenge_delay), .dflt = "30" },
44  { FR_CONF_OFFSET("allow_sync", PW_TYPE_BOOLEAN, rlm_otp_t, allow_sync), .dflt = "yes" },
45  { FR_CONF_OFFSET("allow_async", PW_TYPE_BOOLEAN, rlm_otp_t, allow_async), .dflt = "no" },
46 
47  { FR_CONF_OFFSET("mschapv2_mppe", PW_TYPE_INTEGER, rlm_otp_t, mschapv2_mppe_policy), .dflt = "2" },
48  { FR_CONF_OFFSET("mschapv2_mppe_bits", PW_TYPE_INTEGER, rlm_otp_t, mschapv2_mppe_types), .dflt = "2" },
49  { FR_CONF_OFFSET("mschap_mppe", PW_TYPE_INTEGER, rlm_otp_t, mschap_mppe_policy), .dflt = "2" },
50  { FR_CONF_OFFSET("mschap_mppe_bits", PW_TYPE_INTEGER, rlm_otp_t, mschap_mppe_types), .dflt = "2" },
52 };
53 
54 
55 /*
56  * Per-instance initialization
57  */
58 static int mod_instantiate(CONF_SECTION *conf, void *instance)
59 {
60  rlm_otp_t *inst = instance;
61 
62  /* Onetime initialization. */
63  if (!ninstance) {
64  /* Generate a random key, used to protect the State attribute. */
65  otp_get_random(inst->hmac_key, sizeof(inst->hmac_key));
66 
67  /* Initialize the passcode encoding/checking functions. */
68  otp_pwe_init();
69 
70  /*
71  * Don't do this again.
72  * Only the main thread instantiates and detaches instances,
73  * so this does not need mutex protection.
74  */
75  ninstance++;
76  }
77 
78  /* Verify ranges for those vars that are limited. */
79  if ((inst->challenge_len < 5) ||
81  inst->challenge_len = 6;
82 
83  WARN("invalid challenge_length %d, "
84  "range 5-%d, using default of 6",
86  }
87 
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");
91  return -1;
92  }
93 
94  if (inst->mschapv2_mppe_policy > 2) {
95  inst->mschapv2_mppe_policy = 2;
96  WARN("Invalid value for mschapv2_mppe, using default of 2");
97  }
98 
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");
102  }
103 
104  if (inst->mschap_mppe_policy > 2) {
105  inst->mschap_mppe_policy = 2;
106  WARN("Invalid value for mschap_mppe, using default of 2");
107  }
108 
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");
113  }
114 
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);
118 
119  return 0;
120 }
121 
122 /*
123  * Generate a challenge to be presented to the user.
124  */
125 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
126 {
127  rlm_otp_t *inst = (rlm_otp_t *) instance;
128 
129  char challenge[OTP_MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
130  int auth_type_found;
131 
132  /* Early exit if Auth-Type != inst->name */
133  {
134  VALUE_PAIR *vp;
135 
136  auth_type_found = 0;
137  vp = fr_pair_find_by_num(request->config, 0, PW_AUTH_TYPE, TAG_ANY);
138  if (vp) {
139  auth_type_found = 1;
140  if (strcmp(vp->vp_strvalue, inst->name)) {
141  return RLM_MODULE_NOOP;
142  }
143  }
144  }
145 
146  /* The State attribute will be present if this is a response. */
147  if (fr_pair_find_by_num(request->packet->vps, 0, PW_STATE, TAG_ANY) != NULL) {
148  DEBUG("rlm_otp: autz: Found response to Access-Challenge");
149 
150  return RLM_MODULE_OK;
151  }
152 
153  /* User-Name attribute required. */
154  if (!request->username) {
155  RWDEBUG("Attribute \"User-Name\" "
156  "required for authentication");
157 
158  return RLM_MODULE_INVALID;
159  }
160 
161  if (otp_pwe_present(request) == 0) {
162  RWDEBUG("Attribute "
163  "\"User-Password\" or equivalent required "
164  "for authentication");
165 
166  return RLM_MODULE_INVALID;
167  }
168 
169  /*
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.
176  */
177  if (inst->allow_sync && !inst->allow_async) {
178  /* This is the token sync response. */
179  if (!auth_type_found) {
180  pair_make_config("Auth-Type", inst->name, T_OP_EQ);
181  }
182 
183  return RLM_MODULE_OK;
184  }
185 
186  /*
187  * Generate a random challenge.
188  */
189  otp_async_challenge(challenge, inst->challenge_len);
190 
191  /*
192  * Create the State attribute, which will be returned to
193  * us along with the response.
194  *
195  * We will need this to verify the response.
196  *
197  * It must be hmac protected to prevent insertion of arbitrary
198  * State by an inside attacker.
199  *
200  * If we won't actually use the State (server config doesn't
201  * allow async), we just use a trivial State.
202  *
203  * We always create at least a trivial State, so mod_authorize()
204  * can quickly pass on to mod_authenticate().
205  */
206  {
207  int32_t now = htonl(time(NULL)); //!< Low-order 32 bits on LP64.
208 
209  char gen_state[OTP_MAX_RADSTATE_LEN];
210  size_t len;
211  VALUE_PAIR *vp;
212 
213  len = otp_gen_state(gen_state, challenge, inst->challenge_len,
214  0, now, inst->hmac_key);
215 
216  vp = fr_pair_afrom_num(request->reply, 0, PW_STATE);
217  if (!vp) {
218  return RLM_MODULE_FAIL;
219  }
220 
221  fr_pair_value_memcpy(vp, (uint8_t const *) gen_state, len);
222  fr_pair_add(&request->reply->vps, vp);
223  }
224 
225  /*
226  * Add the challenge to the reply.
227  */
228  {
229  VALUE_PAIR *vp;
230 
231  char *expanded = NULL;
232  ssize_t len;
233 
234  /*
235  * First add the internal OTP challenge attribute to
236  * the reply list.
237  */
238  vp = fr_pair_afrom_num(request->reply, 0, PW_OTP_CHALLENGE);
239  if (!vp) {
240  return RLM_MODULE_FAIL;
241  }
242 
243  fr_pair_value_strcpy(vp, challenge);
244  vp->op = T_OP_SET;
245 
246  fr_pair_add(&request->reply->vps, vp);
247 
248  /*
249  * Then add the message to the user to they known
250  * what the challenge value is.
251  */
252 
253  len = radius_axlat(&expanded, request, inst->chal_prompt, NULL, NULL);
254  if (len < 0) {
255  return RLM_MODULE_FAIL;
256  }
257 
258  vp = fr_pair_afrom_num(request->reply, 0, PW_REPLY_MESSAGE);
259  if (!vp) {
260  talloc_free(expanded);
261  return RLM_MODULE_FAIL;
262  }
263  fr_pair_value_strnsteal(vp, expanded, len);
264  vp->op = T_OP_SET;
265 
266  fr_pair_add(&request->reply->vps, vp);
267  }
268 
269  /*
270  * Mark the packet as an Access-Challenge packet.
271  * The server will take care of sending it to the user.
272  */
273  request->reply->code = PW_CODE_ACCESS_CHALLENGE;
274  DEBUG("rlm_otp: Sending Access-Challenge");
275 
276  if (!auth_type_found) {
277  pair_make_config("Auth-Type", inst->name, T_OP_EQ);
278  }
279 
280  return RLM_MODULE_HANDLED;
281 }
282 
283 
284 /*
285  * Verify the response entered by the user.
286  */
287 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
288 {
289  rlm_otp_t *inst = instance;
290 
291  char const *username;
292  int rc;
293  otp_pwe_t pwe;
294  VALUE_PAIR *vp;
295 
296  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  RWDEBUG("Attribute \"User-Name\" required "
304  "for authentication");
305 
306  return RLM_MODULE_INVALID;
307  }
308 
309  username = request->username->vp_strvalue;
310 
311  pwe = otp_pwe_present(request);
312  if (pwe == 0) {
313  RWDEBUG("Attribute \"User-Password\" "
314  "or equivalent required for authentication");
315 
316  return RLM_MODULE_INVALID;
317  }
318 
319  /*
320  * Retrieve the challenge (from State attribute).
321  */
322  vp = fr_pair_find_by_num(request->packet->vps, 0, PW_STATE, TAG_ANY);
323  if (vp) {
324  char gen_state[OTP_MAX_RADSTATE_LEN]; //!< State as hexits
325  uint8_t bin_state[OTP_MAX_RADSTATE_LEN];
326 
327  int32_t then; //!< State timestamp.
328  size_t elen; //!< Expected State length.
329  size_t len;
330 
331  /*
332  * Set expected State length (see otp_gen_state())
333  */
334  elen = (inst->challenge_len * 2) + 8 + 8 + 32;
335 
336  if (vp->vp_length != elen) {
337  REDEBUG("Bad radstate for [%s]: length", username);
338  return RLM_MODULE_INVALID;
339  }
340 
341  /*
342  * Verify the state.
343  */
344 
345  /*
346  * Convert vp state (ASCII encoded hexits in opaque bin
347  * string) back to binary.
348  *
349  * There are notes in otp_radstate as to why the state
350  * value is encoded as hexits.
351  */
352  len = fr_hex2bin(bin_state, sizeof(bin_state), vp->vp_strvalue, vp->vp_length);
353  if (len != (vp->vp_length / 2)) {
354  REDEBUG("bad radstate for [%s]: not hex", username);
355 
356  return RLM_MODULE_INVALID;
357  }
358 
359  /*
360  * Extract data from State
361  */
362  memcpy(challenge, bin_state, inst->challenge_len);
363 
364  /*
365  * Skip flag data
366  */
367  memcpy(&then, bin_state + inst->challenge_len + 4, 4);
368 
369  /*
370  * Generate new state from returned input data
371  */
372  otp_gen_state(gen_state, challenge, inst->challenge_len, 0,
373  then, inst->hmac_key);
374 
375  /*
376  * Compare generated state (in hex form)
377  * against generated state (in hex form)
378  * to verify hmac.
379  */
380  if (memcmp(gen_state, vp->vp_octets, vp->vp_length)) {
381  REDEBUG("bad radstate for [%s]: hmac", username);
382 
383  return RLM_MODULE_REJECT;
384  }
385 
386  /*
387  * State is valid, but check expiry.
388  */
389  then = ntohl(then);
390  if ((time(NULL) - then) > (int)inst->challenge_delay) {
391  REDEBUG("bad radstate for [%s]: expired",username);
392 
393  return RLM_MODULE_REJECT;
394  }
395  }
396 
397  /* do it */
398  rc = otp_pw_valid(request, pwe, challenge, inst, passcode);
399 
400  /* Add MPPE data as needed. */
401  if (rc == RLM_MODULE_OK) {
402  otp_mppe(request, pwe, inst, passcode);
403  }
404 
405  return rc;
406 }
407 
408 /*
409  * If the module needs to temporarily modify it's instantiation
410  * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
411  * The server will then take care of ensuring that the module
412  * is single-threaded.
413  */
414 extern module_t rlm_otp;
415 module_t rlm_otp = {
417  .name = "otp",
418  .type = RLM_TYPE_THREAD_SAFE,
419  .inst_size = sizeof(rlm_otp_t),
420  .config = module_config,
421  .instantiate = mod_instantiate,
422  .methods = {
425  },
426 };
ssize_t ssize_t ssize_t radius_axlat(char **out, REQUEST *request, char const *fmt, xlat_escape_t escape, void *escape_ctx) CC_HINT(nonnull(1
#define OTP_MAX_RADSTATE_LEN
Definition: extern.h:70
uint32_t challenge_delay
Max delay time for response, in seconds.
Definition: extern.h:50
void otp_pwe_init(void)
Definition: otp_pwe.c:53
RFC2865 - Access-Challenge.
Definition: radius.h:102
uint32_t mschapv2_mppe_types
Key type/length for mschapv2/mppe.
Definition: extern.h:57
The module is OK, continue.
Definition: radiusd.h:91
uint32_t mschap_mppe_policy
Whether or not do to mppe for mschap .
Definition: extern.h:58
enum otp_pwe otp_pwe_t
Metadata exported by the module.
Definition: modules.h:134
VALUE_PAIR * fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int vendor, unsigned int attr)
Create a new valuepair.
Definition: pair.c:106
bool allow_async
C/R mode allowed?
Definition: extern.h:53
struct rlm_otp_t rlm_otp_t
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
Handle authorization requests using Couchbase document data.
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
#define RLM_MODULE_INIT
Definition: modules.h:86
static rlm_rcode_t CC_HINT(nonnull)
Definition: rlm_otp.c:125
size_t otp_gen_state(char[OTP_MAX_RADSTATE_LEN], char const [OTP_MAX_CHALLENGE_LEN], size_t, int32_t, int32_t, uint8_t const [16])
Generate an OTP state value.
Definition: otp_radstate.c:108
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
#define inst
Definition: token.h:46
The module considers the request invalid.
Definition: radiusd.h:93
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_otp.c:58
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
char const * chal_prompt
Text to present challenge to user must have s.
Definition: extern.h:44
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
void otp_async_challenge(char[OTP_MAX_CHALLENGE_LEN+1], size_t)
Generate a random challenge (ascii chars 0-9)
Definition: otp_util.c:63
#define DEBUG(fmt,...)
Definition: log.h:175
void fr_pair_value_strcpy(VALUE_PAIR *vp, char const *src)
Copy data into an "string" data type.
Definition: pair.c:2013
#define pair_make_config(_a, _b, _c)
Definition: radiusd.h:547
void fr_pair_add(VALUE_PAIR **head, VALUE_PAIR *vp)
Add a VP to the end of the list.
Definition: pair.c:659
size_t fr_hex2bin(uint8_t *bin, size_t outlen, char const *hex, size_t inlen)
Convert hex strings to binary data.
Definition: misc.c:220
Immediately reject the request.
Definition: radiusd.h:89
char const * name
Instance name for mod_authorize().
Definition: extern.h:42
void otp_get_random(uint8_t *, size_t)
Generate some random bytes.
Definition: otp_util.c:37
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
0 methods index for authenticate section.
Definition: modules.h:41
A truth value.
Definition: radius.h:56
int otp_pw_valid(REQUEST *, int, char const *, rlm_otp_t const *, char[])
Definition: token.h:45
FR_TOKEN op
Operator to use when moving or inserting valuepair into a list.
Definition: pair.h:118
32 Bit unsigned integer.
Definition: radius.h:34
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
static rs_t * conf
Definition: radsniff.c:46
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
uint32_t challenge_len
Challenge length, min 5 digits.
Definition: extern.h:49
Module succeeded without doing anything.
Definition: radiusd.h:96
void otp_mppe(REQUEST *, otp_pwe_t, rlm_otp_t const *, char const *)
Definition: otp_mppe.c:45
uint32_t mschapv2_mppe_policy
Whether or not do to mppe for mschapv2.
Definition: extern.h:55
static const CONF_PARSER module_config[]
Definition: rlm_otp.c:39
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
#define TAG_ANY
Definition: pair.h:191
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
#define OTP_MAX_CHALLENGE_LEN
Definition: otp.h:35
otp_pwe_t otp_pwe_present(REQUEST const *)
Definition: otp_pwe.c:120
#define OTP_MAX_PASSCODE_LEN
Definition: otp.h:52
#define OTP_CHALLENGE_PROMPT
Definition: extern.h:39
void fr_pair_value_strnsteal(VALUE_PAIR *vp, char *src, size_t len)
Reparent an allocated char buffer to a VALUE_PAIR reallocating the buffer to the correct size...
Definition: pair.c:1980
#define WARN(fmt,...)
Definition: log.h:144
#define REDEBUG(fmt,...)
Definition: log.h:254
bool allow_sync
Useful to override pwdfile card_type settings.
Definition: extern.h:51
VALUE_PAIR * fr_pair_find_by_num(VALUE_PAIR *head, unsigned int vendor, unsigned int attr, int8_t tag)
Find the pair with the matching attribute.
Definition: pair.c:639
String of printable characters.
Definition: radius.h:33
#define RWDEBUG(fmt,...)
Definition: log.h:251
module_t rlm_otp
Definition: rlm_otp.c:415
1 methods index for authorize section.
Definition: modules.h:42
#define RCSID(id)
Definition: build.h:135
The module handled the request, so stop.
Definition: radiusd.h:92
uint32_t mschap_mppe_types
key type/length for mschap/mppe.
Definition: extern.h:60
void fr_pair_value_memcpy(VALUE_PAIR *vp, uint8_t const *src, size_t len)
Copy data into an "octets" data type.
Definition: pair.c:1905
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
static int ninstance
Number of instances, for global init.
Definition: rlm_otp.c:36
uint8_t hmac_key[16]
because it doesn't track State
Definition: extern.h:47
#define OTP_OTPD_RP
Definition: extern.h:36