The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
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: 7287a7b3a7317e633d3b64b0894959a5c8c71d00 $
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
23RCSID("$Id: 7287a7b3a7317e633d3b64b0894959a5c8c71d00 $")
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/util/base16.h>
30#include <freeradius-devel/util/debug.h>
31#include <freeradius-devel/util/rand.h>
32
33#include "eap_mschapv2.h"
34
35static int auth_type_parse(TALLOC_CTX *ctx, void *out, UNUSED void *parent,
36 CONF_ITEM *ci, UNUSED conf_parser_t const *rule);
37
44
48
50 { FR_CONF_OFFSET("with_ntdomain_hack", rlm_eap_mschapv2_t, with_ntdomain_hack), .dflt = "no" },
51
52 { FR_CONF_OFFSET_TYPE_FLAGS("auth_type", FR_TYPE_VOID, 0, rlm_eap_mschapv2_t, auth_type), .func = auth_type_parse, .dflt = "mschap" },
53 { FR_CONF_OFFSET("send_error", rlm_eap_mschapv2_t, send_error), .dflt = "no" },
54 { FR_CONF_OFFSET("identity", rlm_eap_mschapv2_t, identity) },
56};
57
59static fr_dict_t const *dict_radius;
60
63 { .out = &dict_freeradius, .proto = "freeradius" },
64 { .out = &dict_radius, .proto = "radius" },
66};
67
71
73
85
88 { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
89 { .out = &attr_ms_chap_peer_challenge, .name = "MS-CHAP-Peer-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
90 { .out = &attr_ms_chap_user_name, .name = "MS-CHAP-User-Name", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
91
92 { .out = &attr_microsoft, .name = "Vendor-Specific.Microsoft", .type = FR_TYPE_VENDOR, .dict = &dict_radius },
93
94 { .out = &attr_ms_chap_challenge, .name = "Vendor-Specific.Microsoft.CHAP-Challenge", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
95 { .out = &attr_ms_chap_error, .name = "Vendor-Specific.Microsoft.CHAP-Error", .type = FR_TYPE_STRING, .dict = &dict_radius },
96 { .out = &attr_ms_chap_nt_enc_pw, .name = "Vendor-Specific.Microsoft.CHAP-NT-Enc-PW", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
97 { .out = &attr_ms_chap2_cpw, .name = "Vendor-Specific.Microsoft.CHAP2-CPW", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
98 { .out = &attr_ms_chap2_response, .name = "Vendor-Specific.Microsoft.CHAP2-Response", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
99 { .out = &attr_ms_chap2_success, .name = "Vendor-Specific.Microsoft.CHAP2-Success", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
100 { .out = &attr_ms_mppe_encryption_policy, .name = "Vendor-Specific.Microsoft.MPPE-Encryption-Policy", .type = FR_TYPE_UINT32, .dict = &dict_radius },
101 { .out = &attr_ms_mppe_encryption_type, .name = "Vendor-Specific.Microsoft.MPPE-Encryption-Type", .type = FR_TYPE_UINT32, .dict = &dict_radius },
102 { .out = &attr_ms_mppe_send_key, .name = "Vendor-Specific.Microsoft.MPPE-Send-Key", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
103 { .out = &attr_ms_mppe_recv_key, .name = "Vendor-Specific.Microsoft.MPPE-Recv-Key", .type = FR_TYPE_OCTETS, .dict = &dict_radius },
104 { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
106};
107
109{
111
112 RDEBUG2("Storing attributes for final response");
113
115 if (!parent) parent = request->reply_ctx;
116
117 RINDENT();
118 if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
121 }
122 if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
125 }
126 if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
128 RDEBUG2("%s", attr_ms_mppe_recv_key->name);
129 }
130 if (fr_pair_list_copy_by_da(data, &data->mppe_keys, &parent->vp_group,
132 RDEBUG2("%s", attr_ms_mppe_send_key->name);
133 }
134 REXDENT();
135}
136
137/** Translate a string auth_type into an enumeration value
138 *
139 * @param[in] ctx to allocate data.
140 * @param[out] out Where to write the auth_type we created or resolved.
141 * @param[in] parent Base structure address.
142 * @param[in] ci #CONF_PAIR specifying the name of the auth_type.
143 * @param[in] rule unused.
144 * @return
145 * - 0 on success.
146 * - -1 on failure.
147 */
148static int auth_type_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
149 CONF_ITEM *ci, UNUSED conf_parser_t const *rule)
150{
151 char const *auth_type = cf_pair_value(cf_item_to_pair(ci));
152
154 cf_log_err(ci, "Failed adding %s alias", attr_auth_type->name);
155 return -1;
156 }
158
159 return 0;
160}
161
162/*
163 * Compose the response.
164 */
166 fr_pair_t *reply) CC_HINT(nonnull);
169{
170 uint8_t *ptr;
171 int16_t length;
173 eap_round_t *eap_round = eap_session->this_round;
174
175 eap_round->request->code = FR_EAP_CODE_REQUEST;
177
178 /*
179 * Always called with vendor Microsoft
180 */
181 if (reply->da == attr_ms_chap_challenge) {
182 /*
183 * 0 1 2 3
184 * 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
185 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
186 * | Code | Identifier | Length |
187 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
188 * | Type | OpCode | MS-CHAPv2-ID | MS-Length...
189 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
190 * | MS-Length | Value-Size | Challenge...
191 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
192 * | Challenge...
193 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
194 * | Server Name...
195 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
196 */
198 MEM(eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length));
199
200 /*
201 * Allocate room for the EAP-MS-CHAPv2 data.
202 */
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_strlen(inst->identity)));
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
235 /*
236 * One byte of ident, and the 42 byte authenticator response.
237 */
238 if (reply->vp_length != 43) {
239 REDEBUG("%s must be 43 bytes, got %zu", reply->da->name, reply->vp_length);
240 return -1;
241 }
242 length = 46;
243 MEM(eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length));
244
245 /*
246 * Allocate room for the EAP-MS-CHAPv2 data.
247 */
248 memset(eap_round->request->type.data, 0, length);
249 eap_round->request->type.length = length;
250
251 eap_round->request->type.data[0] = FR_EAP_MSCHAPV2_SUCCESS;
252 eap_round->request->type.data[1] = eap_round->response->id;
253 length = htons(length);
254 memcpy((eap_round->request->type.data + 2), &length, sizeof(uint16_t));
255 memcpy((eap_round->request->type.data + 4), reply->vp_strvalue + 1, 42);
256 } else if (reply->da == attr_ms_chap_error) {
257 REDEBUG("MS-CHAPv2 Failure");
258
259 /*
260 * One byte of ident, then the message, which must fit
261 * after the 4 byte header in a 16 bit length.
262 */
263 if ((reply->vp_length < 1) || (reply->vp_length > (UINT16_MAX - 3))) {
264 REDEBUG("%s length %zu is invalid", reply->da->name, reply->vp_length);
265 return -1;
266 }
267 length = 4 + reply->vp_length - 1;
268 MEM(eap_round->request->type.data = talloc_array(eap_round->request, uint8_t, length));
269
270 /*
271 * Allocate room for the EAP-MS-CHAPv2 data.
272 */
273 if (!eap_round->request->type.data) return 0;
274 memset(eap_round->request->type.data, 0, length);
275 eap_round->request->type.length = length;
276
277 eap_round->request->type.data[0] = FR_EAP_MSCHAPV2_FAILURE;
278 eap_round->request->type.data[1] = eap_round->response->id;
279 length = htons(length);
280 memcpy((eap_round->request->type.data + 2), &length, sizeof(uint16_t));
281 /*
282 * Copy the entire failure message.
283 */
284 memcpy((eap_round->request->type.data + 4), reply->vp_strvalue + 1, reply->vp_length - 1);
285 } else {
286 RERROR("%s: Internal sanity check failed", __FUNCTION__);
287 return -1;
288 }
289
290 return 0;
291}
292
293
294static unlang_action_t CC_HINT(nonnull) mod_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request);
295
297{
298 rlm_eap_mschapv2_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_eap_mschapv2_rctx_t);
299 eap_session_t *eap_session = eap_session_get(request->parent);
300 mschapv2_opaque_t *data = talloc_get_type_abort(eap_session->opaque, mschapv2_opaque_t);
301 eap_round_t *eap_round = eap_session->this_round;
302 fr_pair_list_t response;
303 rlm_eap_mschapv2_t const *inst = mctx->mi->data;
304 rlm_rcode_t rcode = rctx->section_result.rcode;
306
307 fr_pair_list_init(&response);
308
309 /*
310 * Delete MPPE keys & encryption policy. We don't
311 * want these here.
312 */
314
316 if (!parent) parent = request->reply_ctx;
317
318 /*
319 * Take the response from the mschap module, and
320 * return success or failure, depending on the result.
321 */
322 if (rcode == RLM_MODULE_OK) {
323 if (fr_pair_list_copy_by_da(data, &response, &parent->vp_group, attr_ms_chap2_success) < 0) {
324 RPERROR("Failed copying %s", attr_ms_chap2_success->name);
326 }
327
329 } else if (inst->send_error) {
330 if (fr_pair_list_copy_by_da(data, &response, &parent->vp_group, attr_ms_chap_error) < 0) {
331 RPERROR("Failed copying %s", attr_ms_chap_error->name);
333 }
334 if (!fr_pair_list_empty(&response)) {
335 int n, err, retry;
336 char buf[34];
337 fr_pair_t *vp = fr_pair_list_head(&response);
338
340
341 RDEBUG2("MSCHAP-Error: %pV", &vp->data);
342
343 /*
344 * Parse the new challenge out of the
345 * MS-CHAP-Error, so that if the client
346 * issues a re-try, we will know which
347 * challenge value that they used.
348 */
349 n = sscanf(vp->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
350 if (n == 3) {
351 RDEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s",
352 err, retry, buf);
353 if (unlikely(fr_base16_decode(NULL,
354 &FR_DBUFF_TMP(data->auth_challenge,
355 sizeof(data->auth_challenge)),
356 &FR_SBUFF_IN_STR(buf),
357 false) != (fr_slen_t)sizeof(data->auth_challenge))) {
358 REDEBUG("Failed parsing new challenge value %s", buf);
359 invalid:
360 fr_pair_list_free(&response);
361 eap_round->request->code = FR_EAP_CODE_FAILURE;
363 }
364 } else {
365 RDEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
366 }
367 }
369 } else {
370 eap_round->request->code = FR_EAP_CODE_FAILURE;
372 }
373
374 /*
375 * No response, die.
376 */
377 if (fr_pair_list_empty(&response)) {
378 REDEBUG("No %s or %s attributes were found", attr_ms_chap2_success->name, attr_ms_chap_error->name);
379 goto invalid;
380 }
381
382 /*
383 * Compose the response (whatever it is),
384 * and return it to the over-lying EAP module.
385 */
386 if (eap_mschapv2_compose(eap_session->inst, request, eap_session, fr_pair_list_head(&response)) < 0) {
387 fr_pair_list_free(&response);
389 }
390 fr_pair_list_free(&response);
391
393}
394
395/*
396 * Authenticate a previously sent challenge.
397 */
399{
400 rlm_eap_mschapv2_t const *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_mschapv2_t);
401 rlm_eap_mschapv2_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_eap_mschapv2_rctx_t);
402 request_t *parent = request->parent;
403 eap_session_t *eap_session = eap_session_get(parent);
404 mschapv2_opaque_t *data = talloc_get_type_abort(eap_session->opaque, mschapv2_opaque_t);
405 eap_round_t *eap_round = eap_session->this_round;
406 fr_pair_t *auth_challenge, *response, *name;
407
408 CONF_SECTION *unlang;
409 int ccode;
410 uint8_t *p;
411 size_t length;
412
413 if (!fr_cond_assert(eap_session->inst)) RETURN_UNLANG_FAIL;
414
415 /*
416 * Sanity check the response.
417 */
418 if (eap_round->response->length < 6) {
419 REDEBUG("Response too short, expected at least 6 bytes, got %zu bytes",
420 eap_round->response->length);
422 }
423
424 ccode = eap_round->response->type.data[0];
425
426 switch (data->code) {
428 if (ccode == FR_EAP_MSCHAPV2_RESPONSE) {
429 RDEBUG2("Authentication re-try from client after we sent a failure");
430 break;
431 }
432
433 /*
434 * if we sent error 648 (password expired) to the client
435 * we might get an MSCHAP-CPW packet here; turn it into a
436 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
437 * (or proxy it, I guess)
438 */
439 if (ccode == FR_EAP_MSCHAPV2_CHGPASSWD) {
440 fr_pair_t *cpw;
441 int mschap_id;
442 int copied = 0;
443 int seq = 1;
444 fr_pair_t *ms;
445
446
447 if (eap_round->response->type.length < 586) {
448 RDEBUG2("Password change has invalid length %zu < 586",
449 eap_round->response->type.length);
451 }
452
453 RDEBUG2("Password change packet received");
454
455 MEM(pair_update_request(&auth_challenge, attr_ms_chap_challenge) >= 0);
456 fr_pair_value_memdup(auth_challenge, data->auth_challenge, MSCHAPV2_CHALLENGE_LEN, false);
457
458 mschap_id = eap_round->response->type.data[1];
459
461 MEM(fr_pair_value_mem_alloc(cpw, &p, 68, false) == 0);
462 p[0] = 7;
463 p[1] = mschap_id;
464 memcpy(p + 2, eap_round->response->type.data + 520, 66);
465
466 ms = fr_pair_find_by_da_nested(&request->request_pairs, attr_microsoft);
467 if (!ms) ms = request->request_ctx;
468
469 /*
470 * break the encoded password into VPs (3 of them)
471 */
472 while (copied < 516) {
473 fr_pair_t *nt_enc;
474
475 int to_copy = 516 - copied;
476 if (to_copy > 243) to_copy = 243;
477
479 MEM(fr_pair_value_mem_alloc(nt_enc, &p, 4 + to_copy, false) == 0);
480 MEM(fr_pair_append(&ms->vp_group, nt_enc) == 0);
481 p[0] = 6;
482 p[1] = mschap_id;
483 p[2] = 0;
484 p[3] = seq++;
485 memcpy(p + 4, eap_round->response->type.data + 4 + copied, to_copy);
486
487 copied += to_copy;
488 }
489
490 RDEBUG2("Built change password packet");
491 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
492
493 /*
494 * jump to "authentication"
495 */
496 goto packet_ready;
497 }
498
499 /*
500 * we sent a failure and are expecting a failure back
501 */
502 if (ccode != FR_EAP_MSCHAPV2_FAILURE) {
503 REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode);
505 }
506
507failure:
508 eap_round->request->code = FR_EAP_CODE_FAILURE;
510
512 /*
513 * we sent a success to the client; some clients send a
514 * success back as-per the RFC, some send an ACK. Permit
515 * both, I guess...
516 */
517
518 switch (ccode) {
520 eap_round->request->code = FR_EAP_CODE_SUCCESS;
521
522 if (!fr_pair_list_empty(&data->mppe_keys)) {
523 fr_pair_t *ms;
524
526 if (!ms) {
527 MEM(ms = fr_pair_afrom_da_nested(parent->reply_ctx, &parent->reply_pairs, attr_microsoft));
528 }
529
530 RDEBUG2("Adding stored attributes to parent");
531 log_request_pair_list(L_DBG_LVL_2, request, NULL, &data->mppe_keys, "parent.reply.");
532 MEM(fr_pair_list_copy(ms, &ms->vp_group, &data->mppe_keys) >= 0);
533 } else {
534 RDEBUG2("No stored attributes to copy to parent");
535 }
536
538
540 MEM(fr_pair_list_copy(parent->reply_ctx, &parent->reply_pairs, &data->reply) >= 0);
542 }
543 REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
545
547 if (ccode == FR_EAP_MSCHAPV2_FAILURE) goto failure;
548
549 /*
550 * we sent a challenge, expecting a response
551 */
552 if (ccode != FR_EAP_MSCHAPV2_RESPONSE) {
553 REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
555 }
556 /* authentication happens below */
557 break;
558
559 default:
560 /* should never happen */
561 REDEBUG("Unknown state %d", data->code);
563 }
564
565
566 /*
567 * Ensure that we have at least enough data
568 * to do the following checks.
569 *
570 * EAP header (4), EAP type, MS-CHAP opcode,
571 * MS-CHAP ident, MS-CHAP data length (2),
572 * MS-CHAP value length.
573 */
574 if (eap_round->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
575 REDEBUG("Response is too short");
577 }
578
579 /*
580 * The 'value_size' is the size of the response,
581 * which is supposed to be the response (48
582 * bytes) plus 1 byte of flags at the end.
583 *
584 * NOTE: When using Cisco NEAT with EAP-MSCHAPv2, the
585 * switch supplicant will send MSCHAPv2 data (EAP type = 26)
586 * but will always set a value_size of 16 and NULL out the
587 * peer challenge.
588 *
589 */
590 if ((eap_round->response->type.data[4] != 49) &&
591 (eap_round->response->type.data[4] != 16)) {
592 REDEBUG("Response is of incorrect length %d", eap_round->response->type.data[4]);
594 }
595
596 /*
597 * The MS-Length field is 5 + value_size + length
598 * of name, which is put after the response.
599 */
600 if (eap_round->response->type.length < (5 + MSCHAPV2_RESPONSE_LEN + 1)) {
601 REDEBUG("MS-CHAPv2 response packet is too short %zu < %d",
602 eap_round->response->type.length, 5 + MSCHAPV2_RESPONSE_LEN + 1);
604 }
605
606 length = fr_nbo_to_uint16(eap_round->response->type.data + 2);
607 if ((length < (5 + 49)) || (length > (256 + 5 + 49))) {
608 REDEBUG("Response contains contradictory length %zu %d", length, 5 + 49);
610 }
611
612 if (eap_round->response->type.length < length) {
613 REDEBUG("Response type data (%zu bytes) is shorter than claimed MS-Length (%zu)",
614 eap_round->response->type.length, length);
616 }
617
618 /*
619 * We now know that the user has sent us a response
620 * to the challenge. Let's try to authenticate it.
621 *
622 * We do this by taking the challenge from 'data',
623 * the response from the EAP packet, and creating fr_pair_t's
624 * to pass to the 'mschap' module. This is a little wonky,
625 * but it works.
626 */
627 MEM(pair_update_request(&auth_challenge, attr_ms_chap_challenge) >= 0);
628 fr_pair_value_memdup(auth_challenge, data->auth_challenge, MSCHAPV2_CHALLENGE_LEN, false);
629
631 MEM(fr_pair_value_mem_alloc(response, &p, MSCHAPV2_RESPONSE_LEN, false) == 0);
632 p[0] = eap_round->response->type.data[1];
633 p[1] = eap_round->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];
634 memcpy(p + 2, &eap_round->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2);
635
636 /*
637 * If we're forcing a peer challenge, use it instead of
638 * the challenge sent by the client.
639 */
640 if (data->has_peer_challenge) memcpy(p + 2, data->peer_challenge, MSCHAPV2_CHALLENGE_LEN);
641
642 /*
643 * MS-Length - MS-Value - 5.
644 */
646 MEM(fr_pair_value_bstrndup(name, (char const *)&eap_round->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
647 length - 49 - 5, true) == 0);
648packet_ready:
649
650 /*
651 * Look for "authenticate foo" in the current virtual
652 * server. If not there, then in the parent one.
653 */
654 RDEBUG("Looking for authenticate %s { ... }", inst->auth_type->name);
655 unlang = cf_section_find(unlang_call_current(parent), "authenticate", inst->auth_type->name);
656 if (!unlang) unlang = cf_section_find(unlang_call_current(request->parent), "authenticate", inst->auth_type->name);
657 if (!unlang) {
658 RDEBUG2("authenticate %s { ... } sub-section not found.",
659 inst->auth_type->name);
661 }
662
664}
665
666/*
667 * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer.
668 */
670{
671 request_t *parent = request->parent;
672 eap_session_t *eap_session = eap_session_get(parent);
673 fr_pair_t *auth_challenge;
674 fr_pair_t *peer_challenge;
676
677 uint8_t *p;
678 int i;
679 bool created_auth_challenge;
680
682
683 /*
684 * We're looking for attributes that should come
685 * from the EAP-TTLS submodule.
686 */
688
689 /*
690 * Keep track of the challenge and the state we are in.
691 */
692 MEM(data = talloc_zero(eap_session, mschapv2_opaque_t));
694 fr_pair_list_init(&data->mppe_keys);
695 fr_pair_list_init(&data->reply);
696
697 /*
698 * Allow the administrator to set the CHAP-Challenge and Peer-Challenge attributes.
699 */
700 auth_challenge = fr_pair_find_by_da_nested(&parent->control_pairs, attr_ms_chap_challenge);
701 if (auth_challenge && (auth_challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
702 RWDEBUG("parent.control.MS-CHAP-Challenge is incorrect length. Ignoring it");
703 auth_challenge = NULL;
704 }
705
706 peer_challenge = fr_pair_find_by_da_nested(&parent->control_pairs, attr_ms_chap_peer_challenge);
707 if (peer_challenge && (peer_challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) {
708 RWDEBUG("parent.control.MS-CHAP-Peer-Challenge is incorrect length. Ignoring it");
709 peer_challenge = NULL;
710 }
711
712 created_auth_challenge = (auth_challenge == NULL);
713
714 /*
715 * if the administrator didn't set a challenge, then create one ourselves.
716 */
717 if (!auth_challenge) {
718 MEM(auth_challenge = fr_pair_afrom_da(eap_session, attr_ms_chap_challenge));
719 MEM(fr_pair_value_mem_alloc(auth_challenge, &p, MSCHAPV2_CHALLENGE_LEN, false) == 0);
720 for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) p[i] = fr_rand();
721 }
722 RDEBUG2("Issuing Challenge");
723
724 /*
725 * We're at the stage where we're challenging the user.
726 */
727 memcpy(data->auth_challenge, auth_challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
728
729 if (peer_challenge) {
730 data->has_peer_challenge = true;
731 memcpy(data->peer_challenge, peer_challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN);
732 }
733
734 eap_session->opaque = data;
735
736 /*
737 * Compose the EAP-MSCHAPV2 packet out of the data structure,
738 * and free it.
739 */
740 eap_mschapv2_compose(mctx->mi->data, request, eap_session, auth_challenge);
741 if (created_auth_challenge) TALLOC_FREE(auth_challenge);
742
743 /*
744 * We don't need to authorize the user at this point.
745 *
746 * We also don't need to keep the challenge, as it's
747 * stored in 'eap_session->this_round', which will be given back
748 * to us...
749 */
750 eap_session->process = mod_process;
751
753}
754
755/*
756 * Attach the module.
757 */
758static int mod_instantiate(module_inst_ctx_t const *mctx)
759{
760 rlm_eap_mschapv2_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_mschapv2_t);
761
762 if (inst->identity && (strlen(inst->identity) > 255)) {
763 cf_log_err(mctx->mi->conf, "identity is too long");
764 return -1;
765 }
766
767 if (!inst->identity) inst->identity = talloc_typed_asprintf(inst, "freeradius-%s", RADIUSD_VERSION_STRING);
768
769 return 0;
770}
771
772/*
773 * The module name should be the only globally exported symbol.
774 * That is, everything else should be 'static'.
775 */
778 .common = {
779 .name = "eap_mschapv2",
780 .magic = MODULE_MAGIC_INIT,
784 .instantiate = mod_instantiate, /* Create new submodule instance */
785 },
786 .provides = { FR_EAP_METHOD_MSCHAPV2 },
787 .session_init = mod_session_init, /* Initialise a new EAP session */
788 .clone_parent_lists = false /* HACK */
789};
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:642
#define fr_base16_decode(_err, _out, _in, _no_trailing)
Definition base16.h:109
static uint8_t request[4]
static uint8_t reply[4]
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:203
#define RCSID(id)
Definition build.h:603
#define FALL_THROUGH
clang 10 doesn't recognised the FALL-THROUGH comment anymore
Definition build.h:422
#define unlikely(_x)
Definition build.h:486
#define UNUSED
Definition build.h:415
CONF_SECTION * unlang_call_current(request_t *request)
Return the last virtual server that was called.
Definition call.c:226
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:712
cf_parse_t func
Override default parsing behaviour for the specified type with a custom parsing function.
Definition cf_parse.h:666
#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:280
#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:238
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:649
Common header for all CONF_* types.
Definition cf_priv.h:54
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:106
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:1247
CONF_PAIR * cf_item_to_pair(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_PAIR.
Definition cf_util.c:651
char const * cf_pair_value(CONF_PAIR const *pair)
Return the value of a CONF_PAIR.
Definition cf_util.c:1758
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:340
eap_type_data_t type
Definition compose.h:38
size_t length
Definition compose.h:37
eap_packet_t * response
Packet we received from the peer.
Definition compose.h:48
eap_code_t code
Definition compose.h:35
uint8_t id
Definition compose.h:36
eap_packet_t * request
Packet we will send to the peer.
Definition compose.h:49
Contains a pair of request and response packets.
Definition compose.h:47
#define FR_DBUFF_TMP(_start, _len_or_end)
Creates a compound literal to pass into functions which accept a dbuff.
Definition dbuff.h:556
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:177
#define MEM(x)
Definition debug.h:38
#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:5052
fr_dict_attr_t const ** out
Where to write a pointer to the resolved fr_dict_attr_t.
Definition dict.h:316
fr_dict_t const ** out
Where to write a pointer to the loaded/resolved fr_dict_t.
Definition dict.h:329
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:2281
#define DICT_AUTOLOAD_TERMINATOR
Definition dict.h:335
fr_dict_enum_value_t const * fr_dict_enum_by_name(fr_dict_attr_t const *da, char const *name, ssize_t len)
Definition dict_util.c:3778
Specifies an attribute which must be present for the module to function.
Definition dict.h:315
Specifies a dictionary which must be loaded/loadable for the module to function.
Definition dict.h:328
Value of an enumerated attribute.
Definition dict.h:277
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
@ FR_EAP_METHOD_MSCHAPV2
Definition types.h:71
eap_type_t num
Definition types.h:110
size_t length
Definition types.h:111
uint8_t * data
Definition types.h:112
@ FR_EAP_CODE_FAILURE
Definition types.h:40
@ FR_EAP_CODE_REQUEST
Definition types.h:37
@ FR_EAP_CODE_SUCCESS
Definition types.h:39
#define MSCHAPV2_CHALLENGE_LEN
uint8_t ms_length[2]
#define MSCHAPV2_HEADER_LEN
#define MSCHAPV2_RESPONSE_LEN
#define FR_EAP_MSCHAPV2_FAILURE
#define FR_EAP_MSCHAPV2_CHALLENGE
#define FR_EAP_MSCHAPV2_SUCCESS
#define FR_EAP_MSCHAPV2_RESPONSE
#define FR_EAP_MSCHAPV2_ACK
#define FR_EAP_MSCHAPV2_CHGPASSWD
rlm_rcode_t rcode
The current rcode, from executing the instruction or merging the result from a frame.
Definition interpret.h:140
static eap_session_t * eap_session_get(request_t *request)
Definition session.h:85
void * opaque
Opaque data used by EAP methods.
Definition session.h:63
module_method_t process
Callback that should be used to process the next round.
Definition session.h:65
void const * inst
Instance of the eap module this session was created by.
Definition session.h:49
eap_round_t * this_round
The EAP response we're processing, and the EAP request we're building.
Definition session.h:60
Tracks the progress of a single session of any EAP method.
Definition session.h:41
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:831
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition log.h:460
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RERROR(fmt,...)
Definition log.h:315
#define RPERROR(fmt,...)
Definition log.h:319
#define RINDENT()
Indent R* messages by one level.
Definition log.h:447
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:68
unsigned short uint16_t
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_UINT32
32 Bit unsigned integer.
@ FR_TYPE_VENDOR
Attribute that represents a vendor in the attribute tree.
@ FR_TYPE_VOID
User data.
@ FR_TYPE_OCTETS
Raw octets.
unsigned char uint8_t
ssize_t fr_slen_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
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:50
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:146
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:2303
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:2897
fr_pair_t * fr_pair_find_by_da_nested(fr_pair_list_t const *list, 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:782
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:1297
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:284
void fr_pair_list_init(fr_pair_list_t *list)
Initialise a pair list header.
Definition pair.c:47
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:2747
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:2846
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:477
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)
Duplicate pairs in a list matching the specified da.
Definition pair.c:2391
#define pair_update_request(_attr, _da)
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RDEBUG(fmt,...)
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:128
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:44
@ RLM_MODULE_OK
The module is OK, continue.
Definition rcode.h:49
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:48
#define RETURN_UNLANG_HANDLED
Definition rcode.h:71
#define RETURN_UNLANG_INVALID
Definition rcode.h:72
#define RETURN_UNLANG_FAIL
Definition rcode.h:69
#define RETURN_UNLANG_REJECT
Definition rcode.h:68
#define RETURN_UNLANG_OK
Definition rcode.h:70
static fr_dict_attr_t const * attr_microsoft
static unlang_action_t mschap_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static int eap_mschapv2_compose(rlm_eap_mschapv2_t const *inst, request_t *request, eap_session_t *eap_session, fr_pair_t *reply)
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 unlang_action_t mod_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
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
unlang_result_t section_result
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[]
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 unlang_action_t mod_session_init(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
static int mod_instantiate(module_inst_ctx_t const *mctx)
static fr_dict_attr_t const * attr_ms_chap2_cpw
static fr_dict_attr_t const * attr_ms_mppe_encryption_policy
static char const * name
#define FR_SBUFF_IN_STR(_start)
CONF_SECTION * conf
Module's instance configuration.
Definition module.h:355
void * data
Module's instance data.
Definition module.h:297
#define MODULE_RCTX(_ctype)
Definition module.h:262
#define MODULE_INST(_ctype)
Definition module.h:260
conf_parser_t const * config
How to convert a CONF_SECTION to a module instance.
Definition module.h:207
@ FR_SIGNAL_NONE
No signals.
Definition signal.h:39
unlang_action_t unlang_module_yield_to_section(unlang_result_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:236
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
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:621
static size_t talloc_strlen(char const *s)
Returns the length of a talloc array containing a string.
Definition talloc.h:143
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
#define PAIR_VERIFY(_x)
Definition pair.h:209
void fr_pair_list_free(fr_pair_list_t *list)
Free memory used by a valuepair list.
fr_pair_t * fr_pair_list_head(fr_pair_list_t const *list)
Get the head of a valuepair list.
Definition pair_inline.c:42
static fr_slen_t parent
Definition pair.h:864
static fr_slen_t data
Definition value.h:1366
static fr_sbuff_err_t char ** out
Definition value.h:1061
int nonnull(2, 5))