#include "eap_peap.h"
+static int setup_fake_request(REQUEST *request, REQUEST *fake, peap_tunnel_t *t);
+
/*
* Send protected EAP-Failure
*
/*
* FIXME: Check the return code.
*/
- tls_handshake_send(tls_session);
+ tls_handshake_send(request, tls_session);
return 1;
}
/*
* FIXME: Check the return code.
*/
- tls_handshake_send(tls_session);
+ tls_handshake_send(request, tls_session);
return 1;
}
+static int eappeap_identity(EAP_HANDLER *handler, tls_session_t *tls_session)
+{
+ eap_packet_t eap_packet;
+
+ eap_packet.code = PW_EAP_REQUEST;
+ eap_packet.id = handler->eap_ds->response->id + 1;
+ eap_packet.length[0] = 0;
+ eap_packet.length[1] = EAP_HEADER_LEN + 1;
+ eap_packet.data[0] = PW_EAP_IDENTITY;
+
+ (tls_session->record_plus)(&tls_session->clean_in,
+ &eap_packet, sizeof(eap_packet));
+
+ tls_handshake_send(handler->request, tls_session);
+ (tls_session->record_init)(&tls_session->clean_in);
+
+ return 1;
+}
+
+/*
+ * Send an MS SoH request
+ */
+static int eappeap_soh(EAP_HANDLER *handler, tls_session_t *tls_session)
+{
+ uint8_t tlv_packet[20];
+
+ tlv_packet[0] = 254; /* extended type */
+
+ tlv_packet[1] = 0;
+ tlv_packet[2] = 0x01; /* ms vendor */
+ tlv_packet[3] = 0x37;
+
+ tlv_packet[4] = 0; /* ms soh eap */
+ tlv_packet[5] = 0;
+ tlv_packet[6] = 0;
+ tlv_packet[7] = 0x21;
+
+ tlv_packet[8] = 0; /* vendor-spec tlv */
+ tlv_packet[9] = 7;
+
+ tlv_packet[10] = 0;
+ tlv_packet[11] = 8; /* payload len */
+
+ tlv_packet[12] = 0; /* ms vendor */
+ tlv_packet[13] = 0;
+ tlv_packet[14] = 0x01;
+ tlv_packet[15] = 0x37;
+
+ tlv_packet[16] = 0;
+ tlv_packet[17] = 2;
+ tlv_packet[18] = 0;
+ tlv_packet[19] = 0;
+
+ (tls_session->record_plus)(&tls_session->clean_in, tlv_packet, 20);
+ tls_handshake_send(handler->request, tls_session);
+ return 1;
+}
+
+static VALUE_PAIR* eapsoh_verify(REQUEST *request, const uint8_t *data, unsigned int data_len) {
+
+ VALUE_PAIR *vp;
+ uint8_t eap_type_base;
+ uint32_t eap_vendor;
+ uint32_t eap_type;
+ int rv;
+
+ vp = pairmake("SoH-Supported", "no", T_OP_EQ);
+ if (data && data[0] == PW_EAP_NAK) {
+ RDEBUG("SoH - client NAKed");
+ goto done;
+ }
+
+ if (!data || data_len < 8) {
+ RDEBUG("SoH - eap payload too short");
+ goto done;
+ }
+
+ eap_type_base = *data++;
+ if (eap_type_base != 254) {
+ RDEBUG("SoH - response is not extended EAP: %i", eap_type_base);
+ goto done;
+ }
+
+ eap_vendor = soh_pull_be_24(data); data += 3;
+ if (eap_vendor != 0x137) {
+ RDEBUG("SoH - extended eap vendor %08x is not Microsoft", eap_vendor);
+ goto done;
+ }
+
+ eap_type = soh_pull_be_32(data); data += 4;
+ if (eap_type != 0x21) {
+ RDEBUG("SoH - response eap type %08x is not EAP-SoH", eap_type);
+ goto done;
+ }
+
+
+ rv = soh_verify(vp, data, data_len - 8);
+ if (rv<0) {
+ RDEBUG("SoH - error decoding payload: %s", fr_strerror());
+ } else {
+ vp->vp_integer = 1;
+ }
+done:
+ return vp;
+}
+
/*
* Verify the tunneled EAP message.
*/
* Convert a list of VALUE_PAIR's to an EAP packet, through the
* simple expedient of dumping the EAP message
*/
-static int vp2eap(tls_session_t *tls_session, VALUE_PAIR *vp)
+static int vp2eap(REQUEST *request, tls_session_t *tls_session, VALUE_PAIR *vp)
{
+ rad_assert(vp != NULL);
+
/*
* Skip the id, code, and length. Just write the EAP
* type & data to the client.
vp->vp_octets, vp->length);
}
- tls_handshake_send(tls_session);
+ tls_handshake_send(request, tls_session);
return 1;
}
pairdelete(&reply->vps, PW_EAP_MESSAGE, 0);
pairdelete(&reply->vps, PW_MESSAGE_AUTHENTICATOR, 0);
+ /*
+ * Delete MPPE keys & encryption policy. We don't
+ * want these here.
+ */
+ pairdelete(&reply->vps, 7, VENDORPEC_MICROSOFT);
+ pairdelete(&reply->vps, 8, VENDORPEC_MICROSOFT);
+ pairdelete(&reply->vps, 16, VENDORPEC_MICROSOFT);
+ pairdelete(&reply->vps, 17, VENDORPEC_MICROSOFT);
+
t->accept_vps = reply->vps;
reply->vps = NULL;
}
* VP's back to the client.
*/
if (vp) {
- vp2eap(tls_session, vp);
+ vp2eap(request, tls_session, vp);
pairfree(&vp);
}
tls_session_t *tls_session = (tls_session_t *) data;
REQUEST *fake, *request = handler->request;
+ rad_assert(request != NULL);
RDEBUG2("Passing reply from proxy back into the tunnel.");
/*
*/
fake->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
RDEBUG2("Passing reply back for EAP-MS-CHAP-V2");
- rcode = module_post_proxy(0, fake);
+ module_post_proxy(0, fake);
/*
* FIXME: If rcode returns fail, do something
#endif
-static void print_tunneled_data(uint8_t *data, size_t data_len)
+static const char *peap_state(peap_tunnel_t *t)
+{
+ switch (t->status) {
+ case PEAP_STATUS_TUNNEL_ESTABLISHED:
+ return "TUNNEL ESTABLISHED";
+ case PEAP_STATUS_WAIT_FOR_SOH_RESPONSE:
+ return "WAITING FOR SOH RESPONSE";
+ case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
+ return "WAITING FOR INNER IDENTITY";
+ case PEAP_STATUS_SENT_TLV_SUCCESS:
+ return "send tlv success";
+ case PEAP_STATUS_SENT_TLV_FAILURE:
+ return "send tlv failure";
+ case PEAP_STATUS_PHASE2_INIT:
+ return "phase2_init";
+ case PEAP_STATUS_PHASE2:
+ return "phase2";
+ default:
+ break;
+ }
+ return "?";
+}
+
+static void print_tunneled_data(const uint8_t *data, size_t data_len)
{
size_t i;
if ((debug_flag > 2) && fr_log_fp) {
for (i = 0; i < data_len; i++) {
- if ((i & 0x0f) == 0) fprintf(fr_log_fp, " PEAP tunnel data in %04x: ", i);
+ if ((i & 0x0f) == 0) fprintf(fr_log_fp, " PEAP tunnel data in %02x: ", (int) i);
fprintf(fr_log_fp, "%02x ", data[i]);
REQUEST *request = handler->request;
EAP_DS *eap_ds = handler->eap_ds;
+ rad_assert(request != NULL);
+
/*
* Just look at the buffer directly, without doing
* record_minus. This lets us avoid another data copy.
tls_session->clean_out.used = 0;
data = tls_session->clean_out.data;
- if (!eapmessage_verify(request, data, data_len)) {
+ RDEBUG2("Peap state %s", peap_state(t));
+
+ if ((t->status != PEAP_STATUS_TUNNEL_ESTABLISHED) &&
+ !eapmessage_verify(request, data, data_len)) {
RDEBUG2("FAILED processing PEAP: Tunneled data is invalid.");
if (debug_flag > 2) print_tunneled_data(data, data_len);
return RLM_MODULE_REJECT;
}
+ switch (t->status) {
+ case PEAP_STATUS_TUNNEL_ESTABLISHED:
+ /* FIXME: should be no data in the buffer here, check & assert? */
+
+ if (SSL_session_reused(tls_session->ssl)) {
+ RDEBUG2("Skipping Phase2 because of session resumption");
+ t->session_resumption_state = PEAP_RESUMPTION_YES;
+ if (t->soh) {
+ t->status = PEAP_STATUS_WAIT_FOR_SOH_RESPONSE;
+ RDEBUG2("Requesting SoH from client");
+ eappeap_soh(handler, tls_session);
+ return RLM_MODULE_HANDLED;
+ }
+ /* we're good, send success TLV */
+ t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+ eappeap_success(handler, tls_session);
+
+ } else {
+ /* send an identity request */
+ t->session_resumption_state = PEAP_RESUMPTION_NO;
+ t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+ eappeap_identity(handler, tls_session);
+ }
+ return RLM_MODULE_HANDLED;
+
+ case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
+ /* we're expecting an identity response */
+ if (data[0] != PW_EAP_IDENTITY) {
+ RDEBUG("Expected EAP-Identity, got something else.");
+ return RLM_MODULE_REJECT;
+ }
+
+ if (data_len >= sizeof(t->username->vp_strvalue)) {
+ RDEBUG("EAP-Identity is too long");
+ return RLM_MODULE_REJECT;
+ }
+
+ /*
+ * Save it for later.
+ */
+ t->username = pairmake("User-Name", "", T_OP_EQ);
+ rad_assert(t->username != NULL);
+
+ memcpy(t->username->vp_strvalue, data + 1, data_len - 1);
+ t->username->length = data_len - 1;
+ t->username->vp_strvalue[t->username->length] = 0;
+ RDEBUG("Got inner identity '%s'", t->username->vp_strvalue);
+ if (t->soh) {
+ t->status = PEAP_STATUS_WAIT_FOR_SOH_RESPONSE;
+ RDEBUG2("Requesting SoH from client");
+ eappeap_soh(handler, tls_session);
+ return RLM_MODULE_HANDLED;
+ }
+ t->status = PEAP_STATUS_PHASE2_INIT;
+ break;
+
+ case PEAP_STATUS_WAIT_FOR_SOH_RESPONSE:
+ fake = request_alloc_fake(request);
+ rad_assert(fake->packet->vps == NULL);
+ fake->packet->vps = eapsoh_verify(request, data, data_len);
+ setup_fake_request(request, fake, t);
+
+ if (t->soh_virtual_server) {
+ fake->server = t->soh_virtual_server;
+ }
+ RDEBUG("Sending SoH request to server %s", fake->server ? fake->server : "NULL");
+ debug_pair_list(fake->packet->vps);
+ RDEBUG("server %s {", fake->server);
+ rad_authenticate(fake);
+ RDEBUG("} # server %s", fake->server);
+ RDEBUG("Got SoH reply");
+ debug_pair_list(fake->reply->vps);
+
+ if (fake->reply->code != PW_AUTHENTICATION_ACK) {
+ RDEBUG2("SoH was rejected");
+ request_free(&fake);
+ t->status = PEAP_STATUS_SENT_TLV_FAILURE;
+ eappeap_failure(handler, tls_session);
+ return RLM_MODULE_HANDLED;
+ }
+
+ /* save the SoH VPs */
+ t->soh_reply_vps = fake->reply->vps;
+ fake->reply->vps = NULL;
+ request_free(&fake);
+
+ if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
+ /* we're good, send success TLV */
+ t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+ eappeap_success(handler, tls_session);
+ return RLM_MODULE_HANDLED;
+ }
+
+ t->status = PEAP_STATUS_PHASE2_INIT;
+ break;
+
+
/*
* If we authenticated the user, then it's OK.
*/
- if (t->status == PEAP_STATUS_SENT_TLV_SUCCESS) {
+ case PEAP_STATUS_SENT_TLV_SUCCESS:
if (eappeap_check_tlv(request, data)) {
RDEBUG2("Success");
return RLM_MODULE_OK;
* We do this by sending an EAP-Identity request
* inside of the PEAP tunnel.
*/
- if ((t->session_resumption_state != PEAP_RESUMPTION_NO) &&
- SSL_session_reused(tls_session->ssl)) {
- eap_packet_t eap_packet;
-
+ if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
RDEBUG2("Client rejected session resumption. Re-starting full authentication");
- eap_packet.code = PW_EAP_REQUEST;
- eap_packet.id = handler->eap_ds->response->id + 1;
- eap_packet.length[0] = 0;
- eap_packet.length[1] = EAP_HEADER_LEN + 1;
- eap_packet.data[0] = PW_EAP_IDENTITY;
/*
* Mark session resumption status.
*/
- t->status = 0;
+ t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
t->session_resumption_state = PEAP_RESUMPTION_NO;
- (tls_session->record_plus)(&tls_session->clean_in,
- &eap_packet,
- sizeof(eap_packet));
- tls_handshake_send(tls_session);
+ eappeap_identity(handler, tls_session);
return RLM_MODULE_HANDLED;
}
+ RDEBUG2("We sent a success, but received something weird in return.");
return RLM_MODULE_REJECT;
- }
-
/*
* Damned if I know why the clients continue sending EAP
* packets after we told them to f*ck off.
*/
- if (t->status == PEAP_STATUS_SENT_TLV_FAILURE) {
+ case PEAP_STATUS_SENT_TLV_FAILURE:
RDEBUG(" The users session was previously rejected: returning reject (again.)");
RDEBUG(" *** This means you need to read the PREVIOUS messages in the debug output");
RDEBUG(" *** to find out the reason why the user was rejected.");
RDEBUG(" *** Look for \"reject\" or \"fail\". Those earlier messages will tell you.");
RDEBUG(" *** what went wrong, and how to fix the problem.");
return RLM_MODULE_REJECT;
+
+ case PEAP_STATUS_PHASE2_INIT:
+ RDEBUG("In state machine in phase2 init?");
+
+ case PEAP_STATUS_PHASE2:
+ break;
+
+ default:
+ RDEBUG2("Unhandled state in peap");
+ return RLM_MODULE_REJECT;
}
fake = request_alloc_fake(request);
rad_assert(fake->packet->vps == NULL);
- fake->packet->vps = eap2vp(request, eap_ds, data, data_len);
- if (!fake->packet->vps) {
- request_free(&fake);
- RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
+ switch (t->status) {
+ /*
+ * If we're in PHASE2_INIT, the phase2 method hasn't been
+ * sent an Identity packet yet; do so from the stored
+ * username and this will kick off the phase2 eap method
+ */
+
+ case PEAP_STATUS_PHASE2_INIT: {
+ int len = t->username->length + EAP_HEADER_LEN + 1;
+
+ t->status = PEAP_STATUS_PHASE2;
+
+ vp = paircreate(PW_EAP_MESSAGE, 0, PW_TYPE_OCTETS);
+
+ vp->vp_octets[0] = PW_EAP_RESPONSE;
+ vp->vp_octets[1] = eap_ds->response->id;
+ vp->vp_octets[2] = (len >> 8) & 0xff;
+ vp->vp_octets[3] = len & 0xff;
+ vp->vp_octets[4] = PW_EAP_IDENTITY;
+
+ memcpy(vp->vp_octets + EAP_HEADER_LEN + 1, t->username->vp_strvalue, t->username->length);
+ vp->length = len;
+
+ pairadd(&fake->packet->vps, vp);
+
+ if (t->default_eap_type != 0) {
+ RDEBUG2("Setting default EAP type for tunneled EAP session.");
+ vp = pairmake("EAP-Type", "0", T_OP_EQ);
+ vp->vp_integer = t->default_eap_type;
+ pairadd(&fake->config_items, vp);
+ }
+ break; }
+
+ case PEAP_STATUS_PHASE2:
+ fake->packet->vps = eap2vp(request, eap_ds, data, data_len);
+ if (!fake->packet->vps) {
+ request_free(&fake);
+ RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
+ return PW_AUTHENTICATION_REJECT;
+ }
+ break;
+
+ default:
+ RDEBUG("Invalid state change in PEAP.");
return PW_AUTHENTICATION_REJECT;
}
}
/*
- * Tell the request that it's a fake one.
- */
- vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ);
- if (vp) {
- pairadd(&fake->packet->vps, vp);
- }
-
- /*
* Update other items in the REQUEST data structure.
*/
if (!t->username) {
}
} /* else there WAS a t->username */
- if (t->username) {
- vp = paircopy(t->username);
- pairadd(&fake->packet->vps, vp);
- fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0);
- DEBUG2(" PEAP: Setting User-Name to %s",
- fake->username->vp_strvalue);
- }
-
- /*
- * Add the State attribute, too, if it exists.
- */
- if (t->state) {
- vp = paircopy(t->state);
- if (vp) pairadd(&fake->packet->vps, vp);
- }
-
- /*
- * If this is set, we copy SOME of the request attributes
- * from outside of the tunnel to inside of the tunnel.
- *
- * We copy ONLY those attributes which do NOT already
- * exist in the tunneled request.
- *
- * This code is copied from ../rlm_eap_ttls/ttls.c
- */
- if (t->copy_request_to_tunnel) {
- VALUE_PAIR *copy;
-
- for (vp = request->packet->vps; vp != NULL; vp = vp->next) {
- /*
- * The attribute is a server-side thingy,
- * don't copy it.
- */
- if ((vp->attribute > 255) &&
- (vp->vendor == 0)) {
- continue;
- }
-
- /*
- * The outside attribute is already in the
- * tunnel, don't copy it.
- *
- * This works for BOTH attributes which
- * are originally in the tunneled request,
- * AND attributes which are copied there
- * from below.
- */
- if (pairfind(fake->packet->vps, vp->attribute, vp->vendor)) {
- continue;
- }
-
- /*
- * Some attributes are handled specially.
- */
- switch (vp->attribute) {
- /*
- * NEVER copy Message-Authenticator,
- * EAP-Message, or State. They're
- * only for outside of the tunnel.
- */
- case PW_USER_NAME:
- case PW_USER_PASSWORD:
- case PW_CHAP_PASSWORD:
- case PW_CHAP_CHALLENGE:
- case PW_PROXY_STATE:
- case PW_MESSAGE_AUTHENTICATOR:
- case PW_EAP_MESSAGE:
- case PW_STATE:
- continue;
- break;
-
- /*
- * By default, copy it over.
- */
- default:
- break;
- }
-
- /*
- * Don't copy from the head, we've already
- * checked it.
- */
- copy = paircopy2(vp, vp->attribute, vp->vendor);
- pairadd(&fake->packet->vps, copy);
- }
- }
+ setup_fake_request(request, fake, t);
if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0)) != NULL) {
fake->server = vp->vp_strvalue;
*/
rad_assert(request->proxy == NULL);
request->proxy = fake->packet;
+ memset(&request->proxy->src_ipaddr, 0,
+ sizeof(request->proxy->src_ipaddr));
+ memset(&request->proxy->src_ipaddr, 0,
+ sizeof(request->proxy->src_ipaddr));
+ request->proxy->src_port = 0;
+ request->proxy->dst_port = 0;
fake->packet = NULL;
rad_free(&fake->reply);
fake->reply = NULL;
return rcode;
}
+
+static int setup_fake_request(REQUEST *request, REQUEST *fake, peap_tunnel_t *t) {
+
+ VALUE_PAIR *vp;
+ /*
+ * Tell the request that it's a fake one.
+ */
+ vp = pairmake("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ);
+ if (vp) {
+ pairadd(&fake->packet->vps, vp);
+ }
+
+ if (t->username) {
+ vp = paircopy(t->username);
+ pairadd(&fake->packet->vps, vp);
+ fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0);
+ RDEBUG2("Setting User-Name to %s", fake->username->vp_strvalue);
+ } else {
+ RDEBUG2("No tunnel username (SSL resumption?)");
+ }
+
+
+ /*
+ * Add the State attribute, too, if it exists.
+ */
+ if (t->state) {
+ vp = paircopy(t->state);
+ if (vp) pairadd(&fake->packet->vps, vp);
+ }
+
+ /*
+ * If this is set, we copy SOME of the request attributes
+ * from outside of the tunnel to inside of the tunnel.
+ *
+ * We copy ONLY those attributes which do NOT already
+ * exist in the tunneled request.
+ *
+ * This code is copied from ../rlm_eap_ttls/ttls.c
+ */
+ if (t->copy_request_to_tunnel) {
+ VALUE_PAIR *copy;
+
+ for (vp = request->packet->vps; vp != NULL; vp = vp->next) {
+ /*
+ * The attribute is a server-side thingy,
+ * don't copy it.
+ */
+ if ((vp->attribute > 255) &&
+ (((vp->attribute >> 16) & 0xffff) == 0)) {
+ continue;
+ }
+
+ /*
+ * The outside attribute is already in the
+ * tunnel, don't copy it.
+ *
+ * This works for BOTH attributes which
+ * are originally in the tunneled request,
+ * AND attributes which are copied there
+ * from below.
+ */
+ if (pairfind(fake->packet->vps, vp->attribute, vp->vendor)) {
+ continue;
+ }
+
+ /*
+ * Some attributes are handled specially.
+ */
+ switch (vp->attribute) {
+ /*
+ * NEVER copy Message-Authenticator,
+ * EAP-Message, or State. They're
+ * only for outside of the tunnel.
+ */
+ case PW_USER_NAME:
+ case PW_USER_PASSWORD:
+ case PW_CHAP_PASSWORD:
+ case PW_CHAP_CHALLENGE:
+ case PW_PROXY_STATE:
+ case PW_MESSAGE_AUTHENTICATOR:
+ case PW_EAP_MESSAGE:
+ case PW_STATE:
+ continue;
+ break;
+
+ /*
+ * By default, copy it over.
+ */
+ default:
+ break;
+ }
+
+ /*
+ * Don't copy from the head, we've already
+ * checked it.
+ */
+ copy = paircopy2(vp, vp->attribute, vp->vendor);
+ pairadd(&fake->packet->vps, copy);
+ }
+ }
+
+ return 0;
+}