The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_eap_mschapv2.c
Go to the documentation of this file.
1 /*
2  * rlm_eap_mschapv2.c Handles that are called from eap
3  *
4  * Version: $Id: 3560c03407997d82bde2fafa0a1e9ea764af1f56 $
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * @copyright 2003,2006 The FreeRADIUS server project
21  */
22 
23 RCSID("$Id: 3560c03407997d82bde2fafa0a1e9ea764af1f56 $")
24 
25 #include <freeradius-devel/server/dependency.h>
26 #include <freeradius-devel/server/pair.h>
27 #include <freeradius-devel/server/virtual_servers.h>
28 #include <freeradius-devel/unlang/call.h>
29 #include <freeradius-devel/unlang/interpret.h>
30 #include <freeradius-devel/util/base16.h>
31 #include <freeradius-devel/util/debug.h>
32 #include <freeradius-devel/util/rand.h>
33 
34 #include "eap_mschapv2.h"
35 
36 static int auth_type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
37  CONF_ITEM *ci, UNUSED conf_parser_t const *rule);
38 
39 typedef struct {
41  bool send_error;
42  char const *identity;
45 
47  { FR_CONF_OFFSET("with_ntdomain_hack", rlm_eap_mschapv2_t, with_ntdomain_hack), .dflt = "no" },
48 
49  { FR_CONF_OFFSET_TYPE_FLAGS("auth_type", FR_TYPE_VOID, 0, rlm_eap_mschapv2_t, auth_type), .func = auth_type_parse, .dflt = "mschap" },
50  { FR_CONF_OFFSET("send_error", rlm_eap_mschapv2_t, send_error), .dflt = "no" },
51  { FR_CONF_OFFSET("identity", rlm_eap_mschapv2_t, identity) },
53 };
54 
55 static fr_dict_t const *dict_freeradius;
56 static fr_dict_t const *dict_radius;
57 
60  { .out = &dict_freeradius, .proto = "freeradius" },
61  { .out = &dict_radius, .proto = "radius" },
62  { NULL }
63 };
64 
68 
70 
81 static fr_dict_attr_t const *attr_state;
83 
86  { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
87  { .out = &attr_ms_chap_peer_challenge, .name = "MS-CHAP-Peer-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
88  { .out = &attr_ms_chap_user_name, .name = "MS-CHAP-User-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
89 
90  { .out = &attr_microsoft, .name = "Vendor-Specific.Microsoft", .type = FR_TYPE_VENDOR, .dict = &dict_radius },
91 
92  { .out = &attr_ms_chap_challenge, .name = "Vendor-Specific.Microsoft.CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
93  { .out = &attr_ms_chap_error, .name = "Vendor-Specific.Microsoft.CHAP-Error", .type = FR_TYPE_STRING, .dict = &dict_radius },
94  { .out = &attr_ms_chap_nt_enc_pw, .name = "Vendor-Specific.Microsoft.CHAP-NT-Enc-PW", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
95  { .out = &attr_ms_chap2_cpw, .name = "Vendor-Specific.Microsoft.CHAP2-CPW", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
96  { .out = &attr_ms_chap2_response, .name = "Vendor-Specific.Microsoft.CHAP2-Response", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
97  { .out = &attr_ms_chap2_success, .name = "Vendor-Specific.Microsoft.CHAP2-Success", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
98  { .out = &attr_ms_mppe_encryption_policy, .name = "Vendor-Specific.Microsoft.MPPE-Encryption-Policy", .type = FR_TYPE_UINT32, .dict = &dict_radius },
99  { .out = &attr_ms_mppe_encryption_type, .name = "Vendor-Specific.Microsoft.MPPE-Encryption-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
100  { .out = &attr_ms_mppe_send_key, .name = "Vendor-Specific.Microsoft.MPPE-Send-Key", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
101  { .out = &attr_ms_mppe_recv_key, .name = "Vendor-Specific.Microsoft.MPPE-Recv-Key", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
102  { .out = &attr_state, .name = "State", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
103  { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
104  { NULL }
105 };
106 
108 {
109  fr_pair_t *parent;
110 
111  RDEBUG2("Storing attributes for final response");
112 
113  parent = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_microsoft);
114  if (!parent) parent = request->reply_ctx;
115 
116  RINDENT();
117  if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
120  }
121  if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
124  }
125  if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
126  attr_ms_mppe_recv_key, 0) > 0) {
127  RDEBUG2("%s", attr_ms_mppe_recv_key->name);
128  }
129  if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
130  attr_ms_mppe_send_key, 0) > 0) {
131  RDEBUG2("%s", attr_ms_mppe_send_key->name);
132  }
133  REXDENT();
134 }
135 
136 /** Translate a string auth_type into an enumeration value
137  *
138  * @param[in] ctx to allocate data.
139  * @param[out] out Where to write the auth_type we created or resolved.
140  * @param[in] parent Base structure address.
141  * @param[in] ci #CONF_PAIR specifying the name of the auth_type.
142  * @param[in] rule unused.
143  * @return
144  * - 0 on success.
145  * - -1 on failure.
146  */
147 static int auth_type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
148  CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
149 {
150  char const *auth_type = cf_pair_value(cf_item_to_pair(ci));
151 
153  cf_log_err(ci, "Failed adding %s alias", attr_auth_type->name);
154  return -1;
155  }
157 
158  return 0;
159 }
160 
161 /*
162  * Compose the response.
163  */
164 static int eap_mschapv2_compose(rlm_eap_mschapv2_t const *inst, request_t *request, eap_session_t *eap_session,
165  fr_pair_t *reply) CC_HINT(nonnull);
166 static int eap_mschapv2_compose(rlm_eap_mschapv2_t const *inst, request_t *request, eap_session_t *eap_session,
167  fr_pair_t *reply)
168 {
169  uint8_t *ptr;
170  int16_t length;
171  mschapv2_header_t *hdr;
172  eap_round_t *eap_round = eap_session->this_round;
173 
174  eap_round->request->code = FR_EAP_CODE_REQUEST;
175  eap_round->request->type.num = FR_EAP_METHOD_MSCHAPV2;
176 
177  /*
178  * Always called with vendor Microsoft
179  */
180  if (reply->da == attr_ms_chap_challenge) {
181  /*
182  * 0 1 2 3
183  * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
184  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
185  * | Code | Identifier | Length |
186  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
187  * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
188  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
189  * | MS-Length | Value-Size | Challenge...
190  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
191  * | Challenge...
192  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
193  * | Server Name...
194  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
195  */
196  length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + (talloc_array_length(inst->identity) - 1);
197  eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length);
198 
199  /*
200  * Allocate room for the EAP-MS-CHAPv2 data.
201  */
202  if (!eap_round->request->type.data) return -1;
203  eap_round->request->type.length = length;
204 
205  ptr = eap_round->request->type.data;
206  hdr = (mschapv2_header_t *) ptr;
207 
209  hdr->mschapv2_id = eap_round->response->id + 1;
210  length = htons(length);
211  memcpy(hdr->ms_length, &length, sizeof(uint16_t));
213 
214  ptr += MSCHAPV2_HEADER_LEN;
215 
216  /*
217  * Copy the Challenge, success, or error over.
218  */
219  memcpy(ptr, reply->vp_octets, reply->vp_length);
220  memcpy((ptr + reply->vp_length), inst->identity, (talloc_array_length(inst->identity) - 1));
221  } else if (reply->da == attr_ms_chap2_success) {
222  /*
223  * 0 1 2 3
224  * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
225  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
226  * | Code | Identifier | Length |
227  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
228  * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
229  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
230  * | MS-Length | Message...
231  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
232  */
233  RDEBUG2("MS-CHAPv2 Success");
234  length = 46;
235  eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length);
236  /*
237  * Allocate room for the EAP-MS-CHAPv2 data.
238  */
239  if (!eap_round->request->type.data) return -1;
240  memset(eap_round->request->type.data, 0, length);
241  eap_round->request->type.length = length;
242 
243  eap_round->request->type.data[0] = FR_EAP_MSCHAPV2_SUCCESS;
244  eap_round->request->type.data[1] = eap_round->response->id;
245  length = htons(length);
246  memcpy((eap_round->request->type.data + 2), &length, sizeof(uint16_t));
247  memcpy((eap_round->request->type.data + 4), reply->vp_strvalue + 1, 42);
248  } else if (reply->da == attr_ms_chap_error) {
249  REDEBUG("MS-CHAPv2 Failure");
250  length = 4 + reply->vp_length - 1;
251  eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length);
252 
253  /*
254  * Allocate room for the EAP-MS-CHAPv2 data.
255  */
256  if (!eap_round->request->type.data) return 0;
257  memset(eap_round->request->type.data, 0, length);
258  eap_round->request->type.length = length;
259 
260  eap_round->request->type.data[0] = FR_EAP_MSCHAPV2_FAILURE;
261  eap_round->request->type.data[1] = eap_round->response->id;
262  length = htons(length);
263  memcpy((eap_round->request->type.data + 2), &length, sizeof(uint16_t));
264  /*
265  * Copy the entire failure message.
266  */
267  memcpy((eap_round->request->type.data + 4), reply->vp_strvalue + 1, reply->vp_length - 1);
268  } else {
269  RERROR("%s: Internal sanity check failed", __FUNCTION__);
270  return -1;
271  }
272 
273  return 0;
274 }
275 
276 
277 static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
278 
279 static unlang_action_t mschap_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
280 {
281  eap_session_t *eap_session = mctx->rctx;
282  mschapv2_opaque_t *data = talloc_get_type_abort(eap_session->opaque, mschapv2_opaque_t);
283  eap_round_t *eap_round = eap_session->this_round;
284  fr_pair_list_t response;
285  rlm_eap_mschapv2_t const *inst = mctx->inst->data;
286  rlm_rcode_t rcode;
287  fr_pair_t *parent;
288 
289  fr_pair_list_init(&response);
290 
291  rcode = unlang_interpret_stack_result(request);
292 
293  /*
294  * Delete MPPE keys & encryption policy. We don't
295  * want these here.
296  */
297  mppe_keys_store(request, data);
298 
299  parent = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_microsoft);
300  if (!parent) parent = request->reply_ctx;
301 
302  /*
303  * Take the response from the mschap module, and
304  * return success or failure, depending on the result.
305  */
306  if (rcode == RLM_MODULE_OK) {
307  if (fr_pair_list_copy_by_da(data, &response, &parent->vp_group, attr_ms_chap2_success, 0) < 0) {
308  RPERROR("Failed copying %s", attr_ms_chap2_success->name);
310  }
311 
313  } else if (inst->send_error) {
314  if (fr_pair_list_copy_by_da(data, &response, &parent->vp_group, attr_ms_chap_error, 0) < 0) {
315  RPERROR("Failed copying %s", attr_ms_chap_error->name);
317  }
318  if (!fr_pair_list_empty(&response)) {
319  int n, err, retry;
320  char buf[34];
321  fr_pair_t *vp = fr_pair_list_head(&response);
322 
323  PAIR_VERIFY(vp);
324 
325  RDEBUG2("MSCHAP-Error: %pV", &vp->data);
326 
327  /*
328  * Parse the new challenge out of the
329  * MS-CHAP-Error, so that if the client
330  * issues a re-try, we will know which
331  * challenge value that they used.
332  */
333  n = sscanf(vp->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
334  if (n == 3) {
335  RDEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s",
336  err, retry, buf);
337  fr_base16_decode(NULL, &FR_DBUFF_TMP(data->auth_challenge, 16),
338  &FR_SBUFF_IN(buf, strlen(buf)), false);
339  } else {
340  RDEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
341  }
342  }
344  } else {
345  eap_round->request->code = FR_EAP_CODE_FAILURE;
347  }
348 
349  /*
350  * No response, die.
351  */
352  if (fr_pair_list_empty(&response)) {
353  REDEBUG("No %s or %s attributes were found", attr_ms_chap2_success->name, attr_ms_chap_error->name);
355  }
356 
357  /*
358  * Compose the response (whatever it is),
359  * and return it to the over-lying EAP module.
360  */
361  eap_mschapv2_compose(eap_session->inst, request, eap_session, fr_pair_list_head(&response));
362  fr_pair_list_free(&response);
363 
365 }
366 
367 /*
368  * Authenticate a previously sent challenge.
369  */
370 static unlang_action_t CC_HINT(nonnull) mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
371 {
372  rlm_eap_mschapv2_t const *inst = talloc_get_type_abort(mctx->inst->data, rlm_eap_mschapv2_t);
373  request_t *parent = request->parent;
374  eap_session_t *eap_session = eap_session_get(parent);
375  mschapv2_opaque_t *data = talloc_get_type_abort(eap_session->opaque, mschapv2_opaque_t);
376  eap_round_t *eap_round = eap_session->this_round;
377  fr_pair_t *auth_challenge, *response, *name;
378 
379  CONF_SECTION *unlang;
380  int ccode;
381  uint8_t *p;
382  size_t length;
383 
384  if (!fr_cond_assert(eap_session->inst)) RETURN_MODULE_FAIL;
385 
386  /*
387  * Sanity check the response.
388  */
389  if (eap_round->response->length < 6) {
390  REDEBUG("Response too short, expected at least 6 bytes, got %zu bytes",
391  eap_round->response->length);
393  }
394 
395  ccode = eap_round->response->type.data[0];
396 
397  switch (data->code) {
399  if (ccode == FR_EAP_MSCHAPV2_RESPONSE) {
400  RDEBUG2("Authentication re-try from client after we sent a failure");
401  break;
402  }
403 
404  /*
405  * if we sent error 648 (password expired) to the client
406  * we might get an MSCHAP-CPW packet here; turn it into a
407  * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
408  * (or proxy it, I guess)
409  */
410  if (ccode == FR_EAP_MSCHAPV2_CHGPASSWD) {
411  fr_pair_t *cpw;
412  int mschap_id = eap_round->response->type.data[1];
413  int copied = 0;
414  int seq = 1;
415  fr_pair_t *ms;
416 
417  RDEBUG2("Password change packet received");
418 
419  MEM(pair_update_request(&auth_challenge, attr_ms_chap_challenge) >= 0);
420  fr_pair_value_memdup(auth_challenge, data->auth_challenge, MSCHAPV2_CHALLENGE_LEN, false);
421 
423  MEM(fr_pair_value_mem_alloc(cpw, &p, 68, false) == 0);
424  p[0] = 7;
425  p[1] = mschap_id;
426  memcpy(p + 2, eap_round->response->type.data + 520, 66);
427 
428  ms = fr_pair_find_by_da_nested(&request->request_pairs, NULL, attr_microsoft);
429  if (!ms) ms = request->request_ctx;
430 
431  /*
432  * break the encoded password into VPs (3 of them)
433  */
434  while (copied < 516) {
435  fr_pair_t *nt_enc;
436 
437  int to_copy = 516 - copied;
438  if (to_copy > 243) to_copy = 243;
439 
441  MEM(fr_pair_value_mem_alloc(nt_enc, &p, 4 + to_copy, false) == 0);
442  MEM(fr_pair_append(&ms->vp_group, nt_enc) == 0);
443  p[0] = 6;
444  p[1] = mschap_id;
445  p[2] = 0;
446  p[3] = seq++;
447  memcpy(p + 4, eap_round->response->type.data + 4 + copied, to_copy);
448 
449  copied += to_copy;
450  }
451 
452  RDEBUG2("Built change password packet");
453  log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
454 
455  /*
456  * jump to "authentication"
457  */
458  goto packet_ready;
459  }
460 
461  /*
462  * we sent a failure and are expecting a failure back
463  */
464  if (ccode != FR_EAP_MSCHAPV2_FAILURE) {
465  REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode);
467  }
468 
469 failure:
470  eap_round->request->code = FR_EAP_CODE_FAILURE;
472 
474  /*
475  * we sent a success to the client; some clients send a
476  * success back as-per the RFC, some send an ACK. Permit
477  * both, I guess...
478  */
479 
480  switch (ccode) {
482  eap_round->request->code = FR_EAP_CODE_SUCCESS;
483 
484  if (!fr_pair_list_empty(&data->mppe_keys)) {
485  fr_pair_t *ms;
486 
487  ms = fr_pair_find_by_da_nested(&parent->reply_pairs, NULL, attr_microsoft);
488  if (!ms) {
489  MEM(ms = fr_pair_afrom_da_nested(parent->reply_ctx, &parent->reply_pairs, attr_microsoft));
490  }
491 
492  RDEBUG2("Adding stored attributes to parent");
493  log_request_pair_list(L_DBG_LVL_2, request, NULL, &data->mppe_keys, "&parent.reply.");
494  MEM(fr_pair_list_copy(ms, &ms->vp_group, &data->mppe_keys) >= 0);
495  } else {
496  RDEBUG2("No stored attributes to copy to parent");
497  }
498 
499  FALL_THROUGH;
500 
501  case FR_EAP_MSCHAPV2_ACK:
502  MEM(fr_pair_list_copy(parent->reply_ctx, &parent->reply_pairs, &data->reply) >= 0);
504  }
505  REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
507 
509  if (ccode == FR_EAP_MSCHAPV2_FAILURE) goto failure;
510 
511  /*
512  * we sent a challenge, expecting a response
513  */
514  if (ccode != FR_EAP_MSCHAPV2_RESPONSE) {
515  REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
517  }
518  /* authentication happens below */
519  break;
520 
521  default:
522  /* should never happen */
523  REDEBUG("Unknown state %d", data->code);
525  }
526 
527 
528  /*
529  * Ensure that we have at least enough data
530  * to do the following checks.
531  *
532  * EAP header (4), EAP type, MS-CHAP opcode,
533  * MS-CHAP ident, MS-CHAP data length (2),
534  * MS-CHAP value length.
535  */
536  if (eap_round->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
537  REDEBUG("Response is too short");
539  }
540 
541  /*
542  * The 'value_size' is the size of the response,
543  * which is supposed to be the response (48
544  * bytes) plus 1 byte of flags at the end.
545  *
546  * NOTE: When using Cisco NEAT with EAP-MSCHAPv2, the
547  * switch supplicant will send MSCHAPv2 data (EAP type = 26)
548  * but will always set a value_size of 16 and NULL out the
549  * peer challenge.
550  *
551  */
552  if ((eap_round->response->type.data[4] != 49) &&
553  (eap_round->response->type.data[4] != 16)) {
554  REDEBUG("Response is of incorrect length %d", eap_round->response->type.data[4]);
556  }
557 
558  /*
559  * The MS-Length field is 5 + value_size + length
560  * of name, which is put after the response.
561  */
562  length = fr_nbo_to_uint16(eap_round->response->type.data + 2);
563  if ((length < (5 + 49)) || (length > (256 + 5 + 49))) {
564  REDEBUG("Response contains contradictory length %zu %d", length, 5 + 49);
566  }
567 
568  /*
569  * We now know that the user has sent us a response
570  * to the challenge. Let's try to authenticate it.
571  *
572  * We do this by taking the challenge from 'data',
573  * the response from the EAP packet, and creating fr_pair_t's
574  * to pass to the 'mschap' module. This is a little wonky,
575  * but it works.
576  */
577  MEM(pair_update_request(&auth_challenge, attr_ms_chap_challenge) >= 0);
578  fr_pair_value_memdup(auth_challenge, data->auth_challenge, MSCHAPV2_CHALLENGE_LEN, false);
579 
581  MEM(fr_pair_value_mem_alloc(response, &p, MSCHAPV2_RESPONSE_LEN, false) == 0);
582  p[0] = eap_round->response->type.data[1];
583  p[1] = eap_round->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
584  memcpy(p + 2, &eap_round->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2);
585 
586  /*
587  * If we're forcing a peer challenge, use it instead of
588  * the challenge sent by the client.
589  */
590  if (data->has_peer_challenge) memcpy(p + 2, data->peer_challenge, MSCHAPV2_CHALLENGE_LEN);
591 
592  /*
593  * MS-Length - MS-Value - 5.
594  */
596  MEM(fr_pair_value_bstrndup(name, (char const *)&eap_round->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
597  length - 49 - 5, true) == 0);
598 packet_ready:
599 
600  /*
601  * Look for "authenticate foo" in the current virtual
602  * server. If not there, then in the parent one.
603  */
604  RDEBUG("Looking for authenticate %s { ... }", inst->auth_type->name);
605  unlang = cf_section_find(unlang_call_current(parent), "authenticate", inst->auth_type->name);
606  if (!unlang) unlang = cf_section_find(unlang_call_current(request->parent), "authenticate", inst->auth_type->name);
607  if (!unlang) {
608  RDEBUG2("authenticate %s { ... } sub-section not found.",
609  inst->auth_type->name);
611  }
612 
613  return unlang_module_yield_to_section(p_result, request, unlang, RLM_MODULE_FAIL, mschap_resume, NULL, 0, eap_session);
614 }
615 
616 /*
617  * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
618  */
619 static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
620 {
621  request_t *parent = request->parent;
622  eap_session_t *eap_session = eap_session_get(parent);
623  fr_pair_t *auth_challenge;
624  fr_pair_t *peer_challenge;
626 
627  uint8_t *p;
628  int i;
629  bool created_auth_challenge;
630 
632 
633  /*
634  * We're looking for attributes that should come
635  * from the EAP-TTLS submodule.
636  */
638 
639  /*
640  * Keep track of the challenge and the state we are in.
641  */
642  MEM(data = talloc_zero(eap_session, mschapv2_opaque_t));
644  fr_pair_list_init(&data->mppe_keys);
645  fr_pair_list_init(&data->reply);
646 
647  /*
648  * Allow the administrator to set the CHAP-Challenge and Peer-Challenge attributes.
649  */
650  auth_challenge = fr_pair_find_by_da_nested(&parent->control_pairs, NULL, attr_ms_chap_challenge);
651  if (auth_challenge && (auth_challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
652  RWDEBUG("&parent.control.MS-CHAP-Challenge is incorrect length. Ignoring it");
653  auth_challenge = NULL;
654  }
655 
656  peer_challenge = fr_pair_find_by_da_nested(&parent->control_pairs, NULL, attr_ms_chap_peer_challenge);
657  if (peer_challenge && (peer_challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
658  RWDEBUG("&parent.control.MS-CHAP-Peer-Challenge is incorrect length. Ignoring it");
659  peer_challenge = NULL;
660  }
661 
662  created_auth_challenge = (auth_challenge == NULL);
663 
664  /*
665  * if the administrator didn't set a challenge, then create one ourselves.
666  */
667  if (!auth_challenge) {
668  MEM(auth_challenge = fr_pair_afrom_da(eap_session, attr_ms_chap_challenge));
669  MEM(fr_pair_value_mem_alloc(auth_challenge, &p, MSCHAPV2_CHALLENGE_LEN, false) == 0);
670  for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) p[i] = fr_rand();
671  }
672  RDEBUG2("Issuing Challenge");
673 
674  /*
675  * We're at the stage where we're challenging the user.
676  */
677  memcpy(data->auth_challenge, auth_challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
678 
679  if (peer_challenge) {
680  data->has_peer_challenge = true;
681  memcpy(data->peer_challenge, peer_challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
682  }
683 
684  eap_session->opaque = data;
685 
686  /*
687  * Compose the EAP-MSCHAPV2 packet out of the data structure,
688  * and free it.
689  */
690  eap_mschapv2_compose(mctx->inst->data, request, eap_session, auth_challenge);
691  if (created_auth_challenge) TALLOC_FREE(auth_challenge);
692 
693  /*
694  * We don't need to authorize the user at this point.
695  *
696  * We also don't need to keep the challenge, as it's
697  * stored in 'eap_session->this_round', which will be given back
698  * to us...
699  */
700  eap_session->process = mod_process;
701 
703 }
704 
705 /*
706  * Attach the module.
707  */
708 static int mod_instantiate(module_inst_ctx_t const *mctx)
709 {
710  rlm_eap_mschapv2_t *inst = talloc_get_type_abort(mctx->inst->data, rlm_eap_mschapv2_t);
711 
712  if (inst->identity && (strlen(inst->identity) > 255)) {
713  cf_log_err(mctx->inst->conf, "identity is too long");
714  return -1;
715  }
716 
717  if (!inst->identity) inst->identity = talloc_typed_asprintf(inst, "freeradius-%s", RADIUSD_VERSION_STRING);
718 
719  return 0;
720 }
721 
722 /*
723  * The module name should be the only globally exported symbol.
724  * That is, everything else should be 'static'.
725  */
728  .common = {
729  .name = "eap_mschapv2",
730  .magic = MODULE_MAGIC_INIT,
731  .inst_size = sizeof(rlm_eap_mschapv2_t),
733  .instantiate = mod_instantiate, /* Create new submodule instance */
734  },
735  .provides = { FR_EAP_METHOD_MSCHAPV2 },
736  .session_init = mod_session_init, /* Initialise a new EAP session */
737  .clone_parent_lists = false /* HACK */
738 };
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition: action.h:35
int n
Definition: acutest.h:577
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition: base16.h:95
#define RCSID(id)
Definition: build.h:444
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition: build.h:320
#define UNUSED
Definition: build.h:313
CONF_SECTION * unlang_call_current(request_t *request)
Return the last virtual server that was called.
Definition: call.c:225
#define CONF_PARSER_TERMINATOR
Definition: cf_parse.h:626
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:268
#define FR_CONF_OFFSET_TYPE_FLAGS(_name, _type, _flags, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition: cf_parse.h:241
Defines a CONF_PAIR to C data type mapping.
Definition: cf_parse.h:563
Common header for all CONF_* types.
Definition: cf_priv.h:49
A section grouping multiple CONF_PAIR.
Definition: cf_priv.h:89
CONF_PAIR * cf_item_to_pair(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_PAIR.
Definition: cf_util.c:629
CONF_SECTION * cf_section_find(CONF_SECTION const *cs, char const *name1, char const *name2)
Find a CONF_SECTION with name1 and optionally name2.
Definition: cf_util.c:970
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition: cf_util.c:1511
#define cf_log_err(_cf, _fmt,...)
Definition: cf_util.h:265
eap_type_data_t type
Definition: compose.h:39
size_t length
Definition: compose.h:38
eap_packet_t * response
Packet we received from the peer.
Definition: compose.h:49
eap_code_t code
Definition: compose.h:36
uint8_t id
Definition: compose.h:37
eap_packet_t * request
Packet we will send to the peer.
Definition: compose.h:50
Contains a pair of request and response packets.
Definition: compose.h:48
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition: dbuff.h:509
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:137
#define RADIUSD_VERSION_STRING
Definition: dependency.h:39
fr_dict_attr_t * fr_dict_attr_unconst(fr_dict_attr_t const *da)
Coerce to non-const.
Definition: dict_util.c:4191
static fr_slen_t err
Definition: dict.h:645
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition: dict.h:250
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition: dict.h:263
int fr_dict_enum_add_name_next(fr_dict_attr_t *da, char const *name)
Add an name to an integer attribute hashing the name for the integer value.
Definition: dict_util.c:1547
fr_dict_enum_value_t * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition: dict_util.c:2992
Specifies an attribute which must be present for the module to function.
Definition: dict.h:249
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition: dict.h:262
Value of an enumerated attribute.
Definition: dict.h:209
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
CONF_SECTION *_CONST conf
Module's instance configuration.
Definition: dl_module.h:166
@ FR_EAP_CODE_FAILURE
Definition: types.h:40
@ FR_EAP_CODE_REQUEST
Definition: types.h:37
@ FR_EAP_CODE_SUCCESS
Definition: types.h:39
eap_type_t num
Definition: types.h:110
size_t length
Definition: types.h:111
uint8_t * data
Definition: types.h:112
@ FR_EAP_METHOD_MSCHAPV2
Definition: types.h:71
#define MSCHAPV2_CHALLENGE_LEN
Definition: eap_mschapv2.h:32
uint8_t ms_length[2]
Definition: eap_mschapv2.h:38
#define MSCHAPV2_HEADER_LEN
Definition: eap_mschapv2.h:31
#define MSCHAPV2_RESPONSE_LEN
Definition: eap_mschapv2.h:33
#define FR_EAP_MSCHAPV2_FAILURE
Definition: eap_mschapv2.h:27
#define FR_EAP_MSCHAPV2_CHALLENGE
Definition: eap_mschapv2.h:24
#define FR_EAP_MSCHAPV2_SUCCESS
Definition: eap_mschapv2.h:26
#define FR_EAP_MSCHAPV2_RESPONSE
Definition: eap_mschapv2.h:25
#define FR_EAP_MSCHAPV2_ACK
Definition: eap_mschapv2.h:23
#define FR_EAP_MSCHAPV2_CHGPASSWD
Definition: eap_mschapv2.h:28
rlm_rcode_t unlang_interpret_stack_result(request_t *request)
Get the current rcode for the frame.
Definition: interpret.c:1278
void * opaque
Opaque data used by EAP methods.
Definition: session.h:62
module_method_t process
Callback that should be used to process the next round.
Definition: session.h:64
void const * inst
Instance of the eap module this session was created by.
Definition: session.h:48
eap_round_t * this_round
The EAP response we're processing, and the EAP request we're building.
Definition: session.h:59
static eap_session_t * eap_session_get(request_t *request)
Definition: session.h:82
Tracks the progress of a single session of any EAP method.
Definition: session.h:40
void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix)
Print a fr_pair_list_t.
Definition: log.c:821
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:443
#define RWDEBUG(fmt,...)
Definition: log.h:361
#define RERROR(fmt,...)
Definition: log.h:298
#define RPERROR(fmt,...)
Definition: log.h:302
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:430
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition: log.h:71
unsigned short uint16_t
Definition: merged_model.c:31
@ FR_TYPE_STRING
String of printable characters.
Definition: merged_model.c:83
@ FR_TYPE_UINT32
32 Bit unsigned integer.
Definition: merged_model.c:99
@ FR_TYPE_VENDOR
Attribute that represents a vendor in the attribute tree.
Definition: merged_model.c:122
@ FR_TYPE_VOID
User data.
Definition: merged_model.c:127
@ FR_TYPE_OCTETS
Raw octets.
Definition: merged_model.c:84
unsigned char uint8_t
Definition: merged_model.c:30
void * rctx
Resume ctx that a module previously set.
Definition: module_ctx.h:45
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:42
Temporary structure to hold arguments for module calls.
Definition: module_ctx.h:41
Temporary structure to hold arguments for instantiation calls.
Definition: module_ctx.h:51
static uint16_t fr_nbo_to_uint16(uint8_t const data[static sizeof(uint16_t)])
Read an unsigned 16bit integer from wire format (big endian)
Definition: nbo.h:137
int fr_pair_list_copy_by_da(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from, fr_dict_attr_t const *da, unsigned int count)
Duplicate pairs in a list matching the specified da.
Definition: pair.c:2403
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition: pair.c:278
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition: pair.c:2316
int fr_pair_value_memdup(fr_pair_t *vp, uint8_t const *src, size_t len, bool tainted)
Copy data into an "octets" data type.
Definition: pair.c:2978
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition: pair.c:1340
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition: pair.c:46
fr_pair_t * fr_pair_afrom_da_nested(TALLOC_CTX *ctx, fr_pair_list_t *list, fr_dict_attr_t const *da)
Create a pair (and all intermediate parents), and append it to the list.
Definition: pair.c:462
int fr_pair_value_bstrndup(fr_pair_t *vp, char const *src, size_t len, bool tainted)
Copy data into a "string" type value pair.
Definition: pair.c:2781
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find a pair with a matching fr_dict_attr_t, by walking the nested fr_dict_attr_t tree.
Definition: pair.c:765
int fr_pair_value_mem_alloc(fr_pair_t *vp, uint8_t **out, size_t size, bool tainted)
Pre-allocate a memory buffer for a "octets" type value pair.
Definition: pair.c:2927
static const conf_parser_t config[]
Definition: base.c:188
#define pair_update_request(_attr, _da)
Definition: radclient-ng.c:60
#define REDEBUG(fmt,...)
Definition: radclient.h:52
#define RDEBUG2(fmt,...)
Definition: radclient.h:54
#define RDEBUG(fmt,...)
Definition: radclient.h:53
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition: rand.c:106
#define RETURN_MODULE_REJECT
Definition: rcode.h:55
#define RETURN_MODULE_HANDLED
Definition: rcode.h:58
#define RETURN_MODULE_INVALID
Definition: rcode.h:59
#define RETURN_MODULE_OK
Definition: rcode.h:57
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
@ RLM_MODULE_OK
The module is OK, continue.
Definition: rcode.h:43
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition: rcode.h:42
static fr_dict_attr_t const * attr_microsoft
static int eap_mschapv2_compose(rlm_eap_mschapv2_t const *inst, request_t *request, eap_session_t *eap_session, fr_pair_t *reply)
static unlang_action_t mod_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
static unlang_action_t mschap_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
static fr_dict_attr_t const * attr_state
static void mppe_keys_store(request_t *request, mschapv2_opaque_t *data)
static fr_dict_attr_t const * attr_ms_mppe_encryption_type
static fr_dict_t const * dict_freeradius
static int auth_type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
fr_dict_enum_value_t * auth_type
static fr_dict_attr_t const * attr_ms_chap2_success
static fr_dict_t const * dict_radius
static fr_dict_attr_t const * attr_ms_chap_challenge
static fr_dict_attr_t const * attr_auth_type
static fr_dict_attr_t const * attr_ms_chap_user_name
static fr_dict_attr_t const * attr_ms_chap_error
static fr_dict_attr_t const * attr_ms_mppe_send_key
static fr_dict_attr_t const * attr_ms_chap2_response
fr_dict_autoload_t rlm_eap_mschapv2_dict[]
static fr_dict_attr_t const * attr_user_name
fr_dict_attr_autoload_t rlm_eap_mschapv2_dict_attr[]
static fr_dict_attr_t const * attr_ms_mppe_recv_key
static conf_parser_t submodule_config[]
char const * identity
static fr_dict_attr_t const * attr_ms_chap_nt_enc_pw
rlm_eap_submodule_t rlm_eap_mschapv2
static fr_dict_attr_t const * attr_ms_chap_peer_challenge
static int mod_instantiate(module_inst_ctx_t const *mctx)
static fr_dict_attr_t const * attr_ms_chap2_cpw
static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
static fr_dict_attr_t const * attr_ms_mppe_encryption_policy
static char const * name
static int instantiate(module_inst_ctx_t const *mctx)
Definition: rlm_rest.c:1312
#define FR_SBUFF_IN(_start, _len_or_end)
unlang_action_t unlang_module_yield_to_section(rlm_rcode_t *p_result, request_t *request, CONF_SECTION *subcs, rlm_rcode_t default_rcode, module_method_t resume, unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
Definition: module.c:516
RETURN_MODULE_FAIL
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
eap_aka_sim_process_conf_t * inst
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition: pair.h:68
fr_dict_attr_t const *_CONST da
Dictionary attribute defines the attribute number, vendor and type of the pair.
Definition: pair.h:69
module_t common
Common fields provided by all modules.
Definition: submodule.h:50
Interface exported by EAP submodules.
Definition: submodule.h:49
char * talloc_typed_asprintf(TALLOC_CTX *ctx, char const *fmt,...)
Call talloc vasprintf, setting the type on the new chunk correctly.
Definition: talloc.c:380
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition: pair_inline.c:43
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
Definition: pair_inline.c:125
#define PAIR_VERIFY(_x)
Definition: pair.h:190
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
Definition: pair_inline.c:113
static fr_slen_t parent
Definition: pair.h:844
static fr_slen_t data
Definition: value.h:1259
int nonnull(2, 5))
static size_t char ** out
Definition: value.h:984