diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/modules/rlm_eap/types/rlm_eap_mschapv2 | |
parent | Initial commit. (diff) | |
download | freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.tar.xz freeradius-b95e0cd7685e6bbc2465b1f4efe1884df2f4ef01.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
4 files changed, 833 insertions, 0 deletions
diff --git a/src/modules/rlm_eap/types/rlm_eap_mschapv2/README.md b/src/modules/rlm_eap/types/rlm_eap_mschapv2/README.md new file mode 100644 index 0000000..4d87c49 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_mschapv2/README.md @@ -0,0 +1,13 @@ +# rlm_eap_mschapv2 +## Metadata +<dl> + <dt>category</dt><dd>authentication</dd> +</dl> + +## Summary +Implements EAP-MSCHAPv2. Usually used as an inner method for PEAP. + +Allows NTLMv2 style authentication against Active-Directory, or where the NT-Password is known. + +Technically does provide its own keying material via MPPE key attributes which could be used for 802.11i +(WPA/2-Enterprise) but in most instances, the keying material from an outer method is used instead. diff --git a/src/modules/rlm_eap/types/rlm_eap_mschapv2/all.mk b/src/modules/rlm_eap/types/rlm_eap_mschapv2/all.mk new file mode 100644 index 0000000..d57c636 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_mschapv2/all.mk @@ -0,0 +1,12 @@ +TARGETNAME := rlm_eap_mschapv2 + +ifneq "$(TARGETNAME)" "" +TARGET := $(TARGETNAME).a +endif + +SOURCES := $(TARGETNAME).c + +SRC_CFLAGS := +TGT_LDLIBS := +SRC_INCDIRS := ../../ ../../libeap/ +TGT_PREREQS := libfreeradius-eap.a diff --git a/src/modules/rlm_eap/types/rlm_eap_mschapv2/eap_mschapv2.h b/src/modules/rlm_eap/types/rlm_eap_mschapv2/eap_mschapv2.h new file mode 100644 index 0000000..1ce2470 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_mschapv2/eap_mschapv2.h @@ -0,0 +1,51 @@ +#ifndef _EAP_MSCHAPV2_H +#define _EAP_MSCHAPV2_H + +RCSIDH(eap_mschapv2_h, "$Id$") + +#include "eap.h" + +/* + * draft-kamath-pppext-eap-mschapv2-00.txt says: + * + * Supplicant FreeRADIUS + * <-- challenge + * response --> + * <-- success + * success --> + * + * But what we often see is: + * + * Supplicant FreeRADIUS + * <-- challenge + * response --> + * <-- success + * ack --> + */ +#define PW_EAP_MSCHAPV2_ACK 0 +#define PW_EAP_MSCHAPV2_CHALLENGE 1 +#define PW_EAP_MSCHAPV2_RESPONSE 2 +#define PW_EAP_MSCHAPV2_SUCCESS 3 +#define PW_EAP_MSCHAPV2_FAILURE 4 +#define PW_EAP_MSCHAPV2_CHGPASSWD 7 +#define PW_EAP_MSCHAPV2_MAX_CODES 7 + +#define MSCHAPV2_HEADER_LEN 5 +#define MSCHAPV2_CHALLENGE_LEN 16 +#define MSCHAPV2_RESPONSE_LEN 50 + +typedef struct mschapv2_header_t { + uint8_t opcode; + uint8_t mschapv2_id; + uint8_t ms_length[2]; + uint8_t value_size; +} mschapv2_header_t; + +typedef struct mschapv2_opaque_t { + int code; + uint8_t challenge[MSCHAPV2_CHALLENGE_LEN]; + VALUE_PAIR *mppe_keys; + VALUE_PAIR *reply; +} mschapv2_opaque_t; + +#endif /*_EAP_MSCHAPV2_H*/ diff --git a/src/modules/rlm_eap/types/rlm_eap_mschapv2/rlm_eap_mschapv2.c b/src/modules/rlm_eap/types/rlm_eap_mschapv2/rlm_eap_mschapv2.c new file mode 100644 index 0000000..c1a0045 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_mschapv2/rlm_eap_mschapv2.c @@ -0,0 +1,757 @@ +/* + * rlm_eap_mschapv2.c Handles that are called from eap + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2003,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include <stdio.h> +#include <stdlib.h> + +#include "eap_mschapv2.h" + +#include <freeradius-devel/rad_assert.h> + +typedef struct rlm_eap_mschapv2_t { + bool with_ntdomain_hack; + bool send_error; + char const *identity; + int auth_type_mschap; +} rlm_eap_mschapv2_t; + +static CONF_PARSER module_config[] = { + { "with_ntdomain_hack", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, with_ntdomain_hack), "no" }, + + { "send_error", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_mschapv2_t, send_error), "no" }, + { "identity", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_mschapv2_t, identity), NULL }, + CONF_PARSER_TERMINATOR +}; + + +static void fix_mppe_keys(eap_handler_t *handler, mschapv2_opaque_t *data) +{ + fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_list_mcopy_by_num(data, &data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); +} + +/* + * Attach the module. + */ +static int mod_instantiate(CONF_SECTION *cs, void **instance) +{ + rlm_eap_mschapv2_t *inst; + DICT_VALUE const *dv; + + *instance = inst = talloc_zero(cs, rlm_eap_mschapv2_t); + if (!inst) return -1; + + /* + * Parse the configuration attributes. + */ + if (cf_section_parse(cs, inst, module_config) < 0) { + return -1; + } + + if (inst->identity && (strlen(inst->identity) > 255)) { + cf_log_err_cs(cs, "identity is too long"); + return -1; + } + + if (!inst->identity) { + inst->identity = talloc_asprintf(inst, "freeradius-%s", RADIUSD_VERSION_STRING); + } + + dv = dict_valbyname(PW_AUTH_TYPE, 0, "MSCHAP"); + if (!dv) dv = dict_valbyname(PW_AUTH_TYPE, 0, "MS-CHAP"); + if (!dv) { + cf_log_err_cs(cs, "Failed to find 'Auth-Type MS-CHAP' section. Cannot authenticate users."); + return -1; + } + inst->auth_type_mschap = dv->value; + + return 0; +} + + +/* + * Compose the response. + */ +static int eapmschapv2_compose(rlm_eap_mschapv2_t *inst, eap_handler_t *handler, VALUE_PAIR *reply) +{ + uint8_t *ptr; + int16_t length; + mschapv2_header_t *hdr; + EAP_DS *eap_ds = handler->eap_ds; + REQUEST *request = handler->request; + + eap_ds->request->code = PW_EAP_REQUEST; + eap_ds->request->type.num = PW_EAP_MSCHAPV2; + + /* + * Always called with vendor Microsoft + */ + switch (reply->da->attr) { + case PW_MSCHAP_CHALLENGE: + /* + * 0 1 2 3 + * 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 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Code | Identifier | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | OpCode | MS-CHAPv2-ID | MS-Length... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MS-Length | Value-Size | Challenge... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Challenge... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Server Name... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + length = MSCHAPV2_HEADER_LEN + MSCHAPV2_CHALLENGE_LEN + strlen(inst->identity); + eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length); + + /* + * Allocate room for the EAP-MS-CHAPv2 data. + */ + if (!eap_ds->request->type.data) { + return 0; + } + eap_ds->request->type.length = length; + + ptr = eap_ds->request->type.data; + hdr = (mschapv2_header_t *) ptr; + + hdr->opcode = PW_EAP_MSCHAPV2_CHALLENGE; + hdr->mschapv2_id = eap_ds->response->id + 1; + length = htons(length); + memcpy(hdr->ms_length, &length, sizeof(uint16_t)); + hdr->value_size = MSCHAPV2_CHALLENGE_LEN; + + ptr += MSCHAPV2_HEADER_LEN; + + /* + * Copy the Challenge, success, or error over. + */ + memcpy(ptr, reply->vp_octets, reply->vp_length); + + memcpy((ptr + reply->vp_length), inst->identity, strlen(inst->identity)); + break; + + case PW_MSCHAP2_SUCCESS: + /* + * 0 1 2 3 + * 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 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Code | Identifier | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | OpCode | MS-CHAPv2-ID | MS-Length... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | MS-Length | Message... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + RDEBUG2("MSCHAP Success"); + length = 46; + eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length); + /* + * Allocate room for the EAP-MS-CHAPv2 data. + */ + if (!eap_ds->request->type.data) { + return 0; + } + memset(eap_ds->request->type.data, 0, length); + eap_ds->request->type.length = length; + + eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_SUCCESS; + eap_ds->request->type.data[1] = eap_ds->response->id; + length = htons(length); + memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t)); + memcpy((eap_ds->request->type.data + 4), reply->vp_strvalue + 1, 42); + break; + + case PW_MSCHAP_ERROR: + REDEBUG("MSCHAP Failure"); + length = 4 + reply->vp_length - 1; + eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, length); + + /* + * Allocate room for the EAP-MS-CHAPv2 data. + */ + if (!eap_ds->request->type.data) return 0; + memset(eap_ds->request->type.data, 0, length); + eap_ds->request->type.length = length; + + eap_ds->request->type.data[0] = PW_EAP_MSCHAPV2_FAILURE; + eap_ds->request->type.data[1] = eap_ds->response->id; + length = htons(length); + memcpy((eap_ds->request->type.data + 2), &length, sizeof(uint16_t)); + /* + * Copy the entire failure message. + */ + memcpy((eap_ds->request->type.data + 4), + reply->vp_strvalue + 1, reply->vp_length - 1); + break; + + default: + RERROR("Internal sanity check failed"); + return 0; + } + + return 1; +} + + +/* + * Initiate the EAP-MSCHAPV2 session by sending a challenge to the peer. + */ +static int mod_session_init(void *instance, eap_handler_t *handler) +{ + int i; + VALUE_PAIR *challenge; + mschapv2_opaque_t *data; + REQUEST *request = handler->request; + uint8_t *p; + bool created_challenge = false; + rlm_eap_mschapv2_t *inst = instance; + + challenge = fr_pair_find_by_num(request->config, PW_MSCHAP_CHALLENGE, VENDORPEC_MICROSOFT, TAG_ANY); + if (challenge && (challenge->vp_length != MSCHAPV2_CHALLENGE_LEN)) { + RWDEBUG("control:MS-CHAP-Challenge is incorrect length. Ignoring it."); + challenge = NULL; + } + + if (!challenge) { + created_challenge = true; + challenge = fr_pair_make(handler, NULL, "MS-CHAP-Challenge", NULL, T_OP_EQ); + + /* + * Get a random challenge. + */ + challenge->vp_length = MSCHAPV2_CHALLENGE_LEN; + challenge->vp_octets = p = talloc_array(challenge, uint8_t, challenge->vp_length); + for (i = 0; i < MSCHAPV2_CHALLENGE_LEN; i++) { + p[i] = fr_rand(); + } + } + RDEBUG2("Issuing Challenge"); + + /* + * Keep track of the challenge. + */ + data = talloc_zero(handler, mschapv2_opaque_t); + rad_assert(data != NULL); + + /* + * We're at the stage where we're challenging the user. + */ + data->code = PW_EAP_MSCHAPV2_CHALLENGE; + memcpy(data->challenge, challenge->vp_octets, MSCHAPV2_CHALLENGE_LEN); + data->mppe_keys = NULL; + data->reply = NULL; + + handler->opaque = data; + + /* + * Compose the EAP-MSCHAPV2 packet out of the data structure, + * and free it. + */ + eapmschapv2_compose(inst, handler, challenge); + if (created_challenge) fr_pair_list_free(&challenge); + +#ifdef WITH_PROXY + /* + * The EAP session doesn't have enough information to + * proxy the "inside EAP" protocol. Disable EAP proxying. + */ + handler->request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; +#endif + + /* + * We don't need to authorize the user at this point. + * + * We also don't need to keep the challenge, as it's + * stored in 'handler->eap_ds', which will be given back + * to us... + */ + handler->stage = PROCESS; + + return 1; +} + +#ifdef WITH_PROXY +/* + * Do post-proxy processing, + * 0 = fail + * 1 = OK. + * + * Called from rlm_eap.c, eap_postproxy(). + */ +static int CC_HINT(nonnull) mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data) +{ + VALUE_PAIR *response = NULL; + mschapv2_opaque_t *data; + REQUEST *request = handler->request; + + data = (mschapv2_opaque_t *) handler->opaque; + rad_assert(request != NULL); + + RDEBUG2("Passing reply from proxy back into the tunnel %d", request->reply->code); + + /* + * There is only a limited number of possibilities. + */ + switch (request->reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG2("Proxied authentication succeeded"); + + /* + * Move the attribute, so it doesn't go into + * the reply. + */ + fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); + break; + + default: + case PW_CODE_ACCESS_REJECT: + REDEBUG("Proxied authentication was rejected"); + return 0; + } + + /* + * No response, die. + */ + if (!response) { + REDEBUG("Proxied reply contained no MS-CHAP2-Success or MS-CHAP-Error"); + return 0; + } + + /* + * Done doing EAP proxy stuff. + */ + request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; + eapmschapv2_compose(NULL, handler, response); + data->code = PW_EAP_MSCHAPV2_SUCCESS; + + /* + * Delete MPPE keys & encryption policy + * + * FIXME: Use intelligent names... + */ + fix_mppe_keys(handler, data); + + /* + * Save any other attributes for re-use in the final + * access-accept e.g. vlan, etc. This lets the PEAP + * use_tunneled_reply code work + */ + data->reply = fr_pair_list_copy(data, request->reply->vps); + + /* + * And we need to challenge the user, not ack/reject them, + * so we re-write the ACK to a challenge. Yuck. + */ + request->reply->code = PW_CODE_ACCESS_CHALLENGE; + fr_pair_list_free(&response); + + return 1; +} +#endif + +/* + * Authenticate a previously sent challenge. + */ +static int CC_HINT(nonnull) mod_process(void *arg, eap_handler_t *handler) +{ + int rcode, ccode; + uint8_t *p; + size_t length; + char *q; + mschapv2_opaque_t *data; + EAP_DS *eap_ds = handler->eap_ds; + VALUE_PAIR *challenge, *response, *name; + rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg; + REQUEST *request = handler->request; + + rad_assert(handler->stage == PROCESS); + + data = (mschapv2_opaque_t *) handler->opaque; + + /* + * Sanity check the response. + */ + if (eap_ds->response->length <= 5) { + REDEBUG("corrupted data"); + return 0; + } + + ccode = eap_ds->response->type.data[0]; + + switch (data->code) { + case PW_EAP_MSCHAPV2_FAILURE: + if (ccode == PW_EAP_MSCHAPV2_RESPONSE) { + RDEBUG2("Authentication re-try from client after we sent a failure"); + break; + } + + /* + * if we sent error 648 (password expired) to the client + * we might get an MSCHAP-CPW packet here; turn it into a + * regular MS-CHAP2-CPW packet and pass it to rlm_mschap + * (or proxy it, I guess) + */ + if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) { + VALUE_PAIR *cpw; + int mschap_id = eap_ds->response->type.data[1]; + int copied = 0 ,seq = 1; + + RDEBUG2("Password change packet received"); + + challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ); + if (!challenge) return 0; + fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); + + cpw = pair_make_request("MS-CHAP2-CPW", NULL, T_OP_EQ); + cpw->vp_length = 68; + + cpw->vp_octets = p = talloc_array(cpw, uint8_t, cpw->vp_length); + p[0] = 7; + p[1] = mschap_id; + memcpy(p + 2, eap_ds->response->type.data + 520, 66); + + /* + * break the encoded password into VPs (3 of them) + */ + while (copied < 516) { + VALUE_PAIR *nt_enc; + + int to_copy = 516 - copied; + if (to_copy > 243) to_copy = 243; + + nt_enc = pair_make_request("MS-CHAP-NT-Enc-PW", NULL, T_OP_ADD); + nt_enc->vp_length = 4 + to_copy; + + nt_enc->vp_octets = p = talloc_array(nt_enc, uint8_t, nt_enc->vp_length); + + p[0] = 6; + p[1] = mschap_id; + p[2] = 0; + p[3] = seq++; + + memcpy(p + 4, eap_ds->response->type.data + 4 + copied, to_copy); + copied += to_copy; + } + + RDEBUG2("Built change password packet"); + rdebug_pair_list(L_DBG_LVL_2, request, request->packet->vps, NULL); + + /* + * jump to "authentication" + */ + goto packet_ready; + } + + /* + * we sent a failure and are expecting a failure back + */ + if (ccode != PW_EAP_MSCHAPV2_FAILURE) { + REDEBUG("Sent FAILURE expecting FAILURE but got %d", ccode); + return 0; + } + +failure: + request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; + eap_ds->request->code = PW_EAP_FAILURE; + return 1; + + case PW_EAP_MSCHAPV2_SUCCESS: + /* + * we sent a success to the client; some clients send a + * success back as-per the RFC, some send an ACK. Permit + * both, I guess... + */ + + switch (ccode) { + case PW_EAP_MSCHAPV2_SUCCESS: + eap_ds->request->code = PW_EAP_SUCCESS; + + fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->mppe_keys, 0, 0, TAG_ANY); + /* FALL-THROUGH */ + + case PW_EAP_MSCHAPV2_ACK: +#ifdef WITH_PROXY + /* + * It's a success. Don't proxy it. + */ + request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP; +#endif + fr_pair_list_mcopy_by_num(request->reply, &request->reply->vps, &data->reply, 0, 0, TAG_ANY); + return 1; + } + REDEBUG("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode); + return 0; + + case PW_EAP_MSCHAPV2_CHALLENGE: + if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure; + + /* + * we sent a challenge, expecting a response + */ + if (ccode != PW_EAP_MSCHAPV2_RESPONSE) { + REDEBUG("Sent CHALLENGE expecting RESPONSE but got %d", ccode); + return 0; + } + /* authentication happens below */ + break; + + default: + /* should never happen */ + REDEBUG("Unknown state %d", data->code); + return 0; + } + + + /* + * Ensure that we have at least enough data + * to do the following checks. + * + * EAP header (4), EAP type, MS-CHAP opcode, + * MS-CHAP ident, MS-CHAP data length (2), + * MS-CHAP value length. + */ + if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) { + REDEBUG("Response is too short"); + return 0; + } + + /* + * The 'value_size' is the size of the response, + * which is supposed to be the response (48 + * bytes) plus 1 byte of flags at the end. + * + * NOTE: When using Cisco NEAT with EAP-MSCHAPv2, the + * switch supplicant will send MSCHAPv2 data (EAP type = 26) + * but will always set a value_size of 16 and NULL out the + * peer challenge. + * + */ + if ((eap_ds->response->type.data[4] != 49) && + (eap_ds->response->type.data[4] != 16)) { + REDEBUG("Response is of incorrect length %d", eap_ds->response->type.data[4]); + return 0; + } + + /* + * The MS-Length field is 5 + value_size + length + * of name, which is put after the response. + */ + length = (eap_ds->response->type.data[2] << 8) | eap_ds->response->type.data[3]; + if ((length < (5 + 49)) || (length > (256 + 5 + 49))) { + REDEBUG("Response contains contradictory length %zu %d", length, 5 + 49); + return 0; + } + + /* + * We now know that the user has sent us a response + * to the challenge. Let's try to authenticate it. + * + * We do this by taking the challenge from 'data', + * the response from the EAP packet, and creating VALUE_PAIR's + * to pass to the 'mschap' module. This is a little wonky, + * but it works. + */ + challenge = pair_make_request("MS-CHAP-Challenge", NULL, T_OP_EQ); + if (!challenge) return 0; + fr_pair_value_memcpy(challenge, data->challenge, MSCHAPV2_CHALLENGE_LEN); + + response = pair_make_request("MS-CHAP2-Response", NULL, T_OP_EQ); + if (!response) return 0; + response->vp_length = MSCHAPV2_RESPONSE_LEN; + response->vp_octets = p = talloc_array(response, uint8_t, response->vp_length); + + p[0] = eap_ds->response->type.data[1]; + p[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN]; + memcpy(p + 2, &eap_ds->response->type.data[5], MSCHAPV2_RESPONSE_LEN - 2); + + name = pair_make_request("MS-CHAP-User-Name", NULL, T_OP_EQ); + if (!name) return 0; + + /* + * MS-Length - MS-Value - 5. + */ + name->vp_length = length - 49 - 5; + name->vp_strvalue = q = talloc_array(name, char, name->vp_length + 1); + memcpy(q, &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN], name->vp_length); + q[name->vp_length] = '\0'; + +packet_ready: + +#ifdef WITH_PROXY + /* + * If this options is set, then we do NOT authenticate the + * user here. Instead, now that we've added the MS-CHAP + * attributes to the request, we STOP, and let the outer + * tunnel code handle it. + * + * This means that the outer tunnel code will DELETE the + * EAP attributes, and proxy the MS-CHAP attributes to a + * home server. + */ + if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) { + char *username = NULL; + eap_tunnel_data_t *tunnel; + + RDEBUG2("Cancelling authentication and letting it be proxied"); + + /* + * Set up the callbacks for the tunnel + */ + tunnel = talloc_zero(request, eap_tunnel_data_t); + + tunnel->tls_session = arg; + tunnel->callback = mschap_postproxy; + + /* + * Associate the callback with the request. + */ + rcode = request_data_add(request, + request->proxy, + REQUEST_DATA_EAP_TUNNEL_CALLBACK, + tunnel, false); + rad_assert(rcode == 0); + + /* + * The State attribute is NOT supposed to + * go into the proxied packet, it will confuse + * other RADIUS servers, and they will discard + * the request. + * + * The PEAP module will take care of adding + * the State attribute back, before passing + * the handler & request back into the tunnel. + */ + fr_pair_delete_by_num(&request->packet->vps, PW_STATE, 0, TAG_ANY); + + /* + * Fix the User-Name when proxying, to strip off + * the NT Domain, if we're told to, and a User-Name + * exists, and there's a \\, meaning an NT-Domain + * in the user name, THEN discard the user name. + */ + if (inst->with_ntdomain_hack && + ((challenge = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) && + ((username = memchr(challenge->vp_octets, '\\', challenge->vp_length)) != NULL)) { + /* + * Wipe out the NT domain. + * + * FIXME: Put it into MS-CHAP-Domain? + */ + username++; /* skip the \\ */ + fr_pair_value_strcpy(challenge, username); + } + + /* + * Remember that in the post-proxy stage, we've got + * to do the work below, AFTER the call to MS-CHAP + * authentication... + */ + return 1; + } +#endif + + /* + * This is a wild & crazy hack. + */ + rcode = process_authenticate(inst->auth_type_mschap, request); + + /* + * Delete MPPE keys & encryption policy. We don't + * want these here. + */ + fix_mppe_keys(handler, data); + + /* + * Take the response from the mschap module, and + * return success or failure, depending on the result. + */ + response = NULL; + if (rcode == RLM_MODULE_OK) { + fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY); + data->code = PW_EAP_MSCHAPV2_SUCCESS; + } else if (inst->send_error) { + fr_pair_list_mcopy_by_num(data, &response, &request->reply->vps, PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY); + if (response) { + int n,err,retry; + char buf[34]; + + VERIFY_VP(response); + + RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue); + + /* + * Parse the new challenge out of the + * MS-CHAP-Error, so that if the client + * issues a re-try, we will know which + * challenge value that they used. + */ + n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]); + if (n == 3) { + RDEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", + err, retry, buf); + fr_hex2bin(data->challenge, 16, buf, strlen(buf)); + } else { + RDEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n); + } + } + data->code = PW_EAP_MSCHAPV2_FAILURE; + } else { + eap_ds->request->code = PW_EAP_FAILURE; + return 1; + } + + /* + * No response, die. + */ + if (!response) { + REDEBUG("No MS-CHAP2-Success or MS-CHAP-Error was found"); + return 0; + } + + /* + * Compose the response (whatever it is), + * and return it to the over-lying EAP module. + */ + eapmschapv2_compose(inst, handler, response); + fr_pair_list_free(&response); + + return 1; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + */ +extern rlm_eap_module_t rlm_eap_mschapv2; +rlm_eap_module_t rlm_eap_mschapv2 = { + .name = "eap_mschapv2", + .instantiate = mod_instantiate, /* Create new submodule instance */ + .session_init = mod_session_init, /* Initialise a new EAP session */ + .process = mod_process /* Process next round of EAP method */ +}; |