]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Merge state machines with EAP modules, use virtual_server_t in more places
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 25 Jul 2025 19:33:55 +0000 (12:33 -0700)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 25 Jul 2025 19:33:55 +0000 (12:33 -0700)
18 files changed:
src/lib/eap/base.c
src/lib/eap/base.h
src/lib/eap_aka_sim/module.h
src/lib/server/virtual_servers.c
src/lib/server/virtual_servers.h
src/lib/unlang/call.c
src/modules/rlm_eap/types/rlm_eap_fast/all.mk
src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c [deleted file]
src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.h
src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c
src/modules/rlm_eap/types/rlm_eap_peap/all.mk
src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h [deleted file]
src/modules/rlm_eap/types/rlm_eap_peap/peap.c [deleted file]
src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c
src/modules/rlm_eap/types/rlm_eap_ttls/all.mk
src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h [deleted file]
src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c
src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c [deleted file]

index dad15a17678c7f343cf2b65deb8efd97ff912ea0..a25f1c38ce8a9f1c16b7a0b12e296d3540b1dd5c 100644 (file)
@@ -419,18 +419,19 @@ static unlang_action_t eap_virtual_server_resume(UNUSED unlang_result_t *p_resul
  *
  * @param[in] request          the current (real) request.
  * @param[in] eap_session      representing the outer eap method.
- * @param[in] server_cs                The virtual server to send the request to.
+ * @param[in] virtual_server   The virtual server to send the request to.
  * @return
  *     - UNLANG_ACTION_PUSHED_CHILD on success
  *     - UNLANG_ACTION_FAIL on error
  */
-unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, CONF_SECTION *server_cs)
+unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_session, virtual_server_t *virtual_server)
 {
        fr_pair_t       *vp;
 
        fr_assert(request->parent);
+       fr_assert(virtual_server);
 
-       RDEBUG2("Running request through virtual server \"%s\"", cf_section_name(server_cs));
+       RDEBUG2("Running request through virtual server \"%s\"", cf_section_name2(virtual_server_cs(virtual_server)));
 
        /*
         *      Re-present the previously stored child's session state if there is one
@@ -449,7 +450,7 @@ unlang_action_t eap_virtual_server(request_t *request, eap_session_t *eap_sessio
                                             UNLANG_SUB_FRAME,
                                             eap_session) < 0) return UNLANG_ACTION_FAIL;
 
-       if (unlang_call_push(NULL, request, server_cs, UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
+       if (unlang_call_push(NULL, request, virtual_server_cs(virtual_server), UNLANG_SUB_FRAME) < 0) return UNLANG_ACTION_FAIL;
 
        return UNLANG_ACTION_PUSHED_CHILD;
 }
index 9896ce8994933dceeb0999cde94f05e298718ac3..2c9a413f8d8c4bce4195a8b2fa91bf4db3461dc1 100644 (file)
@@ -27,6 +27,7 @@ RCSIDH(lib_eap_base_h, "$Id$")
 #include <freeradius-devel/eap/types.h>
 
 #include <freeradius-devel/server/module.h>
+#include <freeradius-devel/server/virtual_servers.h>
 #include <freeradius-devel/util/debug.h>
 
 #include <freeradius-devel/eap/base.h>
@@ -62,9 +63,8 @@ void                  eap_packet_to_vp(TALLOC_CTX *ctx, fr_pair_list_t *list, eap_packet_raw_t
 eap_packet_raw_t       *eap_packet_from_vp(TALLOC_CTX *ctx, fr_pair_list_t *vps);
 void                   eap_add_reply(request_t *request, fr_dict_attr_t const *da, uint8_t const *value, int len);
 
-unlang_action_t                eap_virtual_server(request_t *request, eap_session_t *eap_session, CONF_SECTION *server_cs);
+unlang_action_t                eap_virtual_server(request_t *request, eap_session_t *eap_session, virtual_server_t *virtual_server);
 
 int                    eap_base_init(void);
 
 void                   eap_base_free(void);
-
index 4c28a5a7cfc668f86c9abc39d393b24c939abd81..8a1ca32e86ebfd96b6b3f198530e9241f10a26aa 100644 (file)
@@ -27,6 +27,7 @@
 RCSIDH(lib_eap_aka_sim_module_h, "$Id$")
 
 #include <freeradius-devel/server/cf_util.h>
+#include <freeradius-devel/server/virtual_servers.h>
 #include <freeradius-devel/eap/types.h>
 
 #include <freeradius-devel/tls/openssl_user_macros.h>
@@ -37,7 +38,7 @@ extern "C" {
 #endif
 
 typedef struct {
-       CONF_SECTION                    *virtual_server;                //!< Virtual server.
+       virtual_server_t                *virtual_server;                //!< Virtual server.
 
        /** Whether we should include a bid-down prevention attribute by default
         *
index 298ed578521910ceb7026ba734eadf63df488681..4c1f8290e401818c18817b5426e4a4bd3e0a58df 100644 (file)
@@ -752,16 +752,8 @@ check_default:
  *
  *     Short-term hack
  */
-unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, CONF_SECTION *server_cs, bool top_frame)
+unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *request, virtual_server_t const *vs, bool top_frame)
 {
-       virtual_server_t *vs;
-
-       vs = cf_data_value(cf_data_find(server_cs, virtual_server_t, NULL));
-       if (!vs) {
-               cf_log_err(server_cs, "server_cs does not contain virtual server data");
-               return UNLANG_ACTION_FAIL;
-       }
-
        /*
         *      Add a log destination specific to this virtual server.
         *
@@ -787,7 +779,7 @@ unlang_action_t virtual_server_push(unlang_result_t *p_result, request_t *reques
                                                                  server_remove_log_destination,        /* but when we pop the frame */
                                                                  server_signal_remove_log_destination, ~(FR_SIGNAL_CANCEL),
                                                                  top_frame,
-                                                                 vs);
+                                                                 UNCONST(void *, vs));
                        if (action != UNLANG_ACTION_PUSHED_CHILD) return action;
 
                        top_frame = UNLANG_SUB_FRAME;   /* switch to SUB_FRAME after the first instruction */
@@ -951,6 +943,22 @@ CONF_SECTION *virtual_server_cs(virtual_server_t const *vs)
        return vs->server_cs;
 }
 
+/** Resolve a CONF_SECTION to a virtual server
+ *
+ */
+virtual_server_t const *virtual_server_from_cs(CONF_SECTION *server_cs)
+{
+       virtual_server_t *vs;
+
+       vs = cf_data_value(cf_data_find(server_cs, virtual_server_t, NULL));
+       if (!vs) {
+               cf_log_err(server_cs, "server_cs does not contain virtual server data");
+               return NULL;
+       }
+
+       return vs;
+}
+
 /** Return virtual server matching the specified name
  *
  * @note May be called in bootstrap or instantiate as all servers should be present.
@@ -1061,7 +1069,7 @@ int virtual_server_cf_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *pare
        }
 
 done:
-       *((CONF_SECTION **)out) = vs->server_cs;
+       *((virtual_server_t const **)out) = vs;
 
        return 0;
 }
index 34e439b284d7f1509d57122287d4bee2a7852161..11f219b11058a672e94830f4e15dae24bb59eed5 100644 (file)
@@ -85,6 +85,8 @@ int           virtual_server_has_namespace(CONF_SECTION **out,
  */
 CONF_SECTION           *virtual_server_cs(virtual_server_t const *vs) CC_HINT(nonnull);
 
+virtual_server_t const *virtual_server_from_cs(CONF_SECTION *server_cs);
+
 virtual_server_t const *virtual_server_find(char const *name) CC_HINT(nonnull);
 
 virtual_server_t const *virtual_server_by_child(CONF_ITEM const *ci) CC_HINT(nonnull);
@@ -144,7 +146,7 @@ int         virtual_server_section_register(virtual_server_t *vs, virtual_server_compil
 
 section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, section_name_t const *section) CC_HINT(nonnull);
 
-unlang_action_t        virtual_server_push(unlang_result_t *p_result, request_t *request, CONF_SECTION *server_cs, bool top_frame) CC_HINT(nonnull(2,3));
+unlang_action_t        virtual_server_push(unlang_result_t *p_result, request_t *request, virtual_server_t const *vs, bool top_frame) CC_HINT(nonnull(2,3));
 
 /** @name Parsing, bootstrap and instantiation
  *
index 756e959fa30a28280218ba7dafa58620d68e85b9..f6267ea2594ecf3774b967057aca2086a081f535 100644 (file)
@@ -135,7 +135,7 @@ static unlang_action_t unlang_call_frame_init(unlang_result_t *p_result, request
                frame_repeat(frame, unlang_call_children);
        }
 
-       if (virtual_server_push(NULL, request, gext->server_cs, UNLANG_SUB_FRAME) < 0) goto error;
+       if (virtual_server_push(NULL, request, virtual_server_from_cs(gext->server_cs), UNLANG_SUB_FRAME) < 0) goto error;
 
        return UNLANG_ACTION_PUSHED_CHILD;
 }
index c79dd63b99a9f7b391997bd17eafe0cb600a5f78..82064e05f4814880f66a017d6eaad2280fe3e8f7 100644 (file)
@@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" ""
 TARGET         := $(TARGETNAME)$(L)
 endif
 
-SOURCES                := $(TARGETNAME).c eap_fast.c eap_fast_crypto.c
+SOURCES                := $(TARGETNAME).c eap_fast_crypto.c
 
 SRC_INCDIRS    := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
 
diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c
deleted file mode 100644 (file)
index 05c7366..0000000
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- *   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
- */
-
-/**
- * $Id$
- * @file eap_fast.c
- * @brief Contains the interfaces that are called from the main handler
- *
- * @author Alexander Clouter (alex@digriz.org.uk)
-
- * @copyright 2016 Alan DeKok (aland@freeradius.org)
- * @copyright 2016 The FreeRADIUS server project
- */
-RCSID("$Id$")
-
-#include "eap_fast.h"
-#include "eap_fast_crypto.h"
-#include <freeradius-devel/tls/utils.h>
-#include <freeradius-devel/util/sha1.h>
-#include <freeradius-devel/tls/openssl_user_macros.h>
-#include <openssl/rand.h>
-#include <openssl/ssl.h>
-
-#define RANDFILL(x) do { fr_assert(sizeof(x) % sizeof(uint32_t) == 0); for (size_t i = 0; i < sizeof(x); i += sizeof(uint32_t)) *((uint32_t *)&x[i]) = fr_rand(); } while(0)
-
-/**
- * RFC 4851 section 5.1 - EAP-FAST Authentication Phase 1: Key Derivations
- */
-static void eap_fast_init_keys(request_t *request, fr_tls_session_t *tls_session)
-{
-       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       uint8_t *buf;
-       uint8_t *scratch;
-       size_t ksize;
-
-       RDEBUG2("Deriving EAP-FAST keys");
-
-       fr_assert(t->s_imck == NULL);
-
-       ksize = fr_tls_utils_keyblock_size_get(request, tls_session->ssl);
-       fr_assert(ksize > 0);
-       buf = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
-       scratch = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
-
-       t->keyblock = talloc(t, eap_fast_keyblock_t);
-
-       eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
-       memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
-       memset(buf, 0, ksize + sizeof(*t->keyblock));
-
-       t->s_imck = talloc_array(t, uint8_t, EAP_FAST_SIMCK_LEN);
-       memcpy(t->s_imck, t->keyblock, EAP_FAST_SKS_LEN);       /* S-IMCK[0] = session_key_seed */
-
-       t->cmk = talloc_array(t, uint8_t, EAP_FAST_CMK_LEN);    /* note that CMK[0] is not defined */
-       t->imck_count = 0;
-
-       talloc_free(buf);
-       talloc_free(scratch);
-}
-
-/**
- * RFC 4851 section 5.2 - Intermediate Compound Key Derivations
- */
-static void eap_fast_update_icmk(request_t *request, fr_tls_session_t *tls_session, uint8_t *msk)
-{
-       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       uint8_t imck[EAP_FAST_SIMCK_LEN + EAP_FAST_CMK_LEN];
-
-       RDEBUG2("Updating ICMK");
-
-       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Inner Methods Compound Keys", msk, 32, imck, sizeof(imck));       //-V512
-
-       memcpy(t->s_imck, imck, EAP_FAST_SIMCK_LEN);
-       RHEXDUMP3(t->s_imck, EAP_FAST_SIMCK_LEN, "S-IMCK[j]");
-
-       memcpy(t->cmk, &imck[EAP_FAST_SIMCK_LEN], EAP_FAST_CMK_LEN);
-       RHEXDUMP3(t->cmk, EAP_FAST_CMK_LEN, "CMK[j]");
-
-       t->imck_count++;
-
-       /*
-         * Calculate MSK/EMSK at the same time as they are coupled to ICMK
-         *
-         * RFC 4851 section 5.4 - EAP Master Session Key Generation
-         */
-       t->msk = talloc_array(t, uint8_t, EAP_FAST_KEY_LEN);
-       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Session Key Generating Function", NULL, 0, t->msk, EAP_FAST_KEY_LEN);
-       RHEXDUMP3(t->msk, EAP_FAST_KEY_LEN, "MSK");
-
-       t->emsk = talloc_array(t, uint8_t, EAP_EMSK_LEN);
-       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Extended Session Key Generating Function", NULL, 0, t->emsk, EAP_EMSK_LEN);
-       RHEXDUMP3(t->emsk, EAP_EMSK_LEN, "EMSK");
-}
-
-void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *tlv, bool mandatory, int length, void const *data)
-{
-       uint16_t hdr[2];
-
-       hdr[0] = (mandatory) ? htons(tlv->attr | EAP_FAST_TLV_MANDATORY) : htons(tlv->attr);
-       hdr[1] = htons(length);
-
-       tls_session->record_from_buff(&tls_session->clean_in, &hdr, 4);
-       tls_session->record_from_buff(&tls_session->clean_in, data, length);
-}
-
-static void eap_fast_send_error(fr_tls_session_t *tls_session, int error)
-{
-       uint32_t value;
-       value = htonl(error);
-
-       eap_fast_tlv_append(tls_session, attr_eap_fast_error, true, sizeof(value), &value);
-}
-
-static void eap_fast_append_result(fr_tls_session_t *tls_session, fr_radius_packet_code_t code)
-{
-       eap_fast_tunnel_t       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       uint16_t                state;
-       fr_dict_attr_t const    *da;
-
-
-       da = (t->result_final) ? attr_eap_fast_result : attr_eap_fast_intermediate_result;
-       state = htons((code == FR_RADIUS_CODE_ACCESS_REJECT) ? EAP_FAST_TLV_RESULT_FAILURE : EAP_FAST_TLV_RESULT_SUCCESS);
-
-       eap_fast_tlv_append(tls_session, da, true, sizeof(state), &state);
-}
-
-static void eap_fast_send_identity_request(request_t *request, fr_tls_session_t *tls_session, eap_session_t *eap_session)
-{
-       eap_packet_raw_t eap_packet;
-
-       RDEBUG2("Sending EAP-Identity");
-
-       eap_packet.code = FR_EAP_CODE_REQUEST;
-       eap_packet.id = eap_session->this_round->response->id + 1;
-       eap_packet.length[0] = 0;
-       eap_packet.length[1] = EAP_HEADER_LEN + 1;
-       eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
-
-       eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, sizeof(eap_packet), &eap_packet);
-}
-
-static void eap_fast_send_pac_tunnel(request_t *request, fr_tls_session_t *tls_session)
-{
-       eap_fast_tunnel_t                       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       eap_fast_pac_t                          pac;
-       eap_fast_attr_pac_opaque_plaintext_t    opaque_plaintext;
-       int                                     alen, dlen;
-
-       memset(&pac, 0, sizeof(pac));
-       memset(&opaque_plaintext, 0, sizeof(opaque_plaintext));
-
-       RDEBUG2("Sending Tunnel PAC");
-
-       pac.key.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_key->attr);
-       pac.key.hdr.length = htons(sizeof(pac.key.data));
-       fr_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0);
-       RANDFILL(pac.key.data);
-
-       pac.info.lifetime.hdr.type = htons(attr_eap_fast_pac_info_pac_lifetime->attr);
-       pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data));
-       pac.info.lifetime.data = htonl(fr_time_to_sec(fr_time_add(request->packet->timestamp, t->pac_lifetime)));
-
-       pac.info.a_id.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_a_id->attr);
-       pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data));
-       memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data));
-
-       pac.info.a_id_info.hdr.type = htons(attr_eap_fast_pac_a_id->attr);
-       pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data));
-
-#define MIN(a,b) (((a)>(b)) ? (b) : (a))
-       alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data));
-       memcpy(pac.info.a_id_info.data, t->authority_identity, alen);
-
-       pac.info.type.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_pac_type->attr);
-       pac.info.type.hdr.length = htons(sizeof(pac.info.type.data));
-       pac.info.type.data = htons(PAC_TYPE_TUNNEL);
-
-       pac.info.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_tlv->attr);
-       pac.info.hdr.length = htons(sizeof(pac.info.lifetime)
-                               + sizeof(pac.info.a_id)
-                               + sizeof(pac.info.a_id_info)
-                               + sizeof(pac.info.type));
-
-       memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type));
-       memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime));
-       memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key));
-
-       RHEXDUMP3((uint8_t const *)&opaque_plaintext, sizeof(opaque_plaintext), "PAC-Opaque plaintext data section");
-
-       fr_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN);
-       memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH);
-       fr_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0);
-       dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
-                                   t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
-                                   pac.opaque.data, pac.opaque.tag);
-
-       pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_opaque_tlv->attr);
-       pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
-       RHEXDUMP3((uint8_t const *)&pac.opaque, sizeof(pac.opaque) - sizeof(pac.opaque.data) + dlen, "PAC-Opaque");
-
-       eap_fast_tlv_append(tls_session, attr_eap_fast_pac_tlv, true, sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac);
-}
-
-static void eap_fast_append_crypto_binding(request_t *request, fr_tls_session_t *tls_session)
-{
-       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       eap_tlv_crypto_binding_tlv_t    binding = {0};
-       int const                       len = sizeof(binding) - (&binding.reserved - (uint8_t *)&binding);
-
-       RDEBUG2("Sending Cryptobinding");
-
-       binding.tlv_type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_crypto_binding->attr);
-       binding.length = htons(len);
-       binding.version = EAP_FAST_VERSION;
-       binding.received_version = EAP_FAST_VERSION;    /* FIXME use the clients value */
-       binding.subtype = EAP_FAST_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST;
-
-       fr_assert(sizeof(binding.nonce) % sizeof(uint32_t) == 0);
-       RANDFILL(binding.nonce);
-       binding.nonce[sizeof(binding.nonce) - 1] &= ~0x01; /* RFC 4851 section 4.2.8 */
-       RHEXDUMP3(binding.nonce, sizeof(binding.nonce), "NONCE");
-
-       RHEXDUMP3((uint8_t const *) &binding, sizeof(binding), "Crypto-Binding TLV for Compound MAC calculation");
-
-       fr_hmac_sha1(binding.compound_mac, (uint8_t *)&binding, sizeof(binding), t->cmk, EAP_FAST_CMK_LEN);
-       RHEXDUMP3(binding.compound_mac, sizeof(binding.compound_mac), "Compound MAC");
-
-       eap_fast_tlv_append(tls_session, attr_eap_fast_crypto_binding, true, len, &binding.reserved);
-}
-
-#define EAP_FAST_TLV_MAX 11
-
-static int eap_fast_verify(request_t *request, fr_tls_session_t *tls_session, uint8_t const *data, unsigned int data_len)
-{
-       uint16_t attr;
-       uint16_t length;
-       unsigned int remaining = data_len;
-       int     total = 0;
-       int     num[EAP_FAST_TLV_MAX] = {0};
-       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       uint32_t present = 0;
-
-       fr_assert(sizeof(present) * 8 > EAP_FAST_TLV_MAX);
-
-       while (remaining > 0) {
-               if (remaining < 4) {
-                       RDEBUG2("EAP-FAST TLV is too small (%u) to contain a EAP-FAST TLV header", remaining);
-                       return 0;
-               }
-
-               memcpy(&attr, data, sizeof(attr));
-               attr = ntohs(attr) & EAP_FAST_TLV_TYPE;
-
-               if ((attr == attr_eap_fast_result->attr) ||
-                   (attr == attr_eap_fast_nak->attr) ||
-                   (attr == attr_eap_fast_error->attr) ||
-                   (attr == attr_eap_fast_vendor_specific->attr) ||
-                   (attr == attr_eap_fast_eap_payload->attr) ||
-                   (attr == attr_eap_fast_intermediate_result->attr) ||
-                   (attr == attr_eap_fast_pac_tlv->attr) ||
-                   (attr == attr_eap_fast_crypto_binding->attr)) {
-                       num[attr]++;
-                       present |= 1 << attr;
-
-                       if (num[attr_eap_fast_eap_payload->attr] > 1) {
-                               REDEBUG("Too many EAP-Payload TLVs");
-unexpected:
-                               for (int i = 0; i < EAP_FAST_TLV_MAX; i++) {
-                                       if (present & (1 << i)) RDEBUG2(" - attribute %d is present", i);
-                               }
-                               eap_fast_send_error(tls_session, EAP_FAST_ERR_UNEXPECTED_TLV);
-                               return 0;
-                       }
-
-                       if (num[attr_eap_fast_intermediate_result->attr] > 1) {
-                               REDEBUG("Too many Intermediate-Result TLVs");
-                               goto unexpected;
-                       }
-               } else {
-                       if ((data[0] & 0x80) != 0) {
-                               REDEBUG("Unknown mandatory TLV %02x", attr);
-                               goto unexpected;
-                       }
-
-                       num[0]++;
-               }
-
-               total++;
-
-               memcpy(&length, data + 2, sizeof(length));
-               length = ntohs(length);
-
-               data += 4;
-               remaining -= 4;
-
-               if (length > remaining) {
-                       RDEBUG2("EAP-FAST TLV %u is longer than room remaining in the packet (%u > %u).", attr,
-                               length, remaining);
-                       return 0;
-               }
-
-               /*
-                * If the rest of the TLVs are larger than
-                * this attribute, continue.
-                *
-                * Otherwise, if the attribute over-flows the end
-                * of the TLCs, die.
-                */
-               if (remaining < length) {
-                       RDEBUG2("EAP-FAST TLV overflows packet!");
-                       return 0;
-               }
-
-               /*
-                * If there's an error, we bail out of the
-                * authentication process before allocating
-                * memory.
-                */
-               if ((attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_result->attr)) {
-                       uint16_t status;
-
-                       if (length < 2) {
-                               REDEBUG("EAP-FAST TLV %u is too short.  Expected 2, got %d", attr, length);
-                               return 0;
-                       }
-
-                       memcpy(&status, data, 2);
-                       status = ntohs(status);
-
-                       if (status == EAP_FAST_TLV_RESULT_FAILURE) {
-                               REDEBUG("EAP-FAST TLV %u indicates failure.  Rejecting request", attr);
-                               return 0;
-                       }
-
-                       if (status != EAP_FAST_TLV_RESULT_SUCCESS) {
-                               REDEBUG("EAP-FAST TLV %u contains unknown value.  Rejecting request", attr);
-                               goto unexpected;
-                       }
-               }
-
-               /*
-                * remaining > length, continue.
-                */
-               remaining -= length;
-               data += length;
-       }
-
-       /*
-        * Check if the peer mixed & matched TLVs.
-        */
-       if ((num[attr_eap_fast_nak->attr] > 0) && (num[attr_eap_fast_nak->attr] != total)) {
-               REDEBUG("NAK TLV sent with non-NAK TLVs.  Rejecting request");
-               goto unexpected;
-       }
-
-       if (num[attr_eap_fast_intermediate_result->attr] > 0) {
-               REDEBUG("NAK TLV sent with non-NAK TLVs.  Rejecting request");
-               goto unexpected;
-       }
-
-       /*
-        * Check mandatory or not mandatory TLVs.
-        */
-       switch (t->stage) {
-       case EAP_FAST_TLS_SESSION_HANDSHAKE:
-               if (present) {
-                       REDEBUG("Unexpected TLVs in TLS Session Handshake stage");
-                       goto unexpected;
-               }
-               break;
-       case EAP_FAST_AUTHENTICATION:
-               if (present != (uint32_t)(1 << attr_eap_fast_eap_payload->attr)) {
-                       REDEBUG("Unexpected TLVs in authentication stage");
-                       goto unexpected;
-               }
-               break;
-       case EAP_FAST_CRYPTOBIND_CHECK:
-       {
-               uint32_t bits = (t->result_final)
-                               ? 1 << attr_eap_fast_result->attr
-                               : 1 << attr_eap_fast_intermediate_result->attr;
-               if (present & ~(bits | (1 << attr_eap_fast_crypto_binding->attr) | (1 << attr_eap_fast_pac_tlv->attr))) {
-                       REDEBUG("Unexpected TLVs in cryptobind checking stage");
-                       goto unexpected;
-               }
-               break;
-       }
-       case EAP_FAST_PROVISIONING:
-               if (present & ~((1 << attr_eap_fast_pac_tlv->attr) | (1 << attr_eap_fast_result->attr))) {
-                       REDEBUG("Unexpected TLVs in provisioning stage");
-                       goto unexpected;
-               }
-               break;
-       case EAP_FAST_COMPLETE:
-               if (present) {
-                       REDEBUG("Unexpected TLVs in complete stage");
-                       goto unexpected;
-               }
-               break;
-       default:
-               REDEBUG("Unexpected stage %d", t->stage);
-               return 0;
-       }
-
-       /*
-        * We got this far.  It looks OK.
-        */
-       return 1;
-}
-
-/**
- *
- * FIXME do something with mandatory
- */
-ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
-                            uint8_t const *data, size_t data_len,
-                            void *decode_ctx)
-{
-       fr_dict_attr_t const    *da;
-       uint8_t const           *p = data, *end = p + data_len;
-
-       /*
-        *      Decode the TLVs
-        */
-       while (p < end) {
-               ssize_t         ret;
-               uint16_t        attr;
-               uint16_t        len;
-               fr_pair_t       *vp;
-
-               attr = fr_nbo_to_uint16(p) & EAP_FAST_TLV_TYPE;
-               p += 2;
-               len = fr_nbo_to_uint16(p);
-               p += 2;
-
-               da = fr_dict_attr_child_by_num(parent, attr);
-               if (!da) {
-                       MEM(vp = fr_pair_afrom_child_num(ctx, parent, attr));
-
-               } else if (da->type == FR_TYPE_TLV) {
-                       p += (size_t) eap_fast_decode_pair(ctx, out, parent, p, len, decode_ctx);
-                       continue;
-
-               } else {
-                       MEM(vp = fr_pair_afrom_da(ctx, da));
-               }
-
-               ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
-                                               &FR_DBUFF_TMP(p, (size_t)len), len, true);
-               if (ret != len) {
-                       fr_pair_raw_afrom_pair(vp, p, len);
-               }
-               fr_pair_append(out, vp);
-               p += len;
-       }
-
-       return p - data;
-}
-
-
-/*
- * Use a reply packet to determine what to do.
- */
-static rlm_rcode_t CC_HINT(nonnull) process_reply(UNUSED eap_session_t *eap_session,
-                                                 fr_tls_session_t *tls_session,
-                                                 request_t *request,
-                                                 fr_packet_t *reply, fr_pair_list_t *reply_list)
-{
-       rlm_rcode_t                     rcode = RLM_MODULE_REJECT;
-       fr_pair_t                       *vp;
-       fr_dcursor_t                    cursor;
-
-       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
-       /*
-        * If the response packet was Access-Accept, then
-        * we're OK.  If not, die horribly.
-        *
-        * FIXME: EAP-Messages can only start with 'identity',
-        * NOT 'eap start', so we should check for that....
-        */
-       switch (reply->code) {
-       case FR_RADIUS_CODE_ACCESS_ACCEPT:
-               RDEBUG2("Got tunneled Access-Accept");
-
-               rcode = RLM_MODULE_OK;
-
-               /*
-                * Copy what we need into the TTLS tunnel and leave
-                * the rest to be cleaned up.
-                */
-               for (vp = fr_pair_list_head(reply_list); vp; vp = fr_pair_list_next(reply_list, vp)) {
-                       if (fr_dict_vendor_num_by_da(vp->da) != VENDORPEC_MICROSOFT) continue;
-
-                       /* FIXME must be a better way to capture/re-derive this later for ISK */
-                       switch (vp->da->attr) {
-                       case FR_MSCHAP_MPPE_SEND_KEY:
-                               if (vp->vp_length != MD5_DIGEST_LENGTH) {
-                               wrong_length:
-                                       REDEBUG("Found %s with incorrect length.  Expected %u, got %zu",
-                                               vp->da->name, MD5_DIGEST_LENGTH, vp->vp_length);
-                                       rcode = RLM_MODULE_INVALID;
-                                       break;
-                               }
-
-                               memcpy(t->isk.mppe_send, vp->vp_octets, MD5_DIGEST_LENGTH);
-                               break;
-
-                       case FR_MSCHAP_MPPE_RECV_KEY:
-                               if (vp->vp_length != MD5_DIGEST_LENGTH) goto wrong_length;
-                               memcpy(t->isk.mppe_recv, vp->vp_octets, MD5_DIGEST_LENGTH);
-                               break;
-
-                       case FR_MSCHAP2_SUCCESS:
-                               RDEBUG2("Got %s, tunneling it to the client in a challenge", vp->da->name);
-                               rcode = RLM_MODULE_HANDLED;
-                               t->authenticated = true;
-                               break;
-
-                       default:
-                               break;
-                       }
-               }
-               RHEXDUMP3((uint8_t *)&t->isk, 2 * MD5_DIGEST_LENGTH, "ISK[j]"); /* FIXME (part of above) */
-               break;
-
-       case FR_RADIUS_CODE_ACCESS_REJECT:
-               REDEBUG("Got tunneled Access-Reject");
-               rcode = RLM_MODULE_REJECT;
-               break;
-
-       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
-               RDEBUG2("Got tunneled Access-Challenge");
-
-               /*
-                *      Copy the EAP-Message back to the tunnel.
-                */
-
-               for (vp = fr_pair_dcursor_by_da_init(&cursor, reply_list, attr_eap_message);
-                    vp;
-                    vp = fr_dcursor_next(&cursor)) {
-                       eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, vp->vp_length, vp->vp_octets);
-               }
-
-               rcode = RLM_MODULE_HANDLED;
-               break;
-
-       default:
-               REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
-               rcode = RLM_MODULE_INVALID;
-               break;
-       }
-
-       return rcode;
-}
-
-static fr_radius_packet_code_t eap_fast_eap_payload(request_t *request, eap_session_t *eap_session,
-                                   fr_tls_session_t *tls_session, fr_pair_t *tlv_eap_payload)
-{
-       fr_radius_packet_code_t                 code = FR_RADIUS_CODE_ACCESS_REJECT;
-       rlm_rcode_t             rcode;
-       fr_pair_t               *vp;
-       eap_fast_tunnel_t       *t;
-       request_t                       *fake;
-
-       RDEBUG2("Processing received EAP Payload");
-
-       /*
-        *      Allocate a fake request_t structure.
-        */
-       fake = request_local_alloc_internal(request, &(request_init_args_t){ .parent = request });
-       fr_assert(fr_pair_list_empty(&fake->request_pairs));
-
-       t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
-       /*
-        *      Add the tunneled attributes to the fake request.
-        */
-
-       MEM(vp = fr_pair_afrom_da(fake->request_ctx, attr_eap_message));
-       fr_pair_append(&fake->request_pairs, vp);
-       fr_pair_value_memdup(vp, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false);
-
-       RDEBUG2("Got tunneled request");
-       log_request_pair_list(L_DBG_LVL_1, fake, NULL, &fake->request_pairs, NULL);
-
-       /*
-        *      Tell the request that it's a fake one.
-        */
-       MEM(fr_pair_prepend_by_da(fake->request_ctx, &vp, &fake->request_pairs, attr_freeradius_proxied_to) >= 0);
-       (void)fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1") - 1, NULL, false);
-
-       /*
-        *      If there's no User-Name in the stored data, look for
-        *      an EAP-Identity, and pull it out of there.
-        */
-       if (!t->username) {
-               fr_assert(vp->da == attr_eap_message); /* cached from above */
-
-               if ((vp->vp_length >= EAP_HEADER_LEN + 2) &&
-                   (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
-                   (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
-                   (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
-                       /*
-                        *      Create and remember a User-Name
-                        */
-                       MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
-                       t->username->vp_tainted = true;
-                       fr_pair_value_bstrndup(t->username, (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
-
-                       RDEBUG2("Got tunneled identity of %pV", &t->username->data);
-               } else {
-                       /*
-                        * Don't reject the request outright,
-                        * as it's permitted to do EAP without
-                        * user-name.
-                        */
-                       RWDEBUG2("No EAP-Identity found to start EAP conversation");
-               }
-       } /* else there WAS a t->username */
-
-       if (t->username) {
-               vp = fr_pair_copy(fake->request_ctx, t->username);
-               fr_pair_append(&fake->request_pairs, vp);
-       }
-
-       if (t->stage == EAP_FAST_AUTHENTICATION) {      /* FIXME do this only for MSCHAPv2 */
-               fr_pair_t *tvp;
-
-               MEM(tvp = fr_pair_afrom_da(fake, attr_eap_type));
-               tvp->vp_uint32 = t->default_provisioning_method;
-               fr_pair_append(&fake->control_pairs, tvp);
-
-               /*
-                * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2
-                */
-               if (t->mode == EAP_FAST_PROVISIONING_ANON) {
-                       MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge));
-                       fr_pair_value_memdup(tvp, t->keyblock->server_challenge, MD5_DIGEST_LENGTH, false);
-                       fr_pair_append(&fake->control_pairs, tvp);
-                       RHEXDUMP3(t->keyblock->server_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 auth_challenge");
-
-                       MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge));
-                       fr_pair_value_memdup(tvp, t->keyblock->client_challenge, MD5_DIGEST_LENGTH, false);
-                       fr_pair_append(&fake->control_pairs, tvp);
-                       RHEXDUMP3(t->keyblock->client_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 peer_challenge");
-               }
-       }
-
-       /*
-        * Call authentication recursively, which will
-        * do PAP, CHAP, MS-CHAP, etc.
-        */
-       eap_virtual_server(request, eap_session, t->server_cs);
-
-       /*
-        * Decide what to do with the reply.
-        */
-       switch (fake->reply->code) {
-       case 0:                 /* No reply code, must be proxied... */
-#ifdef WITH_PROXY
-               vp = fr_pair_find_by_da(&fake->control, NULL, attr_proxy_to_realm);
-               if (vp) {
-                       int                     ret;
-                       eap_tunnel_data_t       *tunnel;
-
-                       RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data);
-
-                       /*
-                        *      Tell the original request that it's going to be proxied.
-                        */
-                       fr_pair_list_copy_by_da(request->control_ctx, &request->control_pairs,
-                                               &fake->control_pairs, attr_proxy_to_realm, 0);
-
-                       /*
-                        *      Seed the proxy packet with the tunneled request.
-                        */
-                       fr_assert(!request->proxy);
-
-                       /*
-                        *      FIXME: Actually proxy stuff
-                        */
-                       request->proxy = request_alloc_internal(request, &(request_init_args_t){ .parent = request });
-
-                       request->proxy->packet = talloc_steal(request->proxy, fake->packet);
-                       memset(&request->proxy->packet->src_ipaddr, 0,
-                              sizeof(request->proxy->packet->src_ipaddr));
-                       memset(&request->proxy->packet->src_ipaddr, 0,
-                              sizeof(request->proxy->packet->src_ipaddr));
-                       request->proxy->packet->src_port = 0;
-                       request->proxy->packet->dst_port = 0;
-                       fake->packet = NULL;
-                       fr_packet_free(&fake->reply);
-                       fake->reply = NULL;
-
-                       /*
-                        *      Set up the callbacks for the tunnel
-                        */
-                       tunnel = talloc_zero(request, eap_tunnel_data_t);
-                       tunnel->tls_session = tls_session;
-
-                       /*
-                        *      Associate the callback with the request.
-                        */
-                       ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK,
-                                              tunnel, false, false, false);
-                       fr_cond_assert(ret == 0);
-
-                       /*
-                        *      rlm_eap.c has taken care of associating the eap_session
-                        *      with the fake request.
-                        *
-                        *      So we associate the fake request with this request.
-                        */
-                       ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK,
-                                              fake, true, false, false);
-                       fr_cond_assert(ret == 0);
-
-                       fake = NULL;
-
-                       /*
-                        *      Didn't authenticate the packet, but we're proxying it.
-                        */
-                       code = FR_RADIUS_CODE_STATUS_CLIENT;
-
-               } else
-#endif /* WITH_PROXY */
-                 {
-                         REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user");
-                         code = FR_RADIUS_CODE_ACCESS_REJECT;
-                 }
-               break;
-
-       default:
-               /*
-                *      Returns RLM_MODULE_FOO, and we want to return FR_FOO
-                */
-               rcode = process_reply(eap_session, tls_session, request, fake->reply, &fake->reply_pairs);
-               switch (rcode) {
-               case RLM_MODULE_REJECT:
-                       code = FR_RADIUS_CODE_ACCESS_REJECT;
-                       break;
-
-               case RLM_MODULE_HANDLED:
-                       code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-                       break;
-
-               case RLM_MODULE_OK:
-                       code = FR_RADIUS_CODE_ACCESS_ACCEPT;
-                       break;
-
-               default:
-                       code = FR_RADIUS_CODE_ACCESS_REJECT;
-                       break;
-               }
-               break;
-       }
-
-       talloc_free(fake);
-
-       return code;
-}
-
-static fr_radius_packet_code_t eap_fast_crypto_binding(request_t *request, UNUSED eap_session_t *eap_session,
-                                      fr_tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t *binding)
-{
-       uint8_t                 cmac[sizeof(binding->compound_mac)];
-       eap_fast_tunnel_t       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
-       memcpy(cmac, binding->compound_mac, sizeof(cmac));
-       memset(binding->compound_mac, 0, sizeof(binding->compound_mac));
-
-       RHEXDUMP3((uint8_t const *) binding, sizeof(*binding), "Crypto-Binding TLV for Compound MAC calculation");
-       RHEXDUMP3(cmac, sizeof(cmac), "Received Compound MAC");
-
-       fr_hmac_sha1(binding->compound_mac, (uint8_t *)binding, sizeof(*binding), t->cmk, EAP_FAST_CMK_LEN);
-       if (memcmp(binding->compound_mac, cmac, sizeof(cmac))) {
-               RDEBUG2("Crypto-Binding TLV mismatch");
-               RHEXDUMP3((uint8_t const *) binding->compound_mac,
-                sizeof(binding->compound_mac), "Calculated Compound MAC");
-               return FR_RADIUS_CODE_ACCESS_REJECT;
-       }
-
-       return FR_RADIUS_CODE_ACCESS_ACCEPT;
-}
-
-static fr_radius_packet_code_t eap_fast_process_tlvs(request_t *request, eap_session_t *eap_session,
-                                    fr_tls_session_t *tls_session, fr_pair_list_t *fast_vps)
-{
-       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-       fr_pair_t                       *vp;
-       eap_tlv_crypto_binding_tlv_t    my_binding, *binding = NULL;
-
-       memset(&my_binding, 0, sizeof(my_binding));
-
-       for (vp = fr_pair_list_head(fast_vps);
-            vp;
-            vp = fr_pair_list_next(fast_vps, vp)) {
-               fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
-               if (vp->da->parent == fr_dict_root(dict_eap_fast)) {
-                       if (vp->da == attr_eap_fast_eap_payload) {
-                               code = eap_fast_eap_payload(request, eap_session, tls_session, vp);
-                               if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) t->stage = EAP_FAST_CRYPTOBIND_CHECK;
-                       } else if ((vp->da == attr_eap_fast_result) ||
-                                  (vp->da == attr_eap_fast_intermediate_result)) {
-                               code = FR_RADIUS_CODE_ACCESS_ACCEPT;
-                               t->stage = EAP_FAST_PROVISIONING;
-                       } else {
-                               RDEBUG2("ignoring unknown %pP", vp);
-                               continue;
-                       }
-               } else if (vp->da->parent == attr_eap_fast_crypto_binding) {
-                       binding = &my_binding;
-
-                       /*
-                        *      fr_radius_encode_pair() does not work for structures
-                        */
-                       switch (vp->da->attr) {
-                       case 1: /* FR_EAP_FAST_CRYPTO_BINDING_RESERVED */
-                               binding->reserved = vp->vp_uint8;
-                               break;
-                       case 2: /* FR_EAP_FAST_CRYPTO_BINDING_VERSION */
-                               binding->version = vp->vp_uint8;
-                               break;
-                       case 3: /* FR_EAP_FAST_CRYPTO_BINDING_RECV_VERSION */
-                               binding->received_version = vp->vp_uint8;
-                               break;
-                       case 4: /* FR_EAP_FAST_CRYPTO_BINDING_SUB_TYPE */
-                               binding->subtype = vp->vp_uint8;
-                               break;
-                       case 5: /* FR_EAP_FAST_CRYPTO_BINDING_NONCE */
-                               if (vp->vp_length >= sizeof(binding->nonce)) {
-                                       memcpy(binding->nonce, vp->vp_octets, vp->vp_length);
-                               }
-                               break;
-                       case 6: /* FR_EAP_FAST_CRYPTO_BINDING_COMPOUND_MAC */
-                               if (vp->vp_length >= sizeof(binding->compound_mac)) {
-                                       memcpy(binding->compound_mac, vp->vp_octets, sizeof(binding->compound_mac));
-                               }
-                               break;
-                       }
-                       continue;
-               } else if (vp->da->parent == attr_eap_fast_pac_tlv) {
-                       if (vp->da == attr_eap_fast_pac_acknowledge) {
-                               if (vp->vp_uint32 == EAP_FAST_TLV_RESULT_SUCCESS) {
-                                       code = FR_RADIUS_CODE_ACCESS_ACCEPT;
-                                       t->pac.expires = fr_time_max();
-                                       t->pac.expired = false;
-                                       t->stage = EAP_FAST_COMPLETE;
-                               }
-                       } else if (vp->da == attr_eap_fast_pac_info_pac_type) {
-                               if (vp->vp_uint32 != PAC_TYPE_TUNNEL) {
-                                       RDEBUG2("only able to serve Tunnel PAC's, ignoring request");
-                                       continue;
-                               }
-                               t->pac.send = true;
-                               continue;
-                       } else {
-                               RDEBUG2("ignoring unknown EAP-FAST-PAC-TLV %pP", vp);
-                               continue;
-                       }
-               } else {
-                       RDEBUG2("ignoring non-EAP-FAST TLV %pP", vp);
-                       continue;
-               }
-
-               if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
-       }
-
-       if (binding) {
-               fr_radius_packet_code_t code = eap_fast_crypto_binding(request, eap_session, tls_session, binding);
-               if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) {
-                       t->stage = EAP_FAST_PROVISIONING;
-               }
-               return code;
-       }
-
-       return FR_RADIUS_CODE_ACCESS_ACCEPT;
-}
-
-
-/*
- * Process the inner tunnel data
- */
-fr_radius_packet_code_t eap_fast_process(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       fr_radius_packet_code_t                 code;
-       fr_pair_list_t          fast_vps;
-       uint8_t const           *data;
-       size_t                  data_len;
-       eap_fast_tunnel_t       *t;
-
-       fr_pair_list_init(&fast_vps);
-       /*
-        * Just look at the buffer directly, without doing
-        * record_to_buff.
-        */
-       data_len = tls_session->clean_out.used;
-       tls_session->clean_out.used = 0;
-       data = tls_session->clean_out.data;
-
-       t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
-
-       /*
-        * See if the tunneled data is well formed.
-        */
-       if (!eap_fast_verify(request, tls_session, data, data_len)) return FR_RADIUS_CODE_ACCESS_REJECT;
-
-       if (t->stage == EAP_FAST_TLS_SESSION_HANDSHAKE) {
-               char buf[256];
-
-               fr_assert(t->mode == EAP_FAST_UNKNOWN);
-
-               if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl),
-                                                 buf, sizeof(buf)), "Au=None")) {
-                       /* FIXME enforce MSCHAPv2 - RFC 5422 section 3.2.2 */
-                       RDEBUG2("Using anonymous provisioning");
-                       t->mode = EAP_FAST_PROVISIONING_ANON;
-                       t->pac.send = true;
-               } else {
-                       fr_time_t renew;
-
-                       if (SSL_session_reused(tls_session->ssl)) {
-                               RDEBUG2("Session Resumed from PAC");
-                               t->mode = EAP_FAST_NORMAL_AUTH;
-                       } else {
-                               RDEBUG2("Using authenticated provisioning");
-                               t->mode = EAP_FAST_PROVISIONING_AUTH;
-                       }
-
-                       /*
-                        *      Send a new pac at 60% of the lifetime,
-                        *      or if the PAC has expired, or if no lifetime was set.
-                        */
-                       renew = fr_time_add(request->packet->timestamp,
-                                           fr_time_delta_wrap((fr_time_delta_unwrap(t->pac_lifetime) * 3) / 5));
-
-                       if (t->pac.expired || fr_time_eq(t->pac.expires, fr_time_wrap(0)) ||
-                            fr_time_lteq(t->pac.expires, renew)) {
-                               t->pac.send = true;
-                       }
-               }
-
-               eap_fast_init_keys(request, tls_session);
-
-               eap_fast_send_identity_request(request, tls_session, eap_session);
-
-               t->stage = EAP_FAST_AUTHENTICATION;
-               return FR_RADIUS_CODE_ACCESS_CHALLENGE;
-       }
-
-       if (eap_fast_decode_pair(request, &fast_vps, fr_dict_root(dict_eap_fast),
-                                data, data_len, NULL) < 0) return FR_RADIUS_CODE_ACCESS_REJECT;
-
-       RDEBUG2("Got Tunneled FAST TLVs");
-       log_request_pair_list(L_DBG_LVL_1, request, NULL, &fast_vps, NULL);
-       code = eap_fast_process_tlvs(request, eap_session, tls_session, &fast_vps);
-       fr_pair_list_free(&fast_vps);
-
-       if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
-
-       switch (t->stage) {
-       case EAP_FAST_AUTHENTICATION:
-               code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-               break;
-
-       case EAP_FAST_CRYPTOBIND_CHECK:
-       {
-               if (t->mode != EAP_FAST_PROVISIONING_ANON && !t->pac.send)
-                       t->result_final = true;
-
-               eap_fast_append_result(tls_session, code);
-
-               eap_fast_update_icmk(request, tls_session, (uint8_t *)&t->isk);
-               eap_fast_append_crypto_binding(request, tls_session);
-
-               code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-               break;
-       }
-       case EAP_FAST_PROVISIONING:
-               t->result_final = true;
-
-               eap_fast_append_result(tls_session, code);
-
-               if (t->pac.send) {
-                       RDEBUG2("Peer requires new PAC");
-                       eap_fast_send_pac_tunnel(request, tls_session);
-                       code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-                       break;
-               }
-
-               t->stage = EAP_FAST_COMPLETE;
-               FALL_THROUGH;
-
-       case EAP_FAST_COMPLETE:
-               /*
-                * RFC 5422 section 3.5 - Network Access after EAP-FAST Provisioning
-                */
-               if (t->pac.type && t->pac.expired) {
-                       REDEBUG("Rejecting expired PAC.");
-                       code = FR_RADIUS_CODE_ACCESS_REJECT;
-                       break;
-               }
-
-               if (t->mode == EAP_FAST_PROVISIONING_ANON) {
-                       REDEBUG("Rejecting unauthenticated provisioning");
-                       code = FR_RADIUS_CODE_ACCESS_REJECT;
-                       break;
-               }
-
-               /*
-                * eap_crypto_mppe_keys() is unsuitable for EAP-FAST as Cisco decided
-                * it would be a great idea to flip the recv/send keys around
-                */
-               #define EAPTLS_MPPE_KEY_LEN 32
-               eap_add_reply(request, attr_ms_mppe_recv_key, t->msk, EAPTLS_MPPE_KEY_LEN);
-               eap_add_reply(request, attr_ms_mppe_send_key, &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN);
-               eap_add_reply(request, attr_eap_msk, t->msk, EAP_FAST_KEY_LEN);
-               eap_add_reply(request, attr_eap_emsk, t->emsk, EAP_EMSK_LEN);
-
-               break;
-
-       default:
-               RERROR("Internal sanity check failed in EAP-FAST at %d", t->stage);
-               code = FR_RADIUS_CODE_ACCESS_REJECT;
-       }
-
-       return code;
-}
index 8cdb16bc45b495e6b0d7996bb985e88c190bbdc1..c0b9157e6245cdd650d7284b4a9b6680676a81a4 100644 (file)
@@ -205,60 +205,3 @@ typedef struct {
 #endif
        CONF_SECTION    *server_cs;
 } eap_fast_tunnel_t;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-extern HIDDEN fr_dict_attr_t const *attr_eap_type;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_peer_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_proxy_to_realm;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_eap_msk;
-extern HIDDEN fr_dict_attr_t const *attr_eap_emsk;
-extern HIDDEN fr_dict_attr_t const *attr_freeradius_proxied_to;
-extern HIDDEN fr_dict_attr_t const *attr_ms_mppe_send_key;
-extern HIDDEN fr_dict_attr_t const *attr_ms_mppe_recv_key;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-extern HIDDEN fr_dict_attr_t const *attr_user_password;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_crypto_binding;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_eap_payload;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_error;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_intermediate_result;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_nak;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_a_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_key;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_tlv;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_pac_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_result;
-extern HIDDEN fr_dict_attr_t const *attr_eap_fast_vendor_specific;
-extern HIDDEN fr_dict_t const *dict_eap_fast;
-
-/*
- *     Process the FAST portion of an EAP-FAST request.
- */
-void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *da, bool mandatory,
-                        int length, const void *data) CC_HINT(nonnull);
-fr_radius_packet_code_t eap_fast_process(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
-
-/*
- *     A bunch of EAP-FAST helper functions.
- */
-ssize_t                eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
-                                    uint8_t const *data, size_t data_len,
-                                    UNUSED void *decode_ctx);
index 797f9bb22d80dcb5977956c6e1c1fb78428a0528..756481694ebbac27c9f757c3fa5468974ba777da 100644 (file)
@@ -28,7 +28,9 @@ RCSID("$Id$")
 USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
 
 #include <freeradius-devel/util/md5.h>
-
+#include <freeradius-devel/tls/utils.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
 #include "eap_fast.h"
 #include "eap_fast_crypto.h"
 
@@ -48,7 +50,6 @@ typedef struct {
 
        virtual_server_t        *virtual_server;                        //!< Virtual server to use for processing
                                                                        //!< inner EAP method.
-       CONF_SECTION            *server_cs;
        char const              *cipher_list;                           //!< cipher list specific to EAP-FAST
        bool                    req_client_cert;                        //!< Whether we require a client cert
                                                                        //!< in the outer tunnel.
@@ -94,47 +95,47 @@ fr_dict_autoload_t rlm_eap_fast_dict[] = {
        { NULL }
 };
 
-fr_dict_attr_t const *attr_eap_emsk;
-fr_dict_attr_t const *attr_eap_msk;
-fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-fr_dict_attr_t const *attr_eap_type;
-fr_dict_attr_t const *attr_ms_chap_challenge;
-fr_dict_attr_t const *attr_ms_chap_peer_challenge;
-fr_dict_attr_t const *attr_proxy_to_realm;
-
-fr_dict_attr_t const *attr_eap_message;
-fr_dict_attr_t const *attr_freeradius_proxied_to;
-fr_dict_attr_t const *attr_ms_mppe_send_key;
-fr_dict_attr_t const *attr_ms_mppe_recv_key;
-fr_dict_attr_t const *attr_user_name;
-fr_dict_attr_t const *attr_user_password;
-
-fr_dict_attr_t const *attr_eap_fast_crypto_binding;
-fr_dict_attr_t const *attr_eap_fast_eap_payload;
-fr_dict_attr_t const *attr_eap_fast_error;
-fr_dict_attr_t const *attr_eap_fast_intermediate_result;
-fr_dict_attr_t const *attr_eap_fast_nak;
-fr_dict_attr_t const *attr_eap_fast_pac_a_id;
-fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
-fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
-fr_dict_attr_t const *attr_eap_fast_pac_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
-fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
-fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_key;
-fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
-fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_tlv;
-fr_dict_attr_t const *attr_eap_fast_pac_type;
-fr_dict_attr_t const *attr_eap_fast_result;
-fr_dict_attr_t const *attr_eap_fast_vendor_specific;
+static fr_dict_attr_t const *attr_eap_emsk;
+static fr_dict_attr_t const *attr_eap_msk;
+static fr_dict_attr_t const *attr_eap_tls_require_client_cert;
+static fr_dict_attr_t const *attr_eap_type;
+static fr_dict_attr_t const *attr_ms_chap_challenge;
+static fr_dict_attr_t const *attr_ms_chap_peer_challenge;
+static fr_dict_attr_t const *attr_proxy_to_realm;
+
+static fr_dict_attr_t const *attr_eap_message;
+static fr_dict_attr_t const *attr_freeradius_proxied_to;
+static fr_dict_attr_t const *attr_ms_mppe_send_key;
+static fr_dict_attr_t const *attr_ms_mppe_recv_key;
+static fr_dict_attr_t const *attr_user_name;
+static fr_dict_attr_t const *attr_user_password;
+
+static fr_dict_attr_t const *attr_eap_fast_crypto_binding;
+static fr_dict_attr_t const *attr_eap_fast_eap_payload;
+static fr_dict_attr_t const *attr_eap_fast_error;
+static fr_dict_attr_t const *attr_eap_fast_intermediate_result;
+static fr_dict_attr_t const *attr_eap_fast_nak;
+static fr_dict_attr_t const *attr_eap_fast_pac_a_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_a_id_info;
+static fr_dict_attr_t const *attr_eap_fast_pac_acknowledge;
+static fr_dict_attr_t const *attr_eap_fast_pac_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_a_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_a_id_info;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_pac_info_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_key;
+static fr_dict_attr_t const *attr_eap_fast_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_i_id;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_key;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_lifetime;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_pac_opaque_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_tlv;
+static fr_dict_attr_t const *attr_eap_fast_pac_type;
+static fr_dict_attr_t const *attr_eap_fast_result;
+static fr_dict_attr_t const *attr_eap_fast_vendor_specific;
 
 extern fr_dict_attr_autoload_t rlm_eap_fast_dict_attr[];
 fr_dict_attr_autoload_t rlm_eap_fast_dict_attr[] = {
@@ -183,6 +184,1014 @@ fr_dict_attr_autoload_t rlm_eap_fast_dict_attr[] = {
        { NULL }
 };
 
+#define RANDFILL(x) do { fr_assert(sizeof(x) % sizeof(uint32_t) == 0); for (size_t i = 0; i < sizeof(x); i += sizeof(uint32_t)) *((uint32_t *)&x[i]) = fr_rand(); } while(0)
+
+/**
+ * RFC 4851 section 5.1 - EAP-FAST Authentication Phase 1: Key Derivations
+ */
+static void eap_fast_init_keys(request_t *request, fr_tls_session_t *tls_session)
+{
+       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       uint8_t *buf;
+       uint8_t *scratch;
+       size_t ksize;
+
+       RDEBUG2("Deriving EAP-FAST keys");
+
+       fr_assert(t->s_imck == NULL);
+
+       ksize = fr_tls_utils_keyblock_size_get(request, tls_session->ssl);
+       fr_assert(ksize > 0);
+       buf = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
+       scratch = talloc_array(request, uint8_t, ksize + sizeof(*t->keyblock));
+
+       t->keyblock = talloc(t, eap_fast_keyblock_t);
+
+       eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion");
+       memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock));
+       memset(buf, 0, ksize + sizeof(*t->keyblock));
+
+       t->s_imck = talloc_array(t, uint8_t, EAP_FAST_SIMCK_LEN);
+       memcpy(t->s_imck, t->keyblock, EAP_FAST_SKS_LEN);       /* S-IMCK[0] = session_key_seed */
+
+       t->cmk = talloc_array(t, uint8_t, EAP_FAST_CMK_LEN);    /* note that CMK[0] is not defined */
+       t->imck_count = 0;
+
+       talloc_free(buf);
+       talloc_free(scratch);
+}
+
+/**
+ * RFC 4851 section 5.2 - Intermediate Compound Key Derivations
+ */
+static void eap_fast_update_icmk(request_t *request, fr_tls_session_t *tls_session, uint8_t *msk)
+{
+       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       uint8_t imck[EAP_FAST_SIMCK_LEN + EAP_FAST_CMK_LEN];
+
+       RDEBUG2("Updating ICMK");
+
+       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Inner Methods Compound Keys", msk, 32, imck, sizeof(imck));       //-V512
+
+       memcpy(t->s_imck, imck, EAP_FAST_SIMCK_LEN);
+       RHEXDUMP3(t->s_imck, EAP_FAST_SIMCK_LEN, "S-IMCK[j]");
+
+       memcpy(t->cmk, &imck[EAP_FAST_SIMCK_LEN], EAP_FAST_CMK_LEN);
+       RHEXDUMP3(t->cmk, EAP_FAST_CMK_LEN, "CMK[j]");
+
+       t->imck_count++;
+
+       /*
+         * Calculate MSK/EMSK at the same time as they are coupled to ICMK
+         *
+         * RFC 4851 section 5.4 - EAP Master Session Key Generation
+         */
+       t->msk = talloc_array(t, uint8_t, EAP_FAST_KEY_LEN);
+       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Session Key Generating Function", NULL, 0, t->msk, EAP_FAST_KEY_LEN);
+       RHEXDUMP3(t->msk, EAP_FAST_KEY_LEN, "MSK");
+
+       t->emsk = talloc_array(t, uint8_t, EAP_EMSK_LEN);
+       T_PRF(t->s_imck, EAP_FAST_SIMCK_LEN, "Extended Session Key Generating Function", NULL, 0, t->emsk, EAP_EMSK_LEN);
+       RHEXDUMP3(t->emsk, EAP_EMSK_LEN, "EMSK");
+}
+
+static void eap_fast_tlv_append(fr_tls_session_t *tls_session, fr_dict_attr_t const *tlv, bool mandatory, int length, void const *data)
+{
+       uint16_t hdr[2];
+
+       hdr[0] = (mandatory) ? htons(tlv->attr | EAP_FAST_TLV_MANDATORY) : htons(tlv->attr);
+       hdr[1] = htons(length);
+
+       tls_session->record_from_buff(&tls_session->clean_in, &hdr, 4);
+       tls_session->record_from_buff(&tls_session->clean_in, data, length);
+}
+
+static void eap_fast_send_error(fr_tls_session_t *tls_session, int error)
+{
+       uint32_t value;
+       value = htonl(error);
+
+       eap_fast_tlv_append(tls_session, attr_eap_fast_error, true, sizeof(value), &value);
+}
+
+static void eap_fast_append_result(fr_tls_session_t *tls_session, fr_radius_packet_code_t code)
+{
+       eap_fast_tunnel_t       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       uint16_t                state;
+       fr_dict_attr_t const    *da;
+
+
+       da = (t->result_final) ? attr_eap_fast_result : attr_eap_fast_intermediate_result;
+       state = htons((code == FR_RADIUS_CODE_ACCESS_REJECT) ? EAP_FAST_TLV_RESULT_FAILURE : EAP_FAST_TLV_RESULT_SUCCESS);
+
+       eap_fast_tlv_append(tls_session, da, true, sizeof(state), &state);
+}
+
+static void eap_fast_send_identity_request(request_t *request, fr_tls_session_t *tls_session, eap_session_t *eap_session)
+{
+       eap_packet_raw_t eap_packet;
+
+       RDEBUG2("Sending EAP-Identity");
+
+       eap_packet.code = FR_EAP_CODE_REQUEST;
+       eap_packet.id = eap_session->this_round->response->id + 1;
+       eap_packet.length[0] = 0;
+       eap_packet.length[1] = EAP_HEADER_LEN + 1;
+       eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
+
+       eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, sizeof(eap_packet), &eap_packet);
+}
+
+static void eap_fast_send_pac_tunnel(request_t *request, fr_tls_session_t *tls_session)
+{
+       eap_fast_tunnel_t                       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       eap_fast_pac_t                          pac;
+       eap_fast_attr_pac_opaque_plaintext_t    opaque_plaintext;
+       int                                     alen, dlen;
+
+       memset(&pac, 0, sizeof(pac));
+       memset(&opaque_plaintext, 0, sizeof(opaque_plaintext));
+
+       RDEBUG2("Sending Tunnel PAC");
+
+       pac.key.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_key->attr);
+       pac.key.hdr.length = htons(sizeof(pac.key.data));
+       fr_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0);
+       RANDFILL(pac.key.data);
+
+       pac.info.lifetime.hdr.type = htons(attr_eap_fast_pac_info_pac_lifetime->attr);
+       pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data));
+       pac.info.lifetime.data = htonl(fr_time_to_sec(fr_time_add(request->packet->timestamp, t->pac_lifetime)));
+
+       pac.info.a_id.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_a_id->attr);
+       pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data));
+       memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data));
+
+       pac.info.a_id_info.hdr.type = htons(attr_eap_fast_pac_a_id->attr);
+       pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data));
+
+#define MIN(a,b) (((a)>(b)) ? (b) : (a))
+       alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data));
+       memcpy(pac.info.a_id_info.data, t->authority_identity, alen);
+
+       pac.info.type.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_pac_type->attr);
+       pac.info.type.hdr.length = htons(sizeof(pac.info.type.data));
+       pac.info.type.data = htons(PAC_TYPE_TUNNEL);
+
+       pac.info.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_tlv->attr);
+       pac.info.hdr.length = htons(sizeof(pac.info.lifetime)
+                               + sizeof(pac.info.a_id)
+                               + sizeof(pac.info.a_id_info)
+                               + sizeof(pac.info.type));
+
+       memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type));
+       memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime));
+       memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key));
+
+       RHEXDUMP3((uint8_t const *)&opaque_plaintext, sizeof(opaque_plaintext), "PAC-Opaque plaintext data section");
+
+       fr_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN);
+       memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH);
+       fr_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0);
+       dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext),
+                                   t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv,
+                                   pac.opaque.data, pac.opaque.tag);
+
+       pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_opaque_tlv->attr);
+       pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen);
+       RHEXDUMP3((uint8_t const *)&pac.opaque, sizeof(pac.opaque) - sizeof(pac.opaque.data) + dlen, "PAC-Opaque");
+
+       eap_fast_tlv_append(tls_session, attr_eap_fast_pac_tlv, true, sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac);
+}
+
+static void eap_fast_append_crypto_binding(request_t *request, fr_tls_session_t *tls_session)
+{
+       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       eap_tlv_crypto_binding_tlv_t    binding = {0};
+       int const                       len = sizeof(binding) - (&binding.reserved - (uint8_t *)&binding);
+
+       RDEBUG2("Sending Cryptobinding");
+
+       binding.tlv_type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_crypto_binding->attr);
+       binding.length = htons(len);
+       binding.version = EAP_FAST_VERSION;
+       binding.received_version = EAP_FAST_VERSION;    /* FIXME use the clients value */
+       binding.subtype = EAP_FAST_TLV_CRYPTO_BINDING_SUBTYPE_REQUEST;
+
+       fr_assert(sizeof(binding.nonce) % sizeof(uint32_t) == 0);
+       RANDFILL(binding.nonce);
+       binding.nonce[sizeof(binding.nonce) - 1] &= ~0x01; /* RFC 4851 section 4.2.8 */
+       RHEXDUMP3(binding.nonce, sizeof(binding.nonce), "NONCE");
+
+       RHEXDUMP3((uint8_t const *) &binding, sizeof(binding), "Crypto-Binding TLV for Compound MAC calculation");
+
+       fr_hmac_sha1(binding.compound_mac, (uint8_t *)&binding, sizeof(binding), t->cmk, EAP_FAST_CMK_LEN);
+       RHEXDUMP3(binding.compound_mac, sizeof(binding.compound_mac), "Compound MAC");
+
+       eap_fast_tlv_append(tls_session, attr_eap_fast_crypto_binding, true, len, &binding.reserved);
+}
+
+#define EAP_FAST_TLV_MAX 11
+
+static int eap_fast_verify(request_t *request, fr_tls_session_t *tls_session, uint8_t const *data, unsigned int data_len)
+{
+       uint16_t attr;
+       uint16_t length;
+       unsigned int remaining = data_len;
+       int     total = 0;
+       int     num[EAP_FAST_TLV_MAX] = {0};
+       eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       uint32_t present = 0;
+
+       fr_assert(sizeof(present) * 8 > EAP_FAST_TLV_MAX);
+
+       while (remaining > 0) {
+               if (remaining < 4) {
+                       RDEBUG2("EAP-FAST TLV is too small (%u) to contain a EAP-FAST TLV header", remaining);
+                       return 0;
+               }
+
+               memcpy(&attr, data, sizeof(attr));
+               attr = ntohs(attr) & EAP_FAST_TLV_TYPE;
+
+               if ((attr == attr_eap_fast_result->attr) ||
+                   (attr == attr_eap_fast_nak->attr) ||
+                   (attr == attr_eap_fast_error->attr) ||
+                   (attr == attr_eap_fast_vendor_specific->attr) ||
+                   (attr == attr_eap_fast_eap_payload->attr) ||
+                   (attr == attr_eap_fast_intermediate_result->attr) ||
+                   (attr == attr_eap_fast_pac_tlv->attr) ||
+                   (attr == attr_eap_fast_crypto_binding->attr)) {
+                       num[attr]++;
+                       present |= 1 << attr;
+
+                       if (num[attr_eap_fast_eap_payload->attr] > 1) {
+                               REDEBUG("Too many EAP-Payload TLVs");
+unexpected:
+                               for (int i = 0; i < EAP_FAST_TLV_MAX; i++) {
+                                       if (present & (1 << i)) RDEBUG2(" - attribute %d is present", i);
+                               }
+                               eap_fast_send_error(tls_session, EAP_FAST_ERR_UNEXPECTED_TLV);
+                               return 0;
+                       }
+
+                       if (num[attr_eap_fast_intermediate_result->attr] > 1) {
+                               REDEBUG("Too many Intermediate-Result TLVs");
+                               goto unexpected;
+                       }
+               } else {
+                       if ((data[0] & 0x80) != 0) {
+                               REDEBUG("Unknown mandatory TLV %02x", attr);
+                               goto unexpected;
+                       }
+
+                       num[0]++;
+               }
+
+               total++;
+
+               memcpy(&length, data + 2, sizeof(length));
+               length = ntohs(length);
+
+               data += 4;
+               remaining -= 4;
+
+               if (length > remaining) {
+                       RDEBUG2("EAP-FAST TLV %u is longer than room remaining in the packet (%u > %u).", attr,
+                               length, remaining);
+                       return 0;
+               }
+
+               /*
+                * If the rest of the TLVs are larger than
+                * this attribute, continue.
+                *
+                * Otherwise, if the attribute over-flows the end
+                * of the TLCs, die.
+                */
+               if (remaining < length) {
+                       RDEBUG2("EAP-FAST TLV overflows packet!");
+                       return 0;
+               }
+
+               /*
+                * If there's an error, we bail out of the
+                * authentication process before allocating
+                * memory.
+                */
+               if ((attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_result->attr)) {
+                       uint16_t status;
+
+                       if (length < 2) {
+                               REDEBUG("EAP-FAST TLV %u is too short.  Expected 2, got %d", attr, length);
+                               return 0;
+                       }
+
+                       memcpy(&status, data, 2);
+                       status = ntohs(status);
+
+                       if (status == EAP_FAST_TLV_RESULT_FAILURE) {
+                               REDEBUG("EAP-FAST TLV %u indicates failure.  Rejecting request", attr);
+                               return 0;
+                       }
+
+                       if (status != EAP_FAST_TLV_RESULT_SUCCESS) {
+                               REDEBUG("EAP-FAST TLV %u contains unknown value.  Rejecting request", attr);
+                               goto unexpected;
+                       }
+               }
+
+               /*
+                * remaining > length, continue.
+                */
+               remaining -= length;
+               data += length;
+       }
+
+       /*
+        * Check if the peer mixed & matched TLVs.
+        */
+       if ((num[attr_eap_fast_nak->attr] > 0) && (num[attr_eap_fast_nak->attr] != total)) {
+               REDEBUG("NAK TLV sent with non-NAK TLVs.  Rejecting request");
+               goto unexpected;
+       }
+
+       if (num[attr_eap_fast_intermediate_result->attr] > 0) {
+               REDEBUG("NAK TLV sent with non-NAK TLVs.  Rejecting request");
+               goto unexpected;
+       }
+
+       /*
+        * Check mandatory or not mandatory TLVs.
+        */
+       switch (t->stage) {
+       case EAP_FAST_TLS_SESSION_HANDSHAKE:
+               if (present) {
+                       REDEBUG("Unexpected TLVs in TLS Session Handshake stage");
+                       goto unexpected;
+               }
+               break;
+       case EAP_FAST_AUTHENTICATION:
+               if (present != (uint32_t)(1 << attr_eap_fast_eap_payload->attr)) {
+                       REDEBUG("Unexpected TLVs in authentication stage");
+                       goto unexpected;
+               }
+               break;
+       case EAP_FAST_CRYPTOBIND_CHECK:
+       {
+               uint32_t bits = (t->result_final)
+                               ? 1 << attr_eap_fast_result->attr
+                               : 1 << attr_eap_fast_intermediate_result->attr;
+               if (present & ~(bits | (1 << attr_eap_fast_crypto_binding->attr) | (1 << attr_eap_fast_pac_tlv->attr))) {
+                       REDEBUG("Unexpected TLVs in cryptobind checking stage");
+                       goto unexpected;
+               }
+               break;
+       }
+       case EAP_FAST_PROVISIONING:
+               if (present & ~((1 << attr_eap_fast_pac_tlv->attr) | (1 << attr_eap_fast_result->attr))) {
+                       REDEBUG("Unexpected TLVs in provisioning stage");
+                       goto unexpected;
+               }
+               break;
+       case EAP_FAST_COMPLETE:
+               if (present) {
+                       REDEBUG("Unexpected TLVs in complete stage");
+                       goto unexpected;
+               }
+               break;
+       default:
+               REDEBUG("Unexpected stage %d", t->stage);
+               return 0;
+       }
+
+       /*
+        * We got this far.  It looks OK.
+        */
+       return 1;
+}
+
+/**
+ *
+ * FIXME do something with mandatory
+ */
+static ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
+                                   uint8_t const *data, size_t data_len,
+                                   void *decode_ctx)
+{
+       fr_dict_attr_t const    *da;
+       uint8_t const           *p = data, *end = p + data_len;
+
+       /*
+        *      Decode the TLVs
+        */
+       while (p < end) {
+               ssize_t         ret;
+               uint16_t        attr;
+               uint16_t        len;
+               fr_pair_t       *vp;
+
+               attr = fr_nbo_to_uint16(p) & EAP_FAST_TLV_TYPE;
+               p += 2;
+               len = fr_nbo_to_uint16(p);
+               p += 2;
+
+               da = fr_dict_attr_child_by_num(parent, attr);
+               if (!da) {
+                       MEM(vp = fr_pair_afrom_child_num(ctx, parent, attr));
+
+               } else if (da->type == FR_TYPE_TLV) {
+                       p += (size_t) eap_fast_decode_pair(ctx, out, parent, p, len, decode_ctx);
+                       continue;
+
+               } else {
+                       MEM(vp = fr_pair_afrom_da(ctx, da));
+               }
+
+               ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
+                                               &FR_DBUFF_TMP(p, (size_t)len), len, true);
+               if (ret != len) {
+                       fr_pair_raw_afrom_pair(vp, p, len);
+               }
+               fr_pair_append(out, vp);
+               p += len;
+       }
+
+       return p - data;
+}
+
+
+/*
+ * Use a reply packet to determine what to do.
+ */
+static rlm_rcode_t CC_HINT(nonnull) process_reply(UNUSED eap_session_t *eap_session,
+                                                 fr_tls_session_t *tls_session,
+                                                 request_t *request,
+                                                 fr_packet_t *reply, fr_pair_list_t *reply_list)
+{
+       rlm_rcode_t                     rcode = RLM_MODULE_REJECT;
+       fr_pair_t                       *vp;
+       fr_dcursor_t                    cursor;
+
+       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+       /*
+        * If the response packet was Access-Accept, then
+        * we're OK.  If not, die horribly.
+        *
+        * FIXME: EAP-Messages can only start with 'identity',
+        * NOT 'eap start', so we should check for that....
+        */
+       switch (reply->code) {
+       case FR_RADIUS_CODE_ACCESS_ACCEPT:
+               RDEBUG2("Got tunneled Access-Accept");
+
+               rcode = RLM_MODULE_OK;
+
+               /*
+                * Copy what we need into the TTLS tunnel and leave
+                * the rest to be cleaned up.
+                */
+               for (vp = fr_pair_list_head(reply_list); vp; vp = fr_pair_list_next(reply_list, vp)) {
+                       if (fr_dict_vendor_num_by_da(vp->da) != VENDORPEC_MICROSOFT) continue;
+
+                       /* FIXME must be a better way to capture/re-derive this later for ISK */
+                       switch (vp->da->attr) {
+                       case FR_MSCHAP_MPPE_SEND_KEY:
+                               if (vp->vp_length != MD5_DIGEST_LENGTH) {
+                               wrong_length:
+                                       REDEBUG("Found %s with incorrect length.  Expected %u, got %zu",
+                                               vp->da->name, MD5_DIGEST_LENGTH, vp->vp_length);
+                                       rcode = RLM_MODULE_INVALID;
+                                       break;
+                               }
+
+                               memcpy(t->isk.mppe_send, vp->vp_octets, MD5_DIGEST_LENGTH);
+                               break;
+
+                       case FR_MSCHAP_MPPE_RECV_KEY:
+                               if (vp->vp_length != MD5_DIGEST_LENGTH) goto wrong_length;
+                               memcpy(t->isk.mppe_recv, vp->vp_octets, MD5_DIGEST_LENGTH);
+                               break;
+
+                       case FR_MSCHAP2_SUCCESS:
+                               RDEBUG2("Got %s, tunneling it to the client in a challenge", vp->da->name);
+                               rcode = RLM_MODULE_HANDLED;
+                               t->authenticated = true;
+                               break;
+
+                       default:
+                               break;
+                       }
+               }
+               RHEXDUMP3((uint8_t *)&t->isk, 2 * MD5_DIGEST_LENGTH, "ISK[j]"); /* FIXME (part of above) */
+               break;
+
+       case FR_RADIUS_CODE_ACCESS_REJECT:
+               REDEBUG("Got tunneled Access-Reject");
+               rcode = RLM_MODULE_REJECT;
+               break;
+
+       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+               RDEBUG2("Got tunneled Access-Challenge");
+
+               /*
+                *      Copy the EAP-Message back to the tunnel.
+                */
+
+               for (vp = fr_pair_dcursor_by_da_init(&cursor, reply_list, attr_eap_message);
+                    vp;
+                    vp = fr_dcursor_next(&cursor)) {
+                       eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, vp->vp_length, vp->vp_octets);
+               }
+
+               rcode = RLM_MODULE_HANDLED;
+               break;
+
+       default:
+               REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+               rcode = RLM_MODULE_INVALID;
+               break;
+       }
+
+       return rcode;
+}
+
+static fr_radius_packet_code_t eap_fast_eap_payload(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session,
+                                   fr_tls_session_t *tls_session, fr_pair_t *tlv_eap_payload)
+{
+       fr_radius_packet_code_t         code = FR_RADIUS_CODE_ACCESS_REJECT;
+       rlm_rcode_t                     rcode;
+       fr_pair_t                       *vp;
+       eap_fast_tunnel_t               *t;
+       request_t                       *fake;
+       rlm_eap_fast_t                  *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_fast_t);
+
+       RDEBUG2("Processing received EAP Payload");
+
+       /*
+        *      Allocate a fake request_t structure.
+        */
+       fake = request_local_alloc_internal(request, &(request_init_args_t){ .parent = request });
+       fr_assert(fr_pair_list_empty(&fake->request_pairs));
+
+       t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+       /*
+        *      Add the tunneled attributes to the fake request.
+        */
+
+       MEM(vp = fr_pair_afrom_da(fake->request_ctx, attr_eap_message));
+       fr_pair_append(&fake->request_pairs, vp);
+       fr_pair_value_memdup(vp, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false);
+
+       RDEBUG2("Got tunneled request");
+       log_request_pair_list(L_DBG_LVL_1, fake, NULL, &fake->request_pairs, NULL);
+
+       /*
+        *      Tell the request that it's a fake one.
+        */
+       MEM(fr_pair_prepend_by_da(fake->request_ctx, &vp, &fake->request_pairs, attr_freeradius_proxied_to) >= 0);
+       (void)fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1") - 1, NULL, false);
+
+       /*
+        *      If there's no User-Name in the stored data, look for
+        *      an EAP-Identity, and pull it out of there.
+        */
+       if (!t->username) {
+               fr_assert(vp->da == attr_eap_message); /* cached from above */
+
+               if ((vp->vp_length >= EAP_HEADER_LEN + 2) &&
+                   (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
+                   (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
+                   (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
+                       /*
+                        *      Create and remember a User-Name
+                        */
+                       MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+                       t->username->vp_tainted = true;
+                       fr_pair_value_bstrndup(t->username, (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
+
+                       RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+               } else {
+                       /*
+                        * Don't reject the request outright,
+                        * as it's permitted to do EAP without
+                        * user-name.
+                        */
+                       RWDEBUG2("No EAP-Identity found to start EAP conversation");
+               }
+       } /* else there WAS a t->username */
+
+       if (t->username) {
+               vp = fr_pair_copy(fake->request_ctx, t->username);
+               fr_pair_append(&fake->request_pairs, vp);
+       }
+
+       if (t->stage == EAP_FAST_AUTHENTICATION) {      /* FIXME do this only for MSCHAPv2 */
+               fr_pair_t *tvp;
+
+               MEM(tvp = fr_pair_afrom_da(fake, attr_eap_type));
+               tvp->vp_uint32 = t->default_provisioning_method;
+               fr_pair_append(&fake->control_pairs, tvp);
+
+               /*
+                * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2
+                */
+               if (t->mode == EAP_FAST_PROVISIONING_ANON) {
+                       MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge));
+                       fr_pair_value_memdup(tvp, t->keyblock->server_challenge, MD5_DIGEST_LENGTH, false);
+                       fr_pair_append(&fake->control_pairs, tvp);
+                       RHEXDUMP3(t->keyblock->server_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 auth_challenge");
+
+                       MEM(tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge));
+                       fr_pair_value_memdup(tvp, t->keyblock->client_challenge, MD5_DIGEST_LENGTH, false);
+                       fr_pair_append(&fake->control_pairs, tvp);
+                       RHEXDUMP3(t->keyblock->client_challenge, MD5_DIGEST_LENGTH, "MSCHAPv2 peer_challenge");
+               }
+       }
+
+       /*
+        * Call authentication recursively, which will
+        * do PAP, CHAP, MS-CHAP, etc.
+        */
+       eap_virtual_server(request, eap_session, inst->virtual_server);
+
+       /*
+        * Decide what to do with the reply.
+        */
+       switch (fake->reply->code) {
+       case 0:                 /* No reply code, must be proxied... */
+#ifdef WITH_PROXY
+               vp = fr_pair_find_by_da(&fake->control, NULL, attr_proxy_to_realm);
+               if (vp) {
+                       int                     ret;
+                       eap_tunnel_data_t       *tunnel;
+
+                       RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data);
+
+                       /*
+                        *      Tell the original request that it's going to be proxied.
+                        */
+                       fr_pair_list_copy_by_da(request->control_ctx, &request->control_pairs,
+                                               &fake->control_pairs, attr_proxy_to_realm, 0);
+
+                       /*
+                        *      Seed the proxy packet with the tunneled request.
+                        */
+                       fr_assert(!request->proxy);
+
+                       /*
+                        *      FIXME: Actually proxy stuff
+                        */
+                       request->proxy = request_alloc_internal(request, &(request_init_args_t){ .parent = request });
+
+                       request->proxy->packet = talloc_steal(request->proxy, fake->packet);
+                       memset(&request->proxy->packet->src_ipaddr, 0,
+                              sizeof(request->proxy->packet->src_ipaddr));
+                       memset(&request->proxy->packet->src_ipaddr, 0,
+                              sizeof(request->proxy->packet->src_ipaddr));
+                       request->proxy->packet->src_port = 0;
+                       request->proxy->packet->dst_port = 0;
+                       fake->packet = NULL;
+                       fr_packet_free(&fake->reply);
+                       fake->reply = NULL;
+
+                       /*
+                        *      Set up the callbacks for the tunnel
+                        */
+                       tunnel = talloc_zero(request, eap_tunnel_data_t);
+                       tunnel->tls_session = tls_session;
+
+                       /*
+                        *      Associate the callback with the request.
+                        */
+                       ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK,
+                                              tunnel, false, false, false);
+                       fr_cond_assert(ret == 0);
+
+                       /*
+                        *      rlm_eap.c has taken care of associating the eap_session
+                        *      with the fake request.
+                        *
+                        *      So we associate the fake request with this request.
+                        */
+                       ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK,
+                                              fake, true, false, false);
+                       fr_cond_assert(ret == 0);
+
+                       fake = NULL;
+
+                       /*
+                        *      Didn't authenticate the packet, but we're proxying it.
+                        */
+                       code = FR_RADIUS_CODE_STATUS_CLIENT;
+
+               } else
+#endif /* WITH_PROXY */
+                 {
+                         REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user");
+                         code = FR_RADIUS_CODE_ACCESS_REJECT;
+                 }
+               break;
+
+       default:
+               /*
+                *      Returns RLM_MODULE_FOO, and we want to return FR_FOO
+                */
+               rcode = process_reply(eap_session, tls_session, request, fake->reply, &fake->reply_pairs);
+               switch (rcode) {
+               case RLM_MODULE_REJECT:
+                       code = FR_RADIUS_CODE_ACCESS_REJECT;
+                       break;
+
+               case RLM_MODULE_HANDLED:
+                       code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+                       break;
+
+               case RLM_MODULE_OK:
+                       code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+                       break;
+
+               default:
+                       code = FR_RADIUS_CODE_ACCESS_REJECT;
+                       break;
+               }
+               break;
+       }
+
+       talloc_free(fake);
+
+       return code;
+}
+
+static fr_radius_packet_code_t eap_fast_crypto_binding(request_t *request, UNUSED eap_session_t *eap_session,
+                                      fr_tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t *binding)
+{
+       uint8_t                 cmac[sizeof(binding->compound_mac)];
+       eap_fast_tunnel_t       *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+       memcpy(cmac, binding->compound_mac, sizeof(cmac));
+       memset(binding->compound_mac, 0, sizeof(binding->compound_mac));
+
+       RHEXDUMP3((uint8_t const *) binding, sizeof(*binding), "Crypto-Binding TLV for Compound MAC calculation");
+       RHEXDUMP3(cmac, sizeof(cmac), "Received Compound MAC");
+
+       fr_hmac_sha1(binding->compound_mac, (uint8_t *)binding, sizeof(*binding), t->cmk, EAP_FAST_CMK_LEN);
+       if (memcmp(binding->compound_mac, cmac, sizeof(cmac))) {
+               RDEBUG2("Crypto-Binding TLV mismatch");
+               RHEXDUMP3((uint8_t const *) binding->compound_mac,
+                sizeof(binding->compound_mac), "Calculated Compound MAC");
+               return FR_RADIUS_CODE_ACCESS_REJECT;
+       }
+
+       return FR_RADIUS_CODE_ACCESS_ACCEPT;
+}
+
+static fr_radius_packet_code_t eap_fast_process_tlvs(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session,
+                                    fr_tls_session_t *tls_session, fr_pair_list_t *fast_vps)
+{
+       eap_fast_tunnel_t               *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+       fr_pair_t                       *vp;
+       eap_tlv_crypto_binding_tlv_t    my_binding, *binding = NULL;
+
+       memset(&my_binding, 0, sizeof(my_binding));
+
+       for (vp = fr_pair_list_head(fast_vps);
+            vp;
+            vp = fr_pair_list_next(fast_vps, vp)) {
+               fr_radius_packet_code_t code = FR_RADIUS_CODE_ACCESS_REJECT;
+               if (vp->da->parent == fr_dict_root(dict_eap_fast)) {
+                       if (vp->da == attr_eap_fast_eap_payload) {
+                               code = eap_fast_eap_payload(request, mctx, eap_session, tls_session, vp);
+                               if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) t->stage = EAP_FAST_CRYPTOBIND_CHECK;
+                       } else if ((vp->da == attr_eap_fast_result) ||
+                                  (vp->da == attr_eap_fast_intermediate_result)) {
+                               code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+                               t->stage = EAP_FAST_PROVISIONING;
+                       } else {
+                               RDEBUG2("ignoring unknown %pP", vp);
+                               continue;
+                       }
+               } else if (vp->da->parent == attr_eap_fast_crypto_binding) {
+                       binding = &my_binding;
+
+                       /*
+                        *      fr_radius_encode_pair() does not work for structures
+                        */
+                       switch (vp->da->attr) {
+                       case 1: /* FR_EAP_FAST_CRYPTO_BINDING_RESERVED */
+                               binding->reserved = vp->vp_uint8;
+                               break;
+                       case 2: /* FR_EAP_FAST_CRYPTO_BINDING_VERSION */
+                               binding->version = vp->vp_uint8;
+                               break;
+                       case 3: /* FR_EAP_FAST_CRYPTO_BINDING_RECV_VERSION */
+                               binding->received_version = vp->vp_uint8;
+                               break;
+                       case 4: /* FR_EAP_FAST_CRYPTO_BINDING_SUB_TYPE */
+                               binding->subtype = vp->vp_uint8;
+                               break;
+                       case 5: /* FR_EAP_FAST_CRYPTO_BINDING_NONCE */
+                               if (vp->vp_length >= sizeof(binding->nonce)) {
+                                       memcpy(binding->nonce, vp->vp_octets, vp->vp_length);
+                               }
+                               break;
+                       case 6: /* FR_EAP_FAST_CRYPTO_BINDING_COMPOUND_MAC */
+                               if (vp->vp_length >= sizeof(binding->compound_mac)) {
+                                       memcpy(binding->compound_mac, vp->vp_octets, sizeof(binding->compound_mac));
+                               }
+                               break;
+                       }
+                       continue;
+               } else if (vp->da->parent == attr_eap_fast_pac_tlv) {
+                       if (vp->da == attr_eap_fast_pac_acknowledge) {
+                               if (vp->vp_uint32 == EAP_FAST_TLV_RESULT_SUCCESS) {
+                                       code = FR_RADIUS_CODE_ACCESS_ACCEPT;
+                                       t->pac.expires = fr_time_max();
+                                       t->pac.expired = false;
+                                       t->stage = EAP_FAST_COMPLETE;
+                               }
+                       } else if (vp->da == attr_eap_fast_pac_info_pac_type) {
+                               if (vp->vp_uint32 != PAC_TYPE_TUNNEL) {
+                                       RDEBUG2("only able to serve Tunnel PAC's, ignoring request");
+                                       continue;
+                               }
+                               t->pac.send = true;
+                               continue;
+                       } else {
+                               RDEBUG2("ignoring unknown EAP-FAST-PAC-TLV %pP", vp);
+                               continue;
+                       }
+               } else {
+                       RDEBUG2("ignoring non-EAP-FAST TLV %pP", vp);
+                       continue;
+               }
+
+               if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
+       }
+
+       if (binding) {
+               fr_radius_packet_code_t code = eap_fast_crypto_binding(request, eap_session, tls_session, binding);
+               if (code == FR_RADIUS_CODE_ACCESS_ACCEPT) {
+                       t->stage = EAP_FAST_PROVISIONING;
+               }
+               return code;
+       }
+
+       return FR_RADIUS_CODE_ACCESS_ACCEPT;
+}
+
+
+/*
+ * Process the inner tunnel data
+ */
+static fr_radius_packet_code_t eap_fast_process(request_t *request, module_ctx_t const *mctx, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       fr_radius_packet_code_t                 code;
+       fr_pair_list_t          fast_vps;
+       uint8_t const           *data;
+       size_t                  data_len;
+       eap_fast_tunnel_t       *t;
+
+       fr_pair_list_init(&fast_vps);
+       /*
+        * Just look at the buffer directly, without doing
+        * record_to_buff.
+        */
+       data_len = tls_session->clean_out.used;
+       tls_session->clean_out.used = 0;
+       data = tls_session->clean_out.data;
+
+       t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t);
+
+       /*
+        * See if the tunneled data is well formed.
+        */
+       if (!eap_fast_verify(request, tls_session, data, data_len)) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+       if (t->stage == EAP_FAST_TLS_SESSION_HANDSHAKE) {
+               char buf[256];
+
+               fr_assert(t->mode == EAP_FAST_UNKNOWN);
+
+               if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl),
+                                                 buf, sizeof(buf)), "Au=None")) {
+                       /* FIXME enforce MSCHAPv2 - RFC 5422 section 3.2.2 */
+                       RDEBUG2("Using anonymous provisioning");
+                       t->mode = EAP_FAST_PROVISIONING_ANON;
+                       t->pac.send = true;
+               } else {
+                       fr_time_t renew;
+
+                       if (SSL_session_reused(tls_session->ssl)) {
+                               RDEBUG2("Session Resumed from PAC");
+                               t->mode = EAP_FAST_NORMAL_AUTH;
+                       } else {
+                               RDEBUG2("Using authenticated provisioning");
+                               t->mode = EAP_FAST_PROVISIONING_AUTH;
+                       }
+
+                       /*
+                        *      Send a new pac at 60% of the lifetime,
+                        *      or if the PAC has expired, or if no lifetime was set.
+                        */
+                       renew = fr_time_add(request->packet->timestamp,
+                                           fr_time_delta_wrap((fr_time_delta_unwrap(t->pac_lifetime) * 3) / 5));
+
+                       if (t->pac.expired || fr_time_eq(t->pac.expires, fr_time_wrap(0)) ||
+                            fr_time_lteq(t->pac.expires, renew)) {
+                               t->pac.send = true;
+                       }
+               }
+
+               eap_fast_init_keys(request, tls_session);
+
+               eap_fast_send_identity_request(request, tls_session, eap_session);
+
+               t->stage = EAP_FAST_AUTHENTICATION;
+               return FR_RADIUS_CODE_ACCESS_CHALLENGE;
+       }
+
+       if (eap_fast_decode_pair(request, &fast_vps, fr_dict_root(dict_eap_fast),
+                                data, data_len, NULL) < 0) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+       RDEBUG2("Got Tunneled FAST TLVs");
+       log_request_pair_list(L_DBG_LVL_1, request, NULL, &fast_vps, NULL);
+       code = eap_fast_process_tlvs(request, mctx, eap_session, tls_session, &fast_vps);
+       fr_pair_list_free(&fast_vps);
+
+       if (code == FR_RADIUS_CODE_ACCESS_REJECT) return FR_RADIUS_CODE_ACCESS_REJECT;
+
+       switch (t->stage) {
+       case EAP_FAST_AUTHENTICATION:
+               code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+               break;
+
+       case EAP_FAST_CRYPTOBIND_CHECK:
+       {
+               if (t->mode != EAP_FAST_PROVISIONING_ANON && !t->pac.send)
+                       t->result_final = true;
+
+               eap_fast_append_result(tls_session, code);
+
+               eap_fast_update_icmk(request, tls_session, (uint8_t *)&t->isk);
+               eap_fast_append_crypto_binding(request, tls_session);
+
+               code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+               break;
+       }
+       case EAP_FAST_PROVISIONING:
+               t->result_final = true;
+
+               eap_fast_append_result(tls_session, code);
+
+               if (t->pac.send) {
+                       RDEBUG2("Peer requires new PAC");
+                       eap_fast_send_pac_tunnel(request, tls_session);
+                       code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+                       break;
+               }
+
+               t->stage = EAP_FAST_COMPLETE;
+               FALL_THROUGH;
+
+       case EAP_FAST_COMPLETE:
+               /*
+                * RFC 5422 section 3.5 - Network Access after EAP-FAST Provisioning
+                */
+               if (t->pac.type && t->pac.expired) {
+                       REDEBUG("Rejecting expired PAC.");
+                       code = FR_RADIUS_CODE_ACCESS_REJECT;
+                       break;
+               }
+
+               if (t->mode == EAP_FAST_PROVISIONING_ANON) {
+                       REDEBUG("Rejecting unauthenticated provisioning");
+                       code = FR_RADIUS_CODE_ACCESS_REJECT;
+                       break;
+               }
+
+               /*
+                * eap_crypto_mppe_keys() is unsuitable for EAP-FAST as Cisco decided
+                * it would be a great idea to flip the recv/send keys around
+                */
+               #define EAPTLS_MPPE_KEY_LEN 32
+               eap_add_reply(request, attr_ms_mppe_recv_key, t->msk, EAPTLS_MPPE_KEY_LEN);
+               eap_add_reply(request, attr_ms_mppe_send_key, &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN);
+               eap_add_reply(request, attr_eap_msk, t->msk, EAP_FAST_KEY_LEN);
+               eap_add_reply(request, attr_eap_emsk, t->emsk, EAP_EMSK_LEN);
+
+               break;
+
+       default:
+               RERROR("Internal sanity check failed in EAP-FAST at %d", t->stage);
+               code = FR_RADIUS_CODE_ACCESS_REJECT;
+       }
+
+       return code;
+}
+
 /** Allocate the FAST per-session data
  *
  */
@@ -200,8 +1209,6 @@ static eap_fast_tunnel_t *eap_fast_alloc(TALLOC_CTX *ctx, rlm_eap_fast_t const *
        t->a_id = inst->a_id;
        t->pac_opaque_key = (uint8_t const *)inst->pac_opaque_key;
 
-       t->server_cs = inst->server_cs;
-
        return t;
 }
 
@@ -441,7 +1448,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct
        /*
         *      Process the FAST portion of the request.
         */
-       switch (eap_fast_process(request, eap_session, tls_session)) {
+       switch (eap_fast_process(request, mctx, eap_session, tls_session)) {
        case FR_RADIUS_CODE_ACCESS_REJECT:
                eap_tls_fail(request, eap_session);
                RETURN_UNLANG_FAIL;
@@ -633,8 +1640,6 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
        rlm_eap_fast_t          *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_fast_t);
        CONF_SECTION            *conf = mctx->mi->conf;
 
-       inst->server_cs = virtual_server_cs(inst->virtual_server);
-
        inst->default_provisioning_method = eap_name2type(inst->default_provisioning_method_name);
        if (!inst->default_provisioning_method) {
                cf_log_err_by_child(conf, "default_provisioning_eap_type", "Unknown EAP type %s",
index f7be0b51c1bf93c2005e536c216cf431ecfe5772..f0af56b146b5b6ee32511fd8818b5a0b237befad 100644 (file)
@@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" ""
 TARGET         := $(TARGETNAME)$(L)
 endif
 
-SOURCES                := $(TARGETNAME).c peap.c
+SOURCES                := $(TARGETNAME).c
 
 SRC_INCDIRS    := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
 
diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h b/src/modules/rlm_eap/types/rlm_eap_peap/eap_peap.h
deleted file mode 100644 (file)
index 107ba67..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#pragma once
-/*
- * eap_peap.h
- *
- * 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 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-RCSIDH(eap_peap_h, "$Id$")
-
-#include <freeradius-devel/eap/tls.h>
-
-typedef enum {
-       PEAP_STATUS_INVALID,
-       PEAP_STATUS_SENT_TLV_SUCCESS,
-       PEAP_STATUS_SENT_TLV_FAILURE,
-       PEAP_STATUS_TUNNEL_ESTABLISHED,
-       PEAP_STATUS_INNER_IDENTITY_REQ_SENT,
-       PEAP_STATUS_PHASE2_INIT,
-       PEAP_STATUS_PHASE2,
-} peap_status;
-
-typedef enum {
-       PEAP_RESUMPTION_NO,
-       PEAP_RESUMPTION_YES,
-       PEAP_RESUMPTION_MAYBE
-} peap_resumption;
-
-typedef struct {
-       fr_pair_t       *username;
-       peap_status     status;
-       bool            home_access_accept;
-       int             default_method;
-       CONF_SECTION    *server_cs;
-       peap_resumption session_resumption_state;
-} peap_tunnel_t;
-
-extern HIDDEN fr_dict_attr_t const *attr_auth_type;
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-
-
-#define EAP_TLV_SUCCESS (1)
-#define EAP_TLV_FAILURE (2)
-#define EAP_TLV_ACK_RESULT (3)
-
-#define FR_PEAP_EXTENSIONS_TYPE 33
-
-/*
- *     Process the PEAP portion of an EAP-PEAP request.
- */
-unlang_action_t eap_peap_process(unlang_result_t *p_result, request_t *request,
-                                eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c
deleted file mode 100644 (file)
index 71be8b3..0000000
+++ /dev/null
@@ -1,634 +0,0 @@
-/*
- * peap.c contains the interfaces 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 Alan DeKok (aland@freeradius.org)
- *   @copyright 2006 The FreeRADIUS server project
- */
-
-RCSID("$Id$")
-USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
-
-#include <freeradius-devel/radius/radius.h>
-#include <freeradius-devel/radius/defs.h>
-
-#include "eap_peap.h"
-
-/*
- *     Send protected EAP-Failure
- *
- *       Result-TLV = Failure
- */
-static int eap_peap_failure(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       uint8_t tlv_packet[11];
-
-       RDEBUG2("FAILURE");
-
-       tlv_packet[0] = FR_EAP_CODE_REQUEST;
-       tlv_packet[1] = eap_session->this_round->response->id +1;
-       tlv_packet[2] = 0;
-       tlv_packet[3] = 11;     /* length of this packet */
-       tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
-       tlv_packet[5] = 0x80;
-       tlv_packet[6] = EAP_TLV_ACK_RESULT;
-       tlv_packet[7] = 0;
-       tlv_packet[8] = 2;      /* length of the data portion */
-       tlv_packet[9] = 0;
-       tlv_packet[10] = EAP_TLV_FAILURE;
-
-       (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
-
-       /*
-        *      FIXME: Check the return code.
-        */
-       fr_tls_session_send(request, tls_session);
-
-       return 1;
-}
-
-
-/*
- *     Send protected EAP-Success
- *
- *       Result-TLV = Success
- */
-static int eap_peap_success(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       uint8_t tlv_packet[11];
-
-       RDEBUG2("SUCCESS");
-
-       tlv_packet[0] = FR_EAP_CODE_REQUEST;
-       tlv_packet[1] = eap_session->this_round->response->id +1;
-       tlv_packet[2] = 0;
-       tlv_packet[3] = 11;     /* length of this packet */
-       tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
-       tlv_packet[5] = 0x80;   /* mandatory AVP */
-       tlv_packet[6] = EAP_TLV_ACK_RESULT;
-       tlv_packet[7] = 0;
-       tlv_packet[8] = 2;      /* length of the data portion */
-       tlv_packet[9] = 0;
-       tlv_packet[10] = EAP_TLV_SUCCESS;
-
-       (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
-
-       /*
-        *      FIXME: Check the return code.
-        */
-       fr_tls_session_send(request, tls_session);
-
-       return 1;
-}
-
-
-static int eap_peap_identity(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       eap_packet_raw_t eap_packet;
-
-       eap_packet.code = FR_EAP_CODE_REQUEST;
-       eap_packet.id = eap_session->this_round->response->id + 1;
-       eap_packet.length[0] = 0;
-       eap_packet.length[1] = EAP_HEADER_LEN + 1;
-       eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
-
-       (tls_session->record_from_buff)(&tls_session->clean_in, &eap_packet, sizeof(eap_packet));
-       fr_tls_session_send(request, tls_session);
-       (tls_session->record_init)(&tls_session->clean_in);
-
-       return 1;
-}
-
-/*
- *     Verify the tunneled EAP message.
- */
-static int eap_peap_verify(request_t *request, peap_tunnel_t *peap_tunnel,
-                          uint8_t const *data, size_t data_len)
-{
-       eap_packet_raw_t const  *eap_packet = (eap_packet_raw_t const *) data;
-       eap_type_t              eap_method;
-
-       /*
-        *      No data, OR only 1 byte of EAP type.
-        */
-       if (!data || (data_len == 0) || ((data_len <= 1) && (data[0] != FR_EAP_METHOD_IDENTITY))) return 0;
-
-       /*
-        *  Since the full EAP header is sent for the EAP Extensions type (Type 33),
-        *  but not for other Types, it is difficult for the implementation to distinguish
-        *  an Extensions Request (Code 1) from an EAP Type 1 (Identity) Request packet.
-        *
-        *  i.e. The only way to validate PEAP inner method packets properly is to know
-        *  we just send a protected success/failure.
-        */
-       switch (peap_tunnel->status) {
-       case PEAP_STATUS_SENT_TLV_SUCCESS:
-       case PEAP_STATUS_SENT_TLV_FAILURE:
-               if (eap_packet->data[0] != FR_PEAP_EXTENSIONS_TYPE) {
-                       REDEBUG("Invalid inner tunnel data, expected method (%u), got (%u)",
-                               FR_PEAP_EXTENSIONS_TYPE, eap_packet->data[0]);
-                       return -1;
-               }
-               return 0;
-
-       default:
-               break;
-       }
-
-       eap_method = data[0];   /* Inner EAP header misses off code and identifier */
-       switch (eap_method) {
-       case FR_EAP_METHOD_IDENTITY:
-               RDEBUG2("Received EAP-Identity-Response");
-               return 0;
-
-       /*
-        *      We normally do Microsoft MS-CHAPv2 (26), versus
-        *      Cisco MS-CHAPv2 (29).
-        */
-       case FR_EAP_METHOD_MSCHAPV2:
-       default:
-               RDEBUG2("EAP method %s (%d)", eap_type2name(eap_method), eap_method);
-               return 0;
-       }
-
-}
-
-/*
- *     Convert a pseudo-EAP packet to a list of fr_pair_t's.
- */
-static void eap_peap_inner_to_pairs(TALLOC_CTX *ctx, fr_pair_list_t *pairs,
-                                   eap_round_t *eap_round,
-                                   uint8_t const *data, size_t data_len)
-{
-       size_t          total;
-       uint8_t         *p;
-       fr_pair_t       *vp = NULL;
-
-       if (data_len > 65535) return; /* paranoia */
-
-       MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
-       total = data_len;
-       if (total > 249) total = 249;
-
-       /*
-        *      Hand-build an EAP packet from the crap in PEAP version 0.
-        */
-       MEM(fr_pair_value_mem_alloc(vp, &p, EAP_HEADER_LEN + total, false) == 0);
-       p[0] = FR_EAP_CODE_RESPONSE;
-       p[1] = eap_round->response->id;
-       p[2] = (data_len + EAP_HEADER_LEN) >> 8;
-       p[3] = (data_len + EAP_HEADER_LEN) & 0xff;
-       memcpy(p + EAP_HEADER_LEN, data, total);
-
-       fr_pair_append(pairs, vp);
-       while (total < data_len) {
-               MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
-               fr_pair_value_memdup(vp, data + total, (data_len - total), false);
-
-               total += vp->vp_length;
-
-               fr_pair_append(pairs, vp);
-       }
-}
-
-
-/*
- *     Convert a list of fr_pair_t's to an EAP packet, through the
- *     simple expedient of dumping the EAP message
- */
-static int eap_peap_inner_from_pairs(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *vps)
-{
-       fr_pair_t *this;
-
-       fr_assert(!fr_pair_list_empty(vps));
-
-       /*
-        *      Send the EAP data in the first attribute, WITHOUT the
-        *      header.
-        */
-       this = fr_pair_list_head(vps);
-       (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets + EAP_HEADER_LEN,
-                                       this->vp_length - EAP_HEADER_LEN);
-
-       /*
-        *      Send the rest of the EAP data, but skipping the first VP.
-        */
-       for (this = fr_pair_list_next(vps, this);
-            this;
-            this = fr_pair_list_next(vps, this)) {
-               (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets, this->vp_length);
-       }
-
-       fr_tls_session_send(request, tls_session);
-
-       return 1;
-}
-
-
-/*
- *     See if there's a TLV in the response.
- */
-static int eap_peap_check_tlv(request_t *request, uint8_t const *data, size_t data_len)
-{
-       eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
-
-       if (data_len < 11) return 0;
-
-       /*
-        *      Look for success or failure.
-        */
-       if ((eap_packet->code == FR_EAP_CODE_RESPONSE) &&
-           (eap_packet->data[0] == FR_PEAP_EXTENSIONS_TYPE)) {
-               if (data[10] == EAP_TLV_SUCCESS) {
-                       return 1;
-               }
-
-               if (data[10] == EAP_TLV_FAILURE) {
-                       RDEBUG2("Client rejected our response.  The password is probably incorrect");
-                       return 0;
-               }
-       }
-
-       RDEBUG2("Unknown TLV %02x", data[10]);
-
-       return 0;
-}
-
-
-/*
- *     Use a reply packet to determine what to do.
- */
-static unlang_action_t process_reply(unlang_result_t *p_result, request_t *request, UNUSED void *uctx)
-{
-       eap_session_t           *eap_session = talloc_get_type_abort(uctx, eap_session_t);
-       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
-       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
-       fr_pair_list_t          vps;
-       peap_tunnel_t           *t = tls_session->opaque;
-       request_t               *parent = request->parent;
-       fr_packet_t             *reply = request->reply;
-
-       p_result->priority = MOD_PRIORITY_MAX;
-
-       if (RDEBUG_ENABLED2) {
-
-               /*
-                *      Note that we don't do *anything* with the reply
-                *      attributes.
-                */
-               if (FR_RADIUS_PACKET_CODE_VALID(reply->code)) {
-                       RDEBUG2("Got tunneled reply %s", fr_radius_packet_name[reply->code]);
-               } else {
-                       RDEBUG2("Got tunneled reply code %i", reply->code);
-               }
-               log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL);
-       }
-
-       switch (reply->code) {
-       case FR_RADIUS_CODE_ACCESS_ACCEPT:
-               RDEBUG2("Tunneled authentication was successful");
-               t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
-               eap_peap_success(request, eap_session, tls_session);
-               RETURN_UNLANG_HANDLED;
-
-       case FR_RADIUS_CODE_ACCESS_REJECT:
-               RDEBUG2("Tunneled authentication was rejected");
-               t->status = PEAP_STATUS_SENT_TLV_FAILURE;
-               eap_peap_failure(request, eap_session, tls_session);
-               RETURN_UNLANG_HANDLED;
-
-       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
-               RDEBUG2("Got tunneled Access-Challenge");
-
-               /*
-                *      PEAP takes only EAP-Message attributes inside
-                *      of the tunnel.  Any Reply-Message in the
-                *      Access-Challenge is ignored.
-                */
-               fr_pair_list_init(&vps);
-               MEM(fr_pair_list_copy_by_da(t, &vps, &request->reply_pairs, attr_eap_message, 0) >= 0);
-
-               /*
-                *      Handle the ACK, by tunneling any necessary reply
-                *      VP's back to the client.
-                */
-               if (!fr_pair_list_empty(&vps)) {
-                       eap_peap_inner_from_pairs(parent, tls_session, &vps);
-                       fr_pair_list_free(&vps);
-               }
-               RETURN_UNLANG_HANDLED;
-
-       default:
-               RDEBUG2("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
-               RETURN_UNLANG_REJECT;
-       }
-}
-
-
-static char const *peap_state(peap_tunnel_t *t)
-{
-       switch (t->status) {
-       case PEAP_STATUS_TUNNEL_ESTABLISHED:
-               return "TUNNEL ESTABLISHED";
-
-       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 "?";
-}
-
-/*
- *     Process the pseudo-EAP contents of the tunneled data.
- */
-unlang_action_t eap_peap_process(unlang_result_t *p_result, request_t *request,
-                                eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       peap_tunnel_t   *t = tls_session->opaque;
-       request_t       *child = NULL;
-       fr_pair_t       *vp;
-       rlm_rcode_t     rcode = RLM_MODULE_REJECT;
-       uint8_t const   *data;
-       size_t          data_len;
-       eap_round_t     *eap_round = eap_session->this_round;
-
-       /*
-        *      Just look at the buffer directly, without doing
-        *      record_to_buff.  This lets us avoid another data copy.
-        */
-       data_len = tls_session->clean_out.used;
-       tls_session->clean_out.used = 0;
-       data = tls_session->clean_out.data;
-
-       RDEBUG2("PEAP state %s", peap_state(t));
-
-       if ((t->status != PEAP_STATUS_TUNNEL_ESTABLISHED) && (eap_peap_verify(request, t, data, data_len) < 0)) {
-               REDEBUG("Tunneled data is invalid");
-               RETURN_UNLANG_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;
-                       /* we're good, send success TLV */
-                       t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
-                       eap_peap_success(request, eap_session, tls_session);
-
-               } else {
-                       /* send an identity request */
-                       t->session_resumption_state = PEAP_RESUMPTION_NO;
-                       t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
-                       eap_peap_identity(request, eap_session, tls_session);
-               }
-               rcode = RLM_MODULE_HANDLED;
-               goto finish;
-
-       case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
-               /* we're expecting an identity response */
-               if (data[0] != FR_EAP_METHOD_IDENTITY) {
-                       REDEBUG("Expected EAP-Identity, got something else");
-                       rcode = RLM_MODULE_REJECT;
-                       goto finish;
-               }
-
-               /*
-                *      Save it for later.
-                */
-               MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
-               t->username->vp_tainted = true;
-
-               fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
-
-               RDEBUG2("Got inner identity \"%pV\"", &t->username->data);
-               t->status = PEAP_STATUS_PHASE2_INIT;
-               break;
-
-       /*
-        *      If we authenticated the user, then it's OK.
-        */
-       case PEAP_STATUS_SENT_TLV_SUCCESS:
-               if (eap_peap_check_tlv(request, data, data_len)) {
-                       RDEBUG2("Success");
-                       rcode = RLM_MODULE_OK;
-                       goto finish;
-               }
-
-               /*
-                *      Otherwise, the client rejected the session
-                *      resumption.  If the session is being re-used,
-                *      we need to do a full authentication.
-                *
-                *      We do this by sending an EAP-Identity request
-                *      inside of the PEAP tunnel.
-                */
-               if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
-                       RDEBUG2("Client rejected session resumption.  Re-starting full authentication");
-
-                       /*
-                        *      Mark session resumption status.
-                        */
-                       t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
-                       t->session_resumption_state = PEAP_RESUMPTION_NO;
-
-                       eap_peap_identity(request, eap_session, tls_session);
-                       rcode = RLM_MODULE_HANDLED;
-                       goto finish;
-               }
-
-               REDEBUG("Sent a success, but received something weird in return");
-               rcode = RLM_MODULE_REJECT;
-               goto finish;
-
-       /*
-        *      Supplicant ACKs our failure.
-        */
-       case PEAP_STATUS_SENT_TLV_FAILURE:
-               RINDENT();
-               REDEBUG("The users session was previously rejected: returning reject (again.)");
-               RIDEBUG("This means you need to read the PREVIOUS messages in the debug output");
-               RIDEBUG("to find out the reason why the user was rejected");
-               RIDEBUG("Look for \"reject\" or \"fail\".  Those earlier messages will tell you");
-               RIDEBUG("what went wrong, and how to fix the problem");
-               REXDENT();
-
-               RETURN_UNLANG_REJECT;
-
-               case PEAP_STATUS_PHASE2_INIT:
-                       RDEBUG2("In state machine in phase2 init?");
-                       break;
-
-               case PEAP_STATUS_PHASE2:
-                       break;
-
-               default:
-                       REDEBUG("Unhandled state in peap");
-                       rcode = RLM_MODULE_REJECT;
-                       goto finish;
-       }
-
-       MEM(child = unlang_subrequest_alloc(request, request->proto_dict));
-       fr_assert(fr_pair_list_empty(&child->request_pairs));
-
-       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:
-       {
-               size_t len;
-               uint8_t *q;
-
-               fr_assert(t->username);
-
-               len = t->username->vp_length + EAP_HEADER_LEN + 1;
-               t->status = PEAP_STATUS_PHASE2;
-
-               MEM(vp = fr_pair_afrom_da(child->request_ctx, attr_eap_message));
-               MEM(fr_pair_value_mem_alloc(vp, &q, len, false) == 0);
-               q[0] = FR_EAP_CODE_RESPONSE;
-               q[1] = eap_round->response->id;
-               q[2] = (len >> 8) & 0xff;
-               q[3] = len & 0xff;
-               q[4] = FR_EAP_METHOD_IDENTITY;
-               memcpy(q + EAP_HEADER_LEN + 1,
-                      t->username->vp_strvalue, t->username->vp_length);
-               fr_pair_append(&child->request_pairs, vp);
-       }
-               break;
-
-       case PEAP_STATUS_PHASE2:
-               eap_peap_inner_to_pairs(child->request_ctx, &child->request_pairs,
-                                       eap_round, data, data_len);
-               if (fr_pair_list_empty(&child->request_pairs)) {
-                       TALLOC_FREE(child);
-                       RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
-                       rcode = RLM_MODULE_REJECT;
-                       goto finish;
-               }
-               break;
-
-       default:
-               REDEBUG("Invalid state change in PEAP");
-               rcode = RLM_MODULE_REJECT;
-               goto finish;
-       }
-
-       RDEBUG2("Got tunneled request");
-       log_request_pair_list(L_DBG_LVL_2, request, NULL, &child->request_pairs, NULL);
-
-       /*
-        *      Update other items in the request_t data structure.
-        */
-       if (!t->username) {
-               /*
-                *      There's no User-Name in the tunneled session,
-                *      so we add one here, by pulling it out of the
-                *      EAP-Identity packet.
-                */
-               if ((data[0] == FR_EAP_METHOD_IDENTITY) && (data_len > 1)) {
-                       MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
-                       fr_assert(t->username != NULL);
-                       t->username->vp_tainted = true;
-
-                       fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
-
-                       RDEBUG2("Got tunneled identity of %pV", &t->username->data);
-               }
-       } /* else there WAS a t->username */
-
-       if (t->username) {
-               vp = fr_pair_copy(child->request_ctx, t->username);
-               fr_pair_append(&child->request_pairs, vp);
-               RDEBUG2("Setting request.User-Name from tunneled (inner) identity \"%s\"",
-                       vp->vp_strvalue);
-       } else {
-               RDEBUG2("No tunnel username (SSL resumption?)");
-       }
-
-       /*
-        *      Set the child up for execution.  This represents
-        *      a pseudo protocol inside of PEAPs inner EAP method.
-        */
-       if (unlang_subrequest_child_push(&eap_session->submodule_result, child,
-                                        child,
-                                        false, UNLANG_SUB_FRAME) < 0) goto finish;
-
-       /*
-        *      Setup a function in thie child to process the
-        *      result of the subrequest.
-        */
-       if (unlang_function_push_with_result(NULL,
-                                            child,
-                                            NULL,
-                                            /*
-                                             * Run in the child after the virtual sever executes.
-                                             * This sets the rcode for the subrequest, which is
-                                             * written to eap_session->submodule_result.
-                                             */
-                                            process_reply,
-                                            NULL, 0,
-                                            UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
-
-       /*
-        *      Run inner tunnel in the context of the child
-        */
-       if (unlikely(eap_virtual_server(child, eap_session, t->server_cs) == UNLANG_ACTION_FAIL)) {
-               rcode = RLM_MODULE_FAIL;
-               goto finish;
-       }
-
-       /*
-        *      We now yield to the subrequest.  unlang_subrequest_child_push
-        *      pushed a new frame in the context of the parent which'll start
-        *      the subrequest.
-        */
-       return UNLANG_ACTION_PUSHED_CHILD;
-
-finish:
-       if (child) {
-               /*
-                *      We can't just free the child, we need to detach it
-                *      and then let the interpreter to unwind and eventually
-                *      free the request.
-                */
-               request_detach(child);
-               unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
-       }
-
-       RETURN_UNLANG_RCODE(rcode);
-}
index b1ba5fe27c14cc31b241345da8ebc276734f6142..09eed869b36e6a7d79a82824f617870c24348ccb 100644 (file)
@@ -23,7 +23,6 @@
 RCSID("$Id$")
 
 #include <freeradius-devel/eap/tls.h>
-#include "eap_peap.h"
 
 typedef struct {
        SSL_CTX         *ssl_ctx;                       //!< Thread local SSL_CTX.
@@ -45,6 +44,36 @@ typedef struct {
        bool                    req_client_cert;        //!< Do we do require a client cert?
 } rlm_eap_peap_t;
 
+typedef enum {
+       PEAP_STATUS_INVALID,
+       PEAP_STATUS_SENT_TLV_SUCCESS,
+       PEAP_STATUS_SENT_TLV_FAILURE,
+       PEAP_STATUS_TUNNEL_ESTABLISHED,
+       PEAP_STATUS_INNER_IDENTITY_REQ_SENT,
+       PEAP_STATUS_PHASE2_INIT,
+       PEAP_STATUS_PHASE2,
+} peap_status;
+
+typedef enum {
+       PEAP_RESUMPTION_NO,
+       PEAP_RESUMPTION_YES,
+       PEAP_RESUMPTION_MAYBE
+} peap_resumption;
+
+typedef struct {
+       fr_pair_t       *username;
+       peap_status     status;
+       bool            home_access_accept;
+       int             default_method;
+       peap_resumption session_resumption_state;
+} peap_tunnel_t;
+
+#define EAP_TLV_SUCCESS (1)
+#define EAP_TLV_FAILURE (2)
+#define EAP_TLV_ACK_RESULT (3)
+
+#define FR_PEAP_EXTENSIONS_TYPE 33
+
 static conf_parser_t submodule_config[] = {
        { FR_CONF_OFFSET("tls", rlm_eap_peap_t, tls_conf_name) },
 
@@ -54,7 +83,7 @@ static conf_parser_t submodule_config[] = {
 
        { FR_CONF_OFFSET_TYPE_FLAGS("virtual_server", FR_TYPE_VOID, CONF_FLAG_REQUIRED | CONF_FLAG_NOT_EMPTY, rlm_eap_peap_t, virtual_server),
                                    .func = virtual_server_cf_parse,
-                                   .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "eap_peap"} },
+                                   .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "radius"} },
        { FR_CONF_OFFSET("require_client_cert", rlm_eap_peap_t, req_client_cert), .dflt = "no" },
 
        CONF_PARSER_TERMINATOR
@@ -87,16 +116,618 @@ fr_dict_attr_autoload_t rlm_eap_peap_dict_attr[] = {
 };
 
 
+/*
+ *     Send protected EAP-Failure
+ *
+ *       Result-TLV = Failure
+ */
+static int eap_peap_failure(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       uint8_t tlv_packet[11];
+
+       RDEBUG2("FAILURE");
+
+       tlv_packet[0] = FR_EAP_CODE_REQUEST;
+       tlv_packet[1] = eap_session->this_round->response->id +1;
+       tlv_packet[2] = 0;
+       tlv_packet[3] = 11;     /* length of this packet */
+       tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
+       tlv_packet[5] = 0x80;
+       tlv_packet[6] = EAP_TLV_ACK_RESULT;
+       tlv_packet[7] = 0;
+       tlv_packet[8] = 2;      /* length of the data portion */
+       tlv_packet[9] = 0;
+       tlv_packet[10] = EAP_TLV_FAILURE;
+
+       (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
+
+       /*
+        *      FIXME: Check the return code.
+        */
+       fr_tls_session_send(request, tls_session);
+
+       return 1;
+}
+
+/*
+ *     Send protected EAP-Success
+ *
+ *       Result-TLV = Success
+ */
+static int eap_peap_success(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       uint8_t tlv_packet[11];
+
+       RDEBUG2("SUCCESS");
+
+       tlv_packet[0] = FR_EAP_CODE_REQUEST;
+       tlv_packet[1] = eap_session->this_round->response->id +1;
+       tlv_packet[2] = 0;
+       tlv_packet[3] = 11;     /* length of this packet */
+       tlv_packet[4] = FR_PEAP_EXTENSIONS_TYPE;
+       tlv_packet[5] = 0x80;   /* mandatory AVP */
+       tlv_packet[6] = EAP_TLV_ACK_RESULT;
+       tlv_packet[7] = 0;
+       tlv_packet[8] = 2;      /* length of the data portion */
+       tlv_packet[9] = 0;
+       tlv_packet[10] = EAP_TLV_SUCCESS;
+
+       (tls_session->record_from_buff)(&tls_session->clean_in, tlv_packet, 11);
+
+       /*
+        *      FIXME: Check the return code.
+        */
+       fr_tls_session_send(request, tls_session);
+
+       return 1;
+}
+
+
+static int eap_peap_identity(request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       eap_packet_raw_t eap_packet;
+
+       eap_packet.code = FR_EAP_CODE_REQUEST;
+       eap_packet.id = eap_session->this_round->response->id + 1;
+       eap_packet.length[0] = 0;
+       eap_packet.length[1] = EAP_HEADER_LEN + 1;
+       eap_packet.data[0] = FR_EAP_METHOD_IDENTITY;
+
+       (tls_session->record_from_buff)(&tls_session->clean_in, &eap_packet, sizeof(eap_packet));
+       fr_tls_session_send(request, tls_session);
+       (tls_session->record_init)(&tls_session->clean_in);
+
+       return 1;
+}
+
+/*
+ *     Verify the tunneled EAP message.
+ */
+static int eap_peap_verify(request_t *request, peap_tunnel_t *peap_tunnel,
+                          uint8_t const *data, size_t data_len)
+{
+       eap_packet_raw_t const  *eap_packet = (eap_packet_raw_t const *) data;
+       eap_type_t              eap_method;
+
+       /*
+        *      No data, OR only 1 byte of EAP type.
+        */
+       if (!data || (data_len == 0) || ((data_len <= 1) && (data[0] != FR_EAP_METHOD_IDENTITY))) return 0;
+
+       /*
+        *  Since the full EAP header is sent for the EAP Extensions type (Type 33),
+        *  but not for other Types, it is difficult for the implementation to distinguish
+        *  an Extensions Request (Code 1) from an EAP Type 1 (Identity) Request packet.
+        *
+        *  i.e. The only way to validate PEAP inner method packets properly is to know
+        *  we just send a protected success/failure.
+        */
+       switch (peap_tunnel->status) {
+       case PEAP_STATUS_SENT_TLV_SUCCESS:
+       case PEAP_STATUS_SENT_TLV_FAILURE:
+               if (eap_packet->data[0] != FR_PEAP_EXTENSIONS_TYPE) {
+                       REDEBUG("Invalid inner tunnel data, expected method (%u), got (%u)",
+                               FR_PEAP_EXTENSIONS_TYPE, eap_packet->data[0]);
+                       return -1;
+               }
+               return 0;
+
+       default:
+               break;
+       }
+
+       eap_method = data[0];   /* Inner EAP header misses off code and identifier */
+       switch (eap_method) {
+       case FR_EAP_METHOD_IDENTITY:
+               RDEBUG2("Received EAP-Identity-Response");
+               return 0;
+
+       /*
+        *      We normally do Microsoft MS-CHAPv2 (26), versus
+        *      Cisco MS-CHAPv2 (29).
+        */
+       case FR_EAP_METHOD_MSCHAPV2:
+       default:
+               RDEBUG2("EAP method %s (%d)", eap_type2name(eap_method), eap_method);
+               return 0;
+       }
+
+}
+
+/*
+ *     Convert a pseudo-EAP packet to a list of fr_pair_t's.
+ */
+static void eap_peap_inner_to_pairs(TALLOC_CTX *ctx, fr_pair_list_t *pairs,
+                                   eap_round_t *eap_round,
+                                   uint8_t const *data, size_t data_len)
+{
+       size_t          total;
+       uint8_t         *p;
+       fr_pair_t       *vp = NULL;
+
+       if (data_len > 65535) return; /* paranoia */
+
+       MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
+       total = data_len;
+       if (total > 249) total = 249;
+
+       /*
+        *      Hand-build an EAP packet from the crap in PEAP version 0.
+        */
+       MEM(fr_pair_value_mem_alloc(vp, &p, EAP_HEADER_LEN + total, false) == 0);
+       p[0] = FR_EAP_CODE_RESPONSE;
+       p[1] = eap_round->response->id;
+       p[2] = (data_len + EAP_HEADER_LEN) >> 8;
+       p[3] = (data_len + EAP_HEADER_LEN) & 0xff;
+       memcpy(p + EAP_HEADER_LEN, data, total);
+
+       fr_pair_append(pairs, vp);
+       while (total < data_len) {
+               MEM(vp = fr_pair_afrom_da(ctx, attr_eap_message));
+               fr_pair_value_memdup(vp, data + total, (data_len - total), false);
+
+               total += vp->vp_length;
+
+               fr_pair_append(pairs, vp);
+       }
+}
+
+
+/*
+ *     Convert a list of fr_pair_t's to an EAP packet, through the
+ *     simple expedient of dumping the EAP message
+ */
+static int eap_peap_inner_from_pairs(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *vps)
+{
+       fr_pair_t *this;
+
+       fr_assert(!fr_pair_list_empty(vps));
+
+       /*
+        *      Send the EAP data in the first attribute, WITHOUT the
+        *      header.
+        */
+       this = fr_pair_list_head(vps);
+       (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets + EAP_HEADER_LEN,
+                                       this->vp_length - EAP_HEADER_LEN);
+
+       /*
+        *      Send the rest of the EAP data, but skipping the first VP.
+        */
+       for (this = fr_pair_list_next(vps, this);
+            this;
+            this = fr_pair_list_next(vps, this)) {
+               (tls_session->record_from_buff)(&tls_session->clean_in, this->vp_octets, this->vp_length);
+       }
+
+       fr_tls_session_send(request, tls_session);
+
+       return 1;
+}
+
+
+/*
+ *     See if there's a TLV in the response.
+ */
+static int eap_peap_check_tlv(request_t *request, uint8_t const *data, size_t data_len)
+{
+       eap_packet_raw_t const *eap_packet = (eap_packet_raw_t const *) data;
+
+       if (data_len < 11) return 0;
+
+       /*
+        *      Look for success or failure.
+        */
+       if ((eap_packet->code == FR_EAP_CODE_RESPONSE) &&
+           (eap_packet->data[0] == FR_PEAP_EXTENSIONS_TYPE)) {
+               if (data[10] == EAP_TLV_SUCCESS) {
+                       return 1;
+               }
+
+               if (data[10] == EAP_TLV_FAILURE) {
+                       RDEBUG2("Client rejected our response.  The password is probably incorrect");
+                       return 0;
+               }
+       }
+
+       RDEBUG2("Unknown TLV %02x", data[10]);
+
+       return 0;
+}
+
+
+/*
+ *     Use a reply packet to determine what to do.
+ */
+static unlang_action_t process_reply(unlang_result_t *p_result, request_t *request, UNUSED void *uctx)
+{
+       eap_session_t           *eap_session = talloc_get_type_abort(uctx, eap_session_t);
+       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
+       fr_pair_list_t          vps;
+       peap_tunnel_t           *t = tls_session->opaque;
+       request_t               *parent = request->parent;
+       fr_packet_t             *reply = request->reply;
+
+       p_result->priority = MOD_PRIORITY_MAX;
+
+       if (RDEBUG_ENABLED2) {
+
+               /*
+                *      Note that we don't do *anything* with the reply
+                *      attributes.
+                */
+               if (FR_RADIUS_PACKET_CODE_VALID(reply->code)) {
+                       RDEBUG2("Got tunneled reply %s", fr_radius_packet_name[reply->code]);
+               } else {
+                       RDEBUG2("Got tunneled reply code %i", reply->code);
+               }
+               log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL);
+       }
+
+       switch (reply->code) {
+       case FR_RADIUS_CODE_ACCESS_ACCEPT:
+               RDEBUG2("Tunneled authentication was successful");
+               t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+               eap_peap_success(request, eap_session, tls_session);
+               RETURN_UNLANG_HANDLED;
+
+       case FR_RADIUS_CODE_ACCESS_REJECT:
+               RDEBUG2("Tunneled authentication was rejected");
+               t->status = PEAP_STATUS_SENT_TLV_FAILURE;
+               eap_peap_failure(request, eap_session, tls_session);
+               RETURN_UNLANG_HANDLED;
+
+       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+               RDEBUG2("Got tunneled Access-Challenge");
+
+               /*
+                *      PEAP takes only EAP-Message attributes inside
+                *      of the tunnel.  Any Reply-Message in the
+                *      Access-Challenge is ignored.
+                */
+               fr_pair_list_init(&vps);
+               MEM(fr_pair_list_copy_by_da(t, &vps, &request->reply_pairs, attr_eap_message, 0) >= 0);
+
+               /*
+                *      Handle the ACK, by tunneling any necessary reply
+                *      VP's back to the client.
+                */
+               if (!fr_pair_list_empty(&vps)) {
+                       eap_peap_inner_from_pairs(parent, tls_session, &vps);
+                       fr_pair_list_free(&vps);
+               }
+               RETURN_UNLANG_HANDLED;
+
+       default:
+               RDEBUG2("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+               RETURN_UNLANG_REJECT;
+       }
+}
+
+
+static char const *peap_state(peap_tunnel_t *t)
+{
+       switch (t->status) {
+       case PEAP_STATUS_TUNNEL_ESTABLISHED:
+               return "TUNNEL ESTABLISHED";
+
+       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 "?";
+}
+
+/*
+ *     Process the pseudo-EAP contents of the tunneled data.
+ */
+static unlang_action_t eap_peap_process(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request,
+                                       eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       peap_tunnel_t   *t = tls_session->opaque;
+       request_t       *child = NULL;
+       fr_pair_t       *vp;
+       rlm_rcode_t     rcode = RLM_MODULE_REJECT;
+       uint8_t const   *data;
+       size_t          data_len;
+       eap_round_t     *eap_round = eap_session->this_round;
+       rlm_eap_peap_t  *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_peap_t);
+
+       /*
+        *      Just look at the buffer directly, without doing
+        *      record_to_buff.  This lets us avoid another data copy.
+        */
+       data_len = tls_session->clean_out.used;
+       tls_session->clean_out.used = 0;
+       data = tls_session->clean_out.data;
+
+       RDEBUG2("PEAP state %s", peap_state(t));
+
+       if ((t->status != PEAP_STATUS_TUNNEL_ESTABLISHED) && (eap_peap_verify(request, t, data, data_len) < 0)) {
+               REDEBUG("Tunneled data is invalid");
+               RETURN_UNLANG_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;
+                       /* we're good, send success TLV */
+                       t->status = PEAP_STATUS_SENT_TLV_SUCCESS;
+                       eap_peap_success(request, eap_session, tls_session);
+
+               } else {
+                       /* send an identity request */
+                       t->session_resumption_state = PEAP_RESUMPTION_NO;
+                       t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+                       eap_peap_identity(request, eap_session, tls_session);
+               }
+               rcode = RLM_MODULE_HANDLED;
+               goto finish;
+
+       case PEAP_STATUS_INNER_IDENTITY_REQ_SENT:
+               /* we're expecting an identity response */
+               if (data[0] != FR_EAP_METHOD_IDENTITY) {
+                       REDEBUG("Expected EAP-Identity, got something else");
+                       rcode = RLM_MODULE_REJECT;
+                       goto finish;
+               }
+
+               /*
+                *      Save it for later.
+                */
+               MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+               t->username->vp_tainted = true;
+
+               fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
+
+               RDEBUG2("Got inner identity \"%pV\"", &t->username->data);
+               t->status = PEAP_STATUS_PHASE2_INIT;
+               break;
+
+       /*
+        *      If we authenticated the user, then it's OK.
+        */
+       case PEAP_STATUS_SENT_TLV_SUCCESS:
+               if (eap_peap_check_tlv(request, data, data_len)) {
+                       RDEBUG2("Success");
+                       rcode = RLM_MODULE_OK;
+                       goto finish;
+               }
+
+               /*
+                *      Otherwise, the client rejected the session
+                *      resumption.  If the session is being re-used,
+                *      we need to do a full authentication.
+                *
+                *      We do this by sending an EAP-Identity request
+                *      inside of the PEAP tunnel.
+                */
+               if (t->session_resumption_state == PEAP_RESUMPTION_YES) {
+                       RDEBUG2("Client rejected session resumption.  Re-starting full authentication");
+
+                       /*
+                        *      Mark session resumption status.
+                        */
+                       t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT;
+                       t->session_resumption_state = PEAP_RESUMPTION_NO;
+
+                       eap_peap_identity(request, eap_session, tls_session);
+                       rcode = RLM_MODULE_HANDLED;
+                       goto finish;
+               }
+
+               REDEBUG("Sent a success, but received something weird in return");
+               rcode = RLM_MODULE_REJECT;
+               goto finish;
+
+       /*
+        *      Supplicant ACKs our failure.
+        */
+       case PEAP_STATUS_SENT_TLV_FAILURE:
+               RINDENT();
+               REDEBUG("The users session was previously rejected: returning reject (again.)");
+               RIDEBUG("This means you need to read the PREVIOUS messages in the debug output");
+               RIDEBUG("to find out the reason why the user was rejected");
+               RIDEBUG("Look for \"reject\" or \"fail\".  Those earlier messages will tell you");
+               RIDEBUG("what went wrong, and how to fix the problem");
+               REXDENT();
+
+               RETURN_UNLANG_REJECT;
+
+               case PEAP_STATUS_PHASE2_INIT:
+                       RDEBUG2("In state machine in phase2 init?");
+                       break;
+
+               case PEAP_STATUS_PHASE2:
+                       break;
+
+               default:
+                       REDEBUG("Unhandled state in peap");
+                       rcode = RLM_MODULE_REJECT;
+                       goto finish;
+       }
+
+       MEM(child = unlang_subrequest_alloc(request, request->proto_dict));
+       fr_assert(fr_pair_list_empty(&child->request_pairs));
+
+       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:
+       {
+               size_t len;
+               uint8_t *q;
+
+               fr_assert(t->username);
+
+               len = t->username->vp_length + EAP_HEADER_LEN + 1;
+               t->status = PEAP_STATUS_PHASE2;
+
+               MEM(vp = fr_pair_afrom_da(child->request_ctx, attr_eap_message));
+               MEM(fr_pair_value_mem_alloc(vp, &q, len, false) == 0);
+               q[0] = FR_EAP_CODE_RESPONSE;
+               q[1] = eap_round->response->id;
+               q[2] = (len >> 8) & 0xff;
+               q[3] = len & 0xff;
+               q[4] = FR_EAP_METHOD_IDENTITY;
+               memcpy(q + EAP_HEADER_LEN + 1,
+                      t->username->vp_strvalue, t->username->vp_length);
+               fr_pair_append(&child->request_pairs, vp);
+       }
+               break;
+
+       case PEAP_STATUS_PHASE2:
+               eap_peap_inner_to_pairs(child->request_ctx, &child->request_pairs,
+                                       eap_round, data, data_len);
+               if (fr_pair_list_empty(&child->request_pairs)) {
+                       TALLOC_FREE(child);
+                       RDEBUG2("Unable to convert tunneled EAP packet to internal server data structures");
+                       rcode = RLM_MODULE_REJECT;
+                       goto finish;
+               }
+               break;
+
+       default:
+               REDEBUG("Invalid state change in PEAP");
+               rcode = RLM_MODULE_REJECT;
+               goto finish;
+       }
+
+       RDEBUG2("Got tunneled request");
+       log_request_pair_list(L_DBG_LVL_2, request, NULL, &child->request_pairs, NULL);
+
+       /*
+        *      Update other items in the request_t data structure.
+        */
+       if (!t->username) {
+               /*
+                *      There's no User-Name in the tunneled session,
+                *      so we add one here, by pulling it out of the
+                *      EAP-Identity packet.
+                */
+               if ((data[0] == FR_EAP_METHOD_IDENTITY) && (data_len > 1)) {
+                       MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+                       fr_assert(t->username != NULL);
+                       t->username->vp_tainted = true;
+
+                       fr_pair_value_bstrndup(t->username, (char const *)data + 1, data_len - 1, true);
+
+                       RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+               }
+       } /* else there WAS a t->username */
+
+       if (t->username) {
+               vp = fr_pair_copy(child->request_ctx, t->username);
+               fr_pair_append(&child->request_pairs, vp);
+               RDEBUG2("Setting request.User-Name from tunneled (inner) identity \"%s\"",
+                       vp->vp_strvalue);
+       } else {
+               RDEBUG2("No tunnel username (SSL resumption?)");
+       }
+
+       /*
+        *      Set the child up for execution.  This represents
+        *      a pseudo protocol inside of PEAPs inner EAP method.
+        */
+       if (unlang_subrequest_child_push(&eap_session->submodule_result, child,
+                                        child,
+                                        false, UNLANG_SUB_FRAME) < 0) goto finish;
+
+       /*
+        *      Setup a function in thie child to process the
+        *      result of the subrequest.
+        */
+       if (unlang_function_push_with_result(NULL,
+                                            child,
+                                            NULL,
+                                            /*
+                                             * Run in the child after the virtual sever executes.
+                                             * This sets the rcode for the subrequest, which is
+                                             * written to eap_session->submodule_result.
+                                             */
+                                            process_reply,
+                                            NULL, 0,
+                                            UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
+
+       /*
+        *      Run inner tunnel in the context of the child
+        */
+       if (unlikely(eap_virtual_server(child, eap_session, inst->virtual_server) == UNLANG_ACTION_FAIL)) {
+               rcode = RLM_MODULE_FAIL;
+               goto finish;
+       }
+
+       /*
+        *      We now yield to the subrequest.  unlang_subrequest_child_push
+        *      pushed a new frame in the context of the parent which'll start
+        *      the subrequest.
+        */
+       return UNLANG_ACTION_PUSHED_CHILD;
+
+finish:
+       if (child) {
+               /*
+                *      We can't just free the child, we need to detach it
+                *      and then let the interpreter to unwind and eventually
+                *      free the request.
+                */
+               request_detach(child);
+               unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
+       }
+
+       RETURN_UNLANG_RCODE(rcode);
+}
+
 /*
  *     Allocate the PEAP per-session data
  */
-static peap_tunnel_t *peap_alloc(TALLOC_CTX *ctx, rlm_eap_peap_t *inst)
+static peap_tunnel_t *peap_alloc(TALLOC_CTX *ctx)
 {
        peap_tunnel_t *t;
 
        t = talloc_zero(ctx, peap_tunnel_t);
-
-       t->server_cs = inst->server_cs;
        t->session_resumption_state = PEAP_RESUMPTION_MAYBE;
 
        return t;
@@ -170,7 +801,6 @@ static unlang_action_t process_rcode(unlang_result_t *p_result, module_ctx_t con
 
 static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       rlm_eap_peap_t          *inst = talloc_get_type(mctx->mi->data, rlm_eap_peap_t);
        eap_session_t           *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
        eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
        fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
@@ -238,7 +868,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct
         *      We may need PEAP data associated with the session, so
         *      allocate it here, if it wasn't already alloacted.
         */
-       if (!tls_session->opaque) tls_session->opaque = peap_alloc(tls_session, inst);
+       if (!tls_session->opaque) tls_session->opaque = peap_alloc(tls_session);
 
        /*
         *      Setup the resume point to prepare the correct reply based on
@@ -249,7 +879,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct
        /*
         *      Process the PEAP portion of the request.
         */
-       return eap_peap_process(&eap_session->submodule_result, request, eap_session, tls_session);
+       return eap_peap_process(&eap_session->submodule_result, mctx, request, eap_session, tls_session);
 }
 
 /*
@@ -329,7 +959,7 @@ static unlang_action_t mod_session_init_resume(unlang_result_t *p_result, module
         *      Session resumption requires the storage of data, so
         *      allocate it if it doesn't already exist.
         */
-       tls_session->opaque = peap_alloc(tls_session, inst);
+       tls_session->opaque = peap_alloc(tls_session);
 
        eap_session->process = mod_handshake_process;
 
@@ -382,6 +1012,8 @@ static int mod_instantiate(module_inst_ctx_t const *mctx)
        rlm_eap_peap_t          *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_peap_t);
        CONF_SECTION            *conf = mctx->mi->conf;
 
+       fr_assert(inst->virtual_server);
+
        inst->server_cs = virtual_server_cs(inst->virtual_server);
 
        /*
index 76e914617ba95c5dfc2d0c4ba52e46c6134bb111..d8056b5a198a3dab6f6cbb28b2b183ce3f932e2c 100644 (file)
@@ -4,7 +4,7 @@ ifneq "$(OPENSSL_LIBS)" ""
 TARGET         := $(TARGETNAME)$(L)
 endif
 
-SOURCES                := $(TARGETNAME).c ttls.c
+SOURCES                := $(TARGETNAME).c
 
 SRC_INCDIRS    := ${top_srcdir}/src/modules/rlm_eap/ ${top_srcdir}/src/modules/rlm_eap/lib/base/
 
diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h b/src/modules/rlm_eap/types/rlm_eap_ttls/eap_ttls.h
deleted file mode 100644 (file)
index cd3907f..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-#pragma once
-/*
- *   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
- */
-
-/**
- * $Id$
- * @file eap_ttls.h
- * @brief Declarations for EAP-TTLS as defined by RFC 5281
- *
- * @copyright 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-RCSIDH(eap_ttls_h, "$Id$")
-
-#include <freeradius-devel/eap/tls.h>
-#include <freeradius-devel/radius/radius.h>
-#include <freeradius-devel/radius/defs.h>
-
-extern HIDDEN fr_dict_attr_t const *attr_eap_tls_require_client_cert;
-extern HIDDEN fr_dict_attr_t const *attr_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap2_success;
-extern HIDDEN fr_dict_attr_t const *attr_eap_message;
-extern HIDDEN fr_dict_attr_t const *attr_ms_chap_challenge;
-extern HIDDEN fr_dict_attr_t const *attr_reply_message;
-extern HIDDEN fr_dict_attr_t const *attr_eap_channel_binding_message;
-extern HIDDEN fr_dict_attr_t const *attr_user_name;
-extern HIDDEN fr_dict_attr_t const *attr_user_password;
-extern HIDDEN fr_dict_attr_t const *attr_vendor_specific;
-
-typedef struct {
-       fr_pair_t       *username;
-       bool            authenticated;
-       CONF_SECTION    *server_cs;
-} ttls_tunnel_t;
-
-/*
- *     Process the TTLS portion of an EAP-TTLS request.
- */
-unlang_action_t eap_ttls_process(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session) CC_HINT(nonnull);
-unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session);
index ae1613eabf2f99fee4194928ec6914dbff65f241..2df37f0e540692ac185cb0d4c079d883f5b665a5 100644 (file)
@@ -27,7 +27,8 @@ RCSID("$Id$")
 USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
 
 #include <freeradius-devel/eap/tls.h>
-#include "eap_ttls.h"
+#include <freeradius-devel/eap/chbind.h>
+#include <freeradius-devel/tls/strerror.h>
 
 typedef struct {
        SSL_CTX         *ssl_ctx;               //!< Thread local SSL_CTX.
@@ -63,6 +64,10 @@ typedef struct {
        bool                    req_client_cert;
 } rlm_eap_ttls_t;
 
+typedef struct {
+       fr_pair_t       *username;
+       bool            authenticated;
+} ttls_tunnel_t;
 
 static conf_parser_t submodule_config[] = {
        { FR_CONF_OFFSET("tls", rlm_eap_ttls_t, tls_conf_name) },
@@ -70,7 +75,7 @@ static conf_parser_t submodule_config[] = {
        { FR_CONF_DEPRECATED("use_tunneled_reply", rlm_eap_ttls_t, NULL), .dflt = "no" },
        { FR_CONF_OFFSET_TYPE_FLAGS("virtual_server", FR_TYPE_VOID, CONF_FLAG_REQUIRED | CONF_FLAG_NOT_EMPTY, rlm_eap_ttls_t, virtual_server),
                                    .func = virtual_server_cf_parse,
-                                   .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "eap_ttls"} },
+                                   .uctx = &(virtual_server_cf_parse_uctx_t){ .process_module_name = "radius"} },
        { FR_CONF_OFFSET("include_length", rlm_eap_ttls_t, include_length), .dflt = "yes" },
        { FR_CONF_OFFSET("require_client_cert", rlm_eap_ttls_t, req_client_cert), .dflt = "no" },
        CONF_PARSER_TERMINATOR
@@ -86,17 +91,17 @@ fr_dict_autoload_t rlm_eap_ttls_dict[] = {
        { NULL }
 };
 
-fr_dict_attr_t const *attr_eap_tls_require_client_cert;
+static fr_dict_attr_t const *attr_eap_tls_require_client_cert;
 
-fr_dict_attr_t const *attr_chap_challenge;
-fr_dict_attr_t const *attr_ms_chap2_success;
-fr_dict_attr_t const *attr_eap_message;
-fr_dict_attr_t const *attr_ms_chap_challenge;
-fr_dict_attr_t const *attr_reply_message;
-fr_dict_attr_t const *attr_eap_channel_binding_message;
-fr_dict_attr_t const *attr_user_name;
-fr_dict_attr_t const *attr_user_password;
-fr_dict_attr_t const *attr_vendor_specific;
+static fr_dict_attr_t const *attr_chap_challenge;
+static fr_dict_attr_t const *attr_ms_chap2_success;
+static fr_dict_attr_t const *attr_eap_message;
+static fr_dict_attr_t const *attr_ms_chap_challenge;
+static fr_dict_attr_t const *attr_reply_message;
+static fr_dict_attr_t const *attr_eap_channel_binding_message;
+static fr_dict_attr_t const *attr_user_name;
+static fr_dict_attr_t const *attr_user_password;
+static fr_dict_attr_t const *attr_vendor_specific;
 
 extern fr_dict_attr_autoload_t rlm_eap_ttls_dict_attr[];
 fr_dict_attr_autoload_t rlm_eap_ttls_dict_attr[] = {
@@ -114,15 +119,756 @@ fr_dict_attr_autoload_t rlm_eap_ttls_dict_attr[] = {
        { NULL }
 };
 
+
+#define FR_DIAMETER_AVP_FLAG_VENDOR    0x80
+#define FR_DIAMETER_AVP_FLAG_MANDATORY 0x40
+/*
+ *    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
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                           AVP Code                            |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |V M r r r r r r|                  AVP Length                   |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |                        Vendor-ID (opt)                        |
+ *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *   |    Data ...
+ *   +-+-+-+-+-+-+-+-+
+ */
+
+/*
+ *     Verify that the diameter packet is valid.
+ */
+static int diameter_verify(request_t *request, uint8_t const *data, unsigned int data_len)
+{
+       uint32_t attr;
+       uint32_t length;
+       unsigned int hdr_len;
+       unsigned int remaining = data_len;
+
+       while (remaining > 0) {
+               hdr_len = 12;
+
+               if (remaining < hdr_len) {
+                 RDEBUG2("Diameter attribute is too small (%u) to contain a Diameter header", remaining);
+                       return 0;
+               }
+
+               memcpy(&attr, data, sizeof(attr));
+               attr = ntohl(attr);
+               memcpy(&length, data + 4, sizeof(length));
+               length = ntohl(length);
+
+               if ((data[4] & 0x80) != 0) {
+                       if (remaining < 16) {
+                               RDEBUG2("Diameter attribute is too small to contain a Diameter header with Vendor-Id");
+                               return 0;
+                       }
+
+                       hdr_len = 16;
+               }
+
+               /*
+                *      Get the length.  If it's too big, die.
+                */
+               length &= 0x00ffffff;
+
+               /*
+                *      Too short or too long is bad.
+                */
+               if (length <= (hdr_len - 4)) {
+                       RDEBUG2("Tunneled attribute %u is too short (%u < %u) to contain anything useful.", attr,
+                               length, hdr_len);
+                       return 0;
+               }
+
+               if (length > remaining) {
+                       RDEBUG2("Tunneled attribute %u is longer than room remaining in the packet (%u > %u).", attr,
+                               length, remaining);
+                       return 0;
+               }
+
+               /*
+                *      Check for broken implementations, which don't
+                *      pad the AVP to a 4-octet boundary.
+                */
+               if (remaining == length) break;
+
+               /*
+                *      The length does NOT include the padding, so
+                *      we've got to account for it here by rounding up
+                *      to the nearest 4-byte boundary.
+                */
+               length += 0x03;
+               length &= ~0x03;
+
+               /*
+                *      If the rest of the diameter packet is larger than
+                *      this attribute, continue.
+                *
+                *      Otherwise, if the attribute over-flows the end
+                *      of the packet, die.
+                */
+               if (remaining < length) {
+                       REDEBUG2("Diameter attribute overflows packet!");
+                       return 0;
+               }
+
+               /*
+                *      remaining > length, continue.
+                */
+               remaining -= length;
+               data += length;
+       }
+
+       /*
+        *      We got this far.  It looks OK.
+        */
+       return 1;
+}
+
+/*
+ *     Convert diameter attributes to our fr_pair_t's
+ */
+static ssize_t eap_ttls_decode_pair(request_t *request, TALLOC_CTX *ctx, fr_pair_list_t *out,
+                                   fr_dict_attr_t const *parent,
+                                   uint8_t const *data, size_t data_len,
+                                   void *decode_ctx)
+{
+       uint8_t const           *p = data, *end = p + data_len;
+
+       fr_pair_t               *vp = NULL;
+       SSL                     *ssl = decode_ctx;
+       fr_dict_attr_t const    *attr_radius;
+       fr_dict_attr_t const    *da;
+       TALLOC_CTX              *tmp_ctx = NULL;
+
+       attr_radius = fr_dict_root(dict_radius);
+
+       while (p < end) {
+               ssize_t                 ret;
+               uint32_t                attr, vendor;
+               uint64_t                value_len;
+               uint8_t                 flags;
+               fr_dict_attr_t const    *our_parent = parent;
+
+               if ((end - p) < 8) {
+                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Needed at least 8 bytes, got %zu bytes",
+                                          p - data, end - p);
+               error:
+                       talloc_free(tmp_ctx);
+                       fr_pair_list_free(out);
+                       return -1;
+               }
+
+               RDEBUG3("%04zu %02x%02x%02x%02x %02x%02x%02x%02x ...", p - data,
+                       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+
+               attr = fr_nbo_to_uint32(p);
+               p += 4;
+
+               flags = p[0];
+               p++;
+
+               value_len = fr_nbo_to_uint64v(p, 3);    /* Yes, that is a 24 bit length field */
+               p += 3;
+
+               if (value_len < 8) {
+                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Needed at least length of 8, got %u",
+                                          p - data, (unsigned int) value_len);
+                       goto error;
+               }
+
+               /*
+                *      Account for the 8 bytes we've already read from the packet.
+                */
+               if ((p + ((value_len + 0x03) & ~0x03)) - 8 > end) {
+                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Value length %u overflows input",
+                                          p - data, (unsigned int) value_len);
+                       goto error;
+               }
+
+               value_len -= 8; /* -= 8 for AVP code (4), flags (1), AVP length (3) */
+
+               /*
+                *      Do we have a vendor field?
+                */
+               if (flags & FR_DIAMETER_AVP_FLAG_VENDOR) {
+                       vendor = fr_nbo_to_uint32(p);
+                       p += 4;
+                       value_len -= 4; /* -= 4 for the vendor ID field */
+
+                       our_parent = fr_dict_vendor_da_by_num(attr_vendor_specific, vendor);
+                       if (!our_parent) {
+                               if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+                                       fr_strerror_printf("Mandatory bit set and no vendor %u found", vendor);
+                                       goto error;
+                               }
+
+                               if (!tmp_ctx) {
+                                       fr_dict_attr_t *n;
+
+                                       MEM(our_parent = n = fr_dict_attr_unknown_vendor_afrom_num(ctx, parent, vendor));
+                                       tmp_ctx = n;
+                               } else {
+                                       MEM(our_parent = fr_dict_attr_unknown_vendor_afrom_num(tmp_ctx, parent, vendor));
+                               }
+                       }
+               } else {
+                       our_parent = attr_radius;
+               }
+
+               /*
+                *      Is the attribute known?
+                */
+               da = fr_dict_attr_child_by_num(our_parent, attr);
+               if (!da) {
+                       if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+                               fr_strerror_printf("Mandatory bit set and no attribute %u defined for parent %s", attr, parent->name);
+                               goto error;
+                       }
+
+                       MEM(da = fr_dict_attr_unknown_raw_afrom_num(vp, our_parent, attr));
+               }
+
+               MEM(vp =fr_pair_afrom_da_nested(ctx, out, da));
+
+               ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
+                                               &FR_DBUFF_TMP(p, (size_t)value_len), value_len, true);
+               if (ret < 0) {
+                       /*
+                        *      Mandatory bit is set, and the attribute
+                        *      is malformed. Fail.
+                        */
+                       if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
+                               fr_strerror_const("Mandatory bit is set and attribute is malformed");
+                               goto error;
+                       }
+
+                       fr_pair_raw_afrom_pair(vp, p, value_len);
+               }
+
+               /*
+                *      The length does NOT include the padding, so
+                *      we've got to account for it here by rounding up
+                *      to the nearest 4-byte boundary.
+                */
+               p += (value_len + 0x03) & ~0x03;
+
+               if (vp->da->flags.is_unknown) continue;
+
+               /*
+                *      Ensure that the client is using the correct challenge.
+                *
+                *      This weirdness is to protect against against replay
+                *      attacks, where anyone observing the CHAP exchange could
+                *      pose as that user, by simply choosing to use the same
+                *      challenge.
+                *      By using a challenge based on information from the
+                *      current session, we can guarantee that the client is
+                *      not *choosing* a challenge. We're a little forgiving in
+                *      that we have loose checks on the length, and we do NOT
+                *      check the Id (first octet of the response to the
+                *      challenge) But if the client gets the challenge correct,
+                *      we're not too worried about the Id.
+                */
+               if ((vp->da == attr_chap_challenge) || (vp->da == attr_ms_chap_challenge)) {
+                       uint8_t challenge[17];
+                       static const char label[] = "ttls challenge";
+
+                       if ((vp->vp_length < 8) || (vp->vp_length > 16)) {
+                               fr_strerror_const("Tunneled challenge has invalid length");
+                               goto error;
+                       }
+
+                       /*
+                        *      TLSv1.3 exports a different key depending on the length
+                        *      requested so ask for *exactly* what the spec requires
+                        */
+                       if (SSL_export_keying_material(ssl, challenge, vp->vp_length + 1,
+                                                      label, sizeof(label) - 1, NULL, 0, 0) != 1) {
+                               fr_tls_strerror_printf("Failed generating phase2 challenge");
+                               goto error;
+                       }
+
+                       if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) {
+                               fr_strerror_const("Tunneled challenge is incorrect");
+                               goto error;
+                       }
+               }
+
+               /*
+                *      Diameter pads strings (i.e. User-Password) with trailing zeros.
+                */
+               if (vp->vp_type == FR_TYPE_STRING) fr_pair_value_strtrim(vp);
+       }
+
+       /*
+        *      We got this far.  It looks OK.
+        */
+       talloc_free(tmp_ctx);
+       return p - data;
+}
+
+/*
+ *     Convert fr_pair_t's to diameter attributes, and write them
+ *     to an SSL session.
+ *
+ *     The ONLY fr_pair_t's which may be passed to this function
+ *     are ones which can go inside of a RADIUS (i.e. diameter)
+ *     packet.  So no server-configuration attributes, or the like.
+ */
+static int vp2diameter(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *list)
+{
+       /*
+        *      RADIUS packets are no more than 4k in size, so if
+        *      we've got more than 4k of data to write, it's very
+        *      bad.
+        */
+       uint8_t         buffer[4096];
+       uint8_t         *p;
+       uint32_t        attr;
+       uint32_t        length;
+       uint32_t        vendor;
+       size_t          total;
+       uint64_t        attr64;
+       fr_pair_t       *vp;
+
+       p = buffer;
+       total = 0;
+
+       for (vp = fr_pair_list_head(list);
+            vp;
+            vp = fr_pair_list_next(list, vp)) {
+               /*
+                *      Too much data: die.
+                */
+               if ((total + vp->vp_length + 12) >= sizeof(buffer)) {
+                       RDEBUG2("output buffer is full!");
+                       return 0;
+               }
+
+               /*
+                *      Hmm... we don't group multiple EAP-Messages
+                *      together.  Maybe we should...
+                */
+
+               length = vp->vp_length;
+               vendor = fr_dict_vendor_num_by_da(vp->da);
+               if (vendor != 0) {
+                       attr = vp->da->attr & 0xffff;
+                       length |= ((uint32_t)1 << 31);
+               } else {
+                       attr = vp->da->attr;
+               }
+
+               /*
+                *      Hmm... set the M bit for all attributes?
+                */
+               length |= (1 << 30);
+
+               attr = ntohl(attr);
+
+               memcpy(p, &attr, sizeof(attr));
+               p += 4;
+               total += 4;
+
+               length += 8;    /* includes 8 bytes of attr & length */
+
+               if (vendor != 0) {
+                       length += 4; /* include 4 bytes of vendor */
+
+                       length = ntohl(length);
+                       memcpy(p, &length, sizeof(length));
+                       p += 4;
+                       total += 4;
+
+                       vendor = ntohl(vendor);
+                       memcpy(p, &vendor, sizeof(vendor));
+                       p += 4;
+                       total += 4;
+               } else {
+                       length = ntohl(length);
+                       memcpy(p, &length, sizeof(length));
+                       p += 4;
+                       total += 4;
+               }
+
+               switch (vp->vp_type) {
+               case FR_TYPE_DATE:
+                       attr = htonl(fr_unix_time_to_sec(vp->vp_date)); /* stored in host order */
+                       memcpy(p, &attr, sizeof(attr));
+                       length = 4;
+                       break;
+
+               case FR_TYPE_UINT32:
+                       attr = htonl(vp->vp_uint32); /* stored in host order */
+                       memcpy(p, &attr, sizeof(attr));
+                       length = 4;
+                       break;
+
+               case FR_TYPE_UINT64:
+                       attr64 = htonll(vp->vp_uint64); /* stored in host order */
+                       memcpy(p, &attr64, sizeof(attr64));
+                       length = 8;
+                       break;
+
+               case FR_TYPE_IPV4_ADDR:
+                       memcpy(p, &vp->vp_ipv4addr, 4); /* network order */
+                       length = 4;
+                       break;
+
+               case FR_TYPE_STRING:
+               case FR_TYPE_OCTETS:
+               default:
+                       memcpy(p, vp->vp_strvalue, vp->vp_length);
+                       length = vp->vp_length;
+                       break;
+               }
+
+               /*
+                *      Skip to the end of the data.
+                */
+               p += length;
+               total += length;
+
+               /*
+                *      Align the data to a multiple of 4 bytes.
+                */
+               if ((total & 0x03) != 0) {
+                       size_t i;
+
+                       length = 4 - (total & 0x03);
+                       for (i = 0; i < length; i++) {
+                               *p = '\0';
+                               p++;
+                               total++;
+                       }
+               }
+       } /* loop over the VP's to write. */
+
+       /*
+        *      Write the data in the buffer to the SSL session.
+        */
+       if (total > 0) {
+               (tls_session->record_from_buff)(&tls_session->clean_in, buffer, total);
+
+               /*
+                *      FIXME: Check the return code.
+                */
+               fr_tls_session_send(request, tls_session);
+       }
+
+       /*
+        *      Everything's OK.
+        */
+       return 1;
+}
+
+
+static unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session)
+{
+       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
+       eap_tls_prf_label_t prf_label;
+
+       eap_crypto_prf_label_init(&prf_label, eap_session,
+                                 "ttls keying material",
+                                 sizeof("ttls keying material") - 1);
+       /*
+        *      Success: Automatically return MPPE keys.
+        */
+       if (eap_tls_success(request, eap_session, &prf_label) < 0) RETURN_UNLANG_FAIL;
+
+       /*
+        *      Result is always OK, even if we fail to persist the
+        *      session data.
+        */
+       p_result->rcode = RLM_MODULE_OK;
+
+       /*
+        *      Write the session to the session cache
+        *
+        *      We do this here (instead of relying on OpenSSL to call the
+        *      session caching callback), because we only want to write
+        *      session data to the cache if all phases were successful.
+        *
+        *      If we wrote out the cache data earlier, and the server
+        *      exited whilst the session was in progress, the supplicant
+        *      could resume the session (and get access) even if phase2
+        *      never completed.
+        */
+       return fr_tls_cache_pending_push(request, tls_session);
+}
+
+
+/*
+ *     Use a reply packet to determine what to do.
+ */
+static unlang_action_t process_reply(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+       eap_session_t           *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
+       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
+       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
+       fr_pair_t               *vp = NULL;
+       fr_pair_list_t          tunnel_vps;
+       ttls_tunnel_t           *t = tls_session->opaque;
+       fr_packet_t             *reply = request->reply;
+
+       fr_pair_list_init(&tunnel_vps);
+       fr_assert(eap_session->request == request->parent);
+
+       /*
+        *      If the response packet was Access-Accept, then
+        *      we're OK.  If not, die horribly.
+        *
+        *      FIXME: Take MS-CHAP2-Success attribute, and
+        *      tunnel it back to the client, to authenticate
+        *      ourselves to the client.
+        *
+        *      FIXME: If we have an Access-Challenge, then
+        *      the Reply-Message is tunneled back to the client.
+        *
+        *      FIXME: If we have an EAP-Message, then that message
+        *      must be tunneled back to the client.
+        *
+        *      FIXME: If we have an Access-Challenge with a State
+        *      attribute, then do we tunnel that to the client, or
+        *      keep track of it ourselves?
+        *
+        *      FIXME: EAP-Messages can only start with 'identity',
+        *      NOT 'eap start', so we should check for that....
+        */
+       switch (reply->code) {
+       case FR_RADIUS_CODE_ACCESS_ACCEPT:
+               RDEBUG2("Got tunneled Access-Accept");
+
+               /*
+                *      Copy what we need into the TTLS tunnel and leave
+                *      the rest to be cleaned up.
+                */
+               if ((vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_ms_chap2_success))) {
+                       RDEBUG2("Got MS-CHAP2-Success, tunneling it to the client in a challenge");
+               } else {
+                       vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_eap_channel_binding_message);
+               }
+               if (vp) {
+                       t->authenticated = true;
+                       fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+                       reply->code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
+                       break;
+               }
+
+               /*
+                *      Success: Automatically return MPPE keys.
+                */
+               return eap_ttls_success(p_result, request, eap_session);
+
+       case FR_RADIUS_CODE_ACCESS_REJECT:
+               REDEBUG("Got tunneled Access-Reject");
+               eap_tls_fail(request, eap_session);
+               RETURN_UNLANG_REJECT;
+
+       /*
+        *      Handle Access-Challenge, but only if we
+        *      send tunneled reply data.  This is because
+        *      an Access-Challenge means that we MUST tunnel
+        *      a Reply-Message to the client.
+        */
+       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
+               RDEBUG2("Got tunneled Access-Challenge");
+
+               /*
+                *      Copy what we need into the TTLS tunnel and leave
+                *      the rest to be cleaned up.
+                */
+               vp = NULL;
+               while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
+                       if ((vp->da == attr_eap_message) || (vp->da == attr_reply_message)) {
+                               fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+                       } else if (vp->da == attr_eap_channel_binding_message) {
+                               fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
+                       }
+               }
+               break;
+
+       default:
+               REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
+               eap_tls_fail(request, eap_session);
+               RETURN_UNLANG_INVALID;
+       }
+
+
+       /*
+        *      Pack any tunneled VPs and send them back
+        *      to the supplicant.
+        */
+       if (!fr_pair_list_empty(&tunnel_vps)) {
+               RDEBUG2("Sending tunneled reply attributes");
+               log_request_pair_list(L_DBG_LVL_2, request, NULL, &tunnel_vps, NULL);
+
+               vp2diameter(request, tls_session, &tunnel_vps);
+               fr_pair_list_free(&tunnel_vps);
+       }
+
+       eap_tls_request(request, eap_session);
+       RETURN_UNLANG_OK;
+}
+
+/*
+ *     Process the "diameter" contents of the tunneled data.
+ */
+static unlang_action_t eap_ttls_process(unlang_result_t *p_result, module_ctx_t const *mctx,
+                                       request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
+{
+       fr_pair_t               *vp = NULL;
+       ttls_tunnel_t           *t;
+       uint8_t                 const *data;
+       size_t                  data_len;
+       chbind_packet_t         *chbind;
+       fr_pair_t               *username;
+       rlm_eap_ttls_t          *inst = talloc_get_type_abort(mctx->mi->data, rlm_eap_ttls_t);
+
+       /*
+        *      Just look at the buffer directly, without doing
+        *      record_to_buff.
+        */
+       data_len = tls_session->clean_out.used;
+       tls_session->clean_out.used = 0;
+       data = tls_session->clean_out.data;
+
+       t = (ttls_tunnel_t *) tls_session->opaque;
+
+       /*
+        *      If there's no data, maybe this is an ACK to an
+        *      MS-CHAP2-Success.
+        */
+       if (data_len == 0) {
+               if (t->authenticated) {
+                       RDEBUG2("Got ACK, and the user was already authenticated");
+                       return eap_ttls_success(p_result, request, eap_session);
+               } /* else no session, no data, die. */
+
+               /*
+                *      FIXME: Call SSL_get_error() to see what went
+                *      wrong.
+                */
+               RDEBUG2("SSL_read Error");
+               return UNLANG_ACTION_FAIL;
+       }
+
+       if (!diameter_verify(request, data, data_len)) return UNLANG_ACTION_FAIL;
+
+       /*
+        *      Add the tunneled attributes to the request request.
+        */
+       if (eap_ttls_decode_pair(request, request->request_ctx, &request->request_pairs, fr_dict_root(fr_dict_internal()),
+                                data, data_len, tls_session->ssl) < 0) {
+               RPEDEBUG("Decoding TTLS TLVs failed");
+               return UNLANG_ACTION_FAIL;
+       }
+
+       /*
+        *      Update other items in the request_t data structure.
+        */
+
+       /*
+        *      No User-Name, try to create one from stored data.
+        */
+       username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
+       if (!username) {
+               /*
+                *      No User-Name in the stored data, look for
+                *      an EAP-Identity, and pull it out of there.
+                */
+               if (!t->username) {
+                       vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_eap_message);
+                       if (vp &&
+                           (vp->vp_length >= EAP_HEADER_LEN + 2) &&
+                           (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
+                           (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
+                           (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
+                               /*
+                                *      Create & remember a User-Name
+                                */
+                               MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
+                               t->username->vp_tainted = true;
+
+                               fr_pair_value_bstrndup(t->username,
+                                                      (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
+
+                               RDEBUG2("Got tunneled identity of %pV", &t->username->data);
+                       } else {
+                               /*
+                                *      Don't reject the request outright,
+                                *      as it's permitted to do EAP without
+                                *      user-name.
+                                */
+                               RWDEBUG2("No EAP-Identity found to start EAP conversation");
+                       }
+               } /* else there WAS a t->username */
+
+               if (t->username) {
+                       vp = fr_pair_copy(request->request_ctx, t->username);
+                       fr_pair_append(&request->request_pairs, vp);
+               }
+       } /* else the request ALREADY had a User-Name */
+
+       /*
+        *      Process channel binding.
+        */
+       chbind = eap_chbind_vp2packet(request, &request->request_pairs);
+       if (chbind) {
+               fr_radius_packet_code_t chbind_code;
+               CHBIND_REQ *req = talloc_zero(request, CHBIND_REQ);
+
+               RDEBUG2("received chbind request");
+               req->request = chbind;
+               if (username) {
+                       req->username = username;
+               } else {
+                       req->username = NULL;
+               }
+               chbind_code = chbind_process(request, req);
+
+               /* encapsulate response here */
+               if (req->response) {
+                       RDEBUG2("sending chbind response");
+                       fr_pair_append(&request->reply_pairs,
+                                   eap_chbind_packet2vp(request->reply_ctx, req->response));
+               } else {
+                       RDEBUG2("no chbind response");
+               }
+
+               /* clean up chbind req */
+               talloc_free(req);
+
+               if (chbind_code != FR_RADIUS_CODE_ACCESS_ACCEPT) return UNLANG_ACTION_FAIL;
+       }
+
+       /*
+        *      For this round, when the virtual server returns
+        *      we run the process reply function.
+        */
+       if (unlikely(unlang_module_yield(request, process_reply, NULL, 0, eap_session) != UNLANG_ACTION_YIELD)) {
+               return UNLANG_ACTION_FAIL;
+       }
+
+       /*
+        *      Call authentication recursively, which will
+        *      do PAP, CHAP, MS-CHAP, etc.
+        */
+       return eap_virtual_server(request, eap_session, inst->virtual_server);
+}
+
 /*
  *     Allocate the TTLS per-session data
  */
-static ttls_tunnel_t *ttls_alloc(TALLOC_CTX *ctx, rlm_eap_ttls_t *inst)
+static ttls_tunnel_t *ttls_alloc(TALLOC_CTX *ctx)
 {
        ttls_tunnel_t *t;
 
        t = talloc_zero(ctx, ttls_tunnel_t);
-       t->server_cs = inst->server_cs;
 
        return t;
 }
@@ -191,7 +937,7 @@ static unlang_action_t mod_handshake_resume(unlang_result_t *p_result, module_ct
        /*
         *      Process the TTLS portion of the request.
         */
-       return eap_ttls_process(p_result, request, eap_session, tls_session);
+       return eap_ttls_process(p_result, mctx, request, eap_session, tls_session);
 }
 
 /*
@@ -250,7 +996,7 @@ static unlang_action_t mod_session_init_resume(unlang_result_t *p_result, module
                RETURN_UNLANG_FAIL;
        }
 
-       tls_session->opaque = ttls_alloc(tls_session, inst);
+       tls_session->opaque = ttls_alloc(tls_session);
 
        eap_session->process = mod_handshake_process;
 
diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c
deleted file mode 100644 (file)
index fd4963e..0000000
+++ /dev/null
@@ -1,772 +0,0 @@
-/*
- *   This program is 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
- */
-
-/**
- * $Id$
- * @file ttls.c
- * @brief Library functions for EAP-TTLS as defined by RFC 5281
- *
- * @copyright 2003 Alan DeKok (aland@freeradius.org)
- * @copyright 2006 The FreeRADIUS server project
- */
-
-RCSID("$Id$")
-
-#include <freeradius-devel/eap/chbind.h>
-#include <freeradius-devel/tls/log.h>
-#include <freeradius-devel/tls/strerror.h>
-#include "eap_ttls.h"
-
-#define FR_DIAMETER_AVP_FLAG_VENDOR    0x80
-#define FR_DIAMETER_AVP_FLAG_MANDATORY 0x40
-/*
- *    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
- *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *   |                           AVP Code                            |
- *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *   |V M r r r r r r|                  AVP Length                   |
- *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *   |                        Vendor-ID (opt)                        |
- *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *   |    Data ...
- *   +-+-+-+-+-+-+-+-+
- */
-
-/*
- *     Verify that the diameter packet is valid.
- */
-static int diameter_verify(request_t *request, uint8_t const *data, unsigned int data_len)
-{
-       uint32_t attr;
-       uint32_t length;
-       unsigned int hdr_len;
-       unsigned int remaining = data_len;
-
-       while (remaining > 0) {
-               hdr_len = 12;
-
-               if (remaining < hdr_len) {
-                 RDEBUG2("Diameter attribute is too small (%u) to contain a Diameter header", remaining);
-                       return 0;
-               }
-
-               memcpy(&attr, data, sizeof(attr));
-               attr = ntohl(attr);
-               memcpy(&length, data + 4, sizeof(length));
-               length = ntohl(length);
-
-               if ((data[4] & 0x80) != 0) {
-                       if (remaining < 16) {
-                               RDEBUG2("Diameter attribute is too small to contain a Diameter header with Vendor-Id");
-                               return 0;
-                       }
-
-                       hdr_len = 16;
-               }
-
-               /*
-                *      Get the length.  If it's too big, die.
-                */
-               length &= 0x00ffffff;
-
-               /*
-                *      Too short or too long is bad.
-                */
-               if (length <= (hdr_len - 4)) {
-                       RDEBUG2("Tunneled attribute %u is too short (%u < %u) to contain anything useful.", attr,
-                               length, hdr_len);
-                       return 0;
-               }
-
-               if (length > remaining) {
-                       RDEBUG2("Tunneled attribute %u is longer than room remaining in the packet (%u > %u).", attr,
-                               length, remaining);
-                       return 0;
-               }
-
-               /*
-                *      Check for broken implementations, which don't
-                *      pad the AVP to a 4-octet boundary.
-                */
-               if (remaining == length) break;
-
-               /*
-                *      The length does NOT include the padding, so
-                *      we've got to account for it here by rounding up
-                *      to the nearest 4-byte boundary.
-                */
-               length += 0x03;
-               length &= ~0x03;
-
-               /*
-                *      If the rest of the diameter packet is larger than
-                *      this attribute, continue.
-                *
-                *      Otherwise, if the attribute over-flows the end
-                *      of the packet, die.
-                */
-               if (remaining < length) {
-                       REDEBUG2("Diameter attribute overflows packet!");
-                       return 0;
-               }
-
-               /*
-                *      remaining > length, continue.
-                */
-               remaining -= length;
-               data += length;
-       }
-
-       /*
-        *      We got this far.  It looks OK.
-        */
-       return 1;
-}
-
-
-/*
- *     Convert diameter attributes to our fr_pair_t's
- */
-static ssize_t eap_ttls_decode_pair(request_t *request, TALLOC_CTX *ctx, fr_pair_list_t *out,
-                                   fr_dict_attr_t const *parent,
-                                   uint8_t const *data, size_t data_len,
-                                   void *decode_ctx)
-{
-       uint8_t const           *p = data, *end = p + data_len;
-
-       fr_pair_t               *vp = NULL;
-       SSL                     *ssl = decode_ctx;
-       fr_dict_t const         *dict_radius;
-       fr_dict_attr_t const    *attr_radius;
-       fr_dict_attr_t const    *da;
-       TALLOC_CTX              *tmp_ctx = NULL;
-
-       dict_radius = fr_dict_by_protocol_name("radius");
-       fr_assert(dict_radius != NULL);
-       attr_radius = fr_dict_root(dict_radius);
-
-       while (p < end) {
-               ssize_t                 ret;
-               uint32_t                attr, vendor;
-               uint64_t                value_len;
-               uint8_t                 flags;
-               fr_dict_attr_t const    *our_parent = parent;
-
-               if ((end - p) < 8) {
-                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Needed at least 8 bytes, got %zu bytes",
-                                          p - data, end - p);
-               error:
-                       talloc_free(tmp_ctx);
-                       fr_pair_list_free(out);
-                       return -1;
-               }
-
-               RDEBUG3("%04zu %02x%02x%02x%02x %02x%02x%02x%02x ...", p - data,
-                       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
-
-               attr = fr_nbo_to_uint32(p);
-               p += 4;
-
-               flags = p[0];
-               p++;
-
-               value_len = fr_nbo_to_uint64v(p, 3);    /* Yes, that is a 24 bit length field */
-               p += 3;
-
-               if (value_len < 8) {
-                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Needed at least length of 8, got %u",
-                                          p - data, (unsigned int) value_len);
-                       goto error;
-               }
-
-               /*
-                *      Account for the 8 bytes we've already read from the packet.
-                */
-               if ((p + ((value_len + 0x03) & ~0x03)) - 8 > end) {
-                       fr_strerror_printf("Malformed diameter attribute at offset %zu.  Value length %u overflows input",
-                                          p - data, (unsigned int) value_len);
-                       goto error;
-               }
-
-               value_len -= 8; /* -= 8 for AVP code (4), flags (1), AVP length (3) */
-
-               /*
-                *      Do we have a vendor field?
-                */
-               if (flags & FR_DIAMETER_AVP_FLAG_VENDOR) {
-                       vendor = fr_nbo_to_uint32(p);
-                       p += 4;
-                       value_len -= 4; /* -= 4 for the vendor ID field */
-
-                       our_parent = fr_dict_vendor_da_by_num(attr_vendor_specific, vendor);
-                       if (!our_parent) {
-                               if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
-                                       fr_strerror_printf("Mandatory bit set and no vendor %u found", vendor);
-                                       goto error;
-                               }
-
-                               if (!tmp_ctx) {
-                                       fr_dict_attr_t *n;
-
-                                       MEM(our_parent = n = fr_dict_attr_unknown_vendor_afrom_num(ctx, parent, vendor));
-                                       tmp_ctx = n;
-                               } else {
-                                       MEM(our_parent = fr_dict_attr_unknown_vendor_afrom_num(tmp_ctx, parent, vendor));
-                               }
-                       }
-               } else {
-                       our_parent = attr_radius;
-               }
-
-               /*
-                *      Is the attribute known?
-                */
-               da = fr_dict_attr_child_by_num(our_parent, attr);
-               if (!da) {
-                       if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
-                               fr_strerror_printf("Mandatory bit set and no attribute %u defined for parent %s", attr, parent->name);
-                               goto error;
-                       }
-
-                       MEM(da = fr_dict_attr_unknown_raw_afrom_num(vp, our_parent, attr));
-               }
-
-               MEM(vp =fr_pair_afrom_da_nested(ctx, out, da));
-
-               ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
-                                               &FR_DBUFF_TMP(p, (size_t)value_len), value_len, true);
-               if (ret < 0) {
-                       /*
-                        *      Mandatory bit is set, and the attribute
-                        *      is malformed. Fail.
-                        */
-                       if (flags & FR_DIAMETER_AVP_FLAG_MANDATORY) {
-                               fr_strerror_const("Mandatory bit is set and attribute is malformed");
-                               goto error;
-                       }
-
-                       fr_pair_raw_afrom_pair(vp, p, value_len);
-               }
-
-               /*
-                *      The length does NOT include the padding, so
-                *      we've got to account for it here by rounding up
-                *      to the nearest 4-byte boundary.
-                */
-               p += (value_len + 0x03) & ~0x03;
-
-               if (vp->da->flags.is_unknown) continue;
-
-               /*
-                *      Ensure that the client is using the correct challenge.
-                *
-                *      This weirdness is to protect against against replay
-                *      attacks, where anyone observing the CHAP exchange could
-                *      pose as that user, by simply choosing to use the same
-                *      challenge.
-                *      By using a challenge based on information from the
-                *      current session, we can guarantee that the client is
-                *      not *choosing* a challenge. We're a little forgiving in
-                *      that we have loose checks on the length, and we do NOT
-                *      check the Id (first octet of the response to the
-                *      challenge) But if the client gets the challenge correct,
-                *      we're not too worried about the Id.
-                */
-               if ((vp->da == attr_chap_challenge) || (vp->da == attr_ms_chap_challenge)) {
-                       uint8_t challenge[17];
-                       static const char label[] = "ttls challenge";
-
-                       if ((vp->vp_length < 8) || (vp->vp_length > 16)) {
-                               fr_strerror_const("Tunneled challenge has invalid length");
-                               goto error;
-                       }
-
-                       /*
-                        *      TLSv1.3 exports a different key depending on the length
-                        *      requested so ask for *exactly* what the spec requires
-                        */
-                       if (SSL_export_keying_material(ssl, challenge, vp->vp_length + 1,
-                                                      label, sizeof(label) - 1, NULL, 0, 0) != 1) {
-                               fr_tls_strerror_printf("Failed generating phase2 challenge");
-                               goto error;
-                       }
-
-                       if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) {
-                               fr_strerror_const("Tunneled challenge is incorrect");
-                               goto error;
-                       }
-               }
-
-               /*
-                *      Diameter pads strings (i.e. User-Password) with trailing zeros.
-                */
-               if (vp->vp_type == FR_TYPE_STRING) fr_pair_value_strtrim(vp);
-       }
-
-       /*
-        *      We got this far.  It looks OK.
-        */
-       talloc_free(tmp_ctx);
-       return p - data;
-}
-
-/*
- *     Convert fr_pair_t's to diameter attributes, and write them
- *     to an SSL session.
- *
- *     The ONLY fr_pair_t's which may be passed to this function
- *     are ones which can go inside of a RADIUS (i.e. diameter)
- *     packet.  So no server-configuration attributes, or the like.
- */
-static int vp2diameter(request_t *request, fr_tls_session_t *tls_session, fr_pair_list_t *list)
-{
-       /*
-        *      RADIUS packets are no more than 4k in size, so if
-        *      we've got more than 4k of data to write, it's very
-        *      bad.
-        */
-       uint8_t         buffer[4096];
-       uint8_t         *p;
-       uint32_t        attr;
-       uint32_t        length;
-       uint32_t        vendor;
-       size_t          total;
-       uint64_t        attr64;
-       fr_pair_t       *vp;
-
-       p = buffer;
-       total = 0;
-
-       for (vp = fr_pair_list_head(list);
-            vp;
-            vp = fr_pair_list_next(list, vp)) {
-               /*
-                *      Too much data: die.
-                */
-               if ((total + vp->vp_length + 12) >= sizeof(buffer)) {
-                       RDEBUG2("output buffer is full!");
-                       return 0;
-               }
-
-               /*
-                *      Hmm... we don't group multiple EAP-Messages
-                *      together.  Maybe we should...
-                */
-
-               length = vp->vp_length;
-               vendor = fr_dict_vendor_num_by_da(vp->da);
-               if (vendor != 0) {
-                       attr = vp->da->attr & 0xffff;
-                       length |= ((uint32_t)1 << 31);
-               } else {
-                       attr = vp->da->attr;
-               }
-
-               /*
-                *      Hmm... set the M bit for all attributes?
-                */
-               length |= (1 << 30);
-
-               attr = ntohl(attr);
-
-               memcpy(p, &attr, sizeof(attr));
-               p += 4;
-               total += 4;
-
-               length += 8;    /* includes 8 bytes of attr & length */
-
-               if (vendor != 0) {
-                       length += 4; /* include 4 bytes of vendor */
-
-                       length = ntohl(length);
-                       memcpy(p, &length, sizeof(length));
-                       p += 4;
-                       total += 4;
-
-                       vendor = ntohl(vendor);
-                       memcpy(p, &vendor, sizeof(vendor));
-                       p += 4;
-                       total += 4;
-               } else {
-                       length = ntohl(length);
-                       memcpy(p, &length, sizeof(length));
-                       p += 4;
-                       total += 4;
-               }
-
-               switch (vp->vp_type) {
-               case FR_TYPE_DATE:
-                       attr = htonl(fr_unix_time_to_sec(vp->vp_date)); /* stored in host order */
-                       memcpy(p, &attr, sizeof(attr));
-                       length = 4;
-                       break;
-
-               case FR_TYPE_UINT32:
-                       attr = htonl(vp->vp_uint32); /* stored in host order */
-                       memcpy(p, &attr, sizeof(attr));
-                       length = 4;
-                       break;
-
-               case FR_TYPE_UINT64:
-                       attr64 = htonll(vp->vp_uint64); /* stored in host order */
-                       memcpy(p, &attr64, sizeof(attr64));
-                       length = 8;
-                       break;
-
-               case FR_TYPE_IPV4_ADDR:
-                       memcpy(p, &vp->vp_ipv4addr, 4); /* network order */
-                       length = 4;
-                       break;
-
-               case FR_TYPE_STRING:
-               case FR_TYPE_OCTETS:
-               default:
-                       memcpy(p, vp->vp_strvalue, vp->vp_length);
-                       length = vp->vp_length;
-                       break;
-               }
-
-               /*
-                *      Skip to the end of the data.
-                */
-               p += length;
-               total += length;
-
-               /*
-                *      Align the data to a multiple of 4 bytes.
-                */
-               if ((total & 0x03) != 0) {
-                       size_t i;
-
-                       length = 4 - (total & 0x03);
-                       for (i = 0; i < length; i++) {
-                               *p = '\0';
-                               p++;
-                               total++;
-                       }
-               }
-       } /* loop over the VP's to write. */
-
-       /*
-        *      Write the data in the buffer to the SSL session.
-        */
-       if (total > 0) {
-               (tls_session->record_from_buff)(&tls_session->clean_in, buffer, total);
-
-               /*
-                *      FIXME: Check the return code.
-                */
-               fr_tls_session_send(request, tls_session);
-       }
-
-       /*
-        *      Everything's OK.
-        */
-       return 1;
-}
-
-/*
- *     Use a reply packet to determine what to do.
- */
-static unlang_action_t process_reply(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
-       eap_session_t           *eap_session = talloc_get_type_abort(mctx->rctx, eap_session_t);
-       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
-       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
-       fr_pair_t               *vp = NULL;
-       fr_pair_list_t          tunnel_vps;
-       ttls_tunnel_t           *t = tls_session->opaque;
-       fr_packet_t             *reply = request->reply;
-
-       fr_pair_list_init(&tunnel_vps);
-       fr_assert(eap_session->request == request->parent);
-
-       /*
-        *      If the response packet was Access-Accept, then
-        *      we're OK.  If not, die horribly.
-        *
-        *      FIXME: Take MS-CHAP2-Success attribute, and
-        *      tunnel it back to the client, to authenticate
-        *      ourselves to the client.
-        *
-        *      FIXME: If we have an Access-Challenge, then
-        *      the Reply-Message is tunneled back to the client.
-        *
-        *      FIXME: If we have an EAP-Message, then that message
-        *      must be tunneled back to the client.
-        *
-        *      FIXME: If we have an Access-Challenge with a State
-        *      attribute, then do we tunnel that to the client, or
-        *      keep track of it ourselves?
-        *
-        *      FIXME: EAP-Messages can only start with 'identity',
-        *      NOT 'eap start', so we should check for that....
-        */
-       switch (reply->code) {
-       case FR_RADIUS_CODE_ACCESS_ACCEPT:
-               RDEBUG2("Got tunneled Access-Accept");
-
-               /*
-                *      Copy what we need into the TTLS tunnel and leave
-                *      the rest to be cleaned up.
-                */
-               if ((vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_ms_chap2_success))) {
-                       RDEBUG2("Got MS-CHAP2-Success, tunneling it to the client in a challenge");
-               } else {
-                       vp = fr_pair_find_by_da_nested(&request->reply_pairs, NULL, attr_eap_channel_binding_message);
-               }
-               if (vp) {
-                       t->authenticated = true;
-                       fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
-                       reply->code = FR_RADIUS_CODE_ACCESS_CHALLENGE;
-                       break;
-               }
-
-               /*
-                *      Success: Automatically return MPPE keys.
-                */
-               return eap_ttls_success(p_result, request, eap_session);
-
-       case FR_RADIUS_CODE_ACCESS_REJECT:
-               REDEBUG("Got tunneled Access-Reject");
-               eap_tls_fail(request, eap_session);
-               RETURN_UNLANG_REJECT;
-
-       /*
-        *      Handle Access-Challenge, but only if we
-        *      send tunneled reply data.  This is because
-        *      an Access-Challenge means that we MUST tunnel
-        *      a Reply-Message to the client.
-        */
-       case FR_RADIUS_CODE_ACCESS_CHALLENGE:
-               RDEBUG2("Got tunneled Access-Challenge");
-
-               /*
-                *      Copy what we need into the TTLS tunnel and leave
-                *      the rest to be cleaned up.
-                */
-               vp = NULL;
-               while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
-                       if ((vp->da == attr_eap_message) || (vp->da == attr_reply_message)) {
-                               fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
-                       } else if (vp->da == attr_eap_channel_binding_message) {
-                               fr_pair_prepend(&tunnel_vps, fr_pair_copy(tls_session, vp));
-                       }
-               }
-               break;
-
-       default:
-               REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
-               eap_tls_fail(request, eap_session);
-               RETURN_UNLANG_INVALID;
-       }
-
-
-       /*
-        *      Pack any tunneled VPs and send them back
-        *      to the supplicant.
-        */
-       if (!fr_pair_list_empty(&tunnel_vps)) {
-               RDEBUG2("Sending tunneled reply attributes");
-               log_request_pair_list(L_DBG_LVL_2, request, NULL, &tunnel_vps, NULL);
-
-               vp2diameter(request, tls_session, &tunnel_vps);
-               fr_pair_list_free(&tunnel_vps);
-       }
-
-       eap_tls_request(request, eap_session);
-       RETURN_UNLANG_OK;
-}
-
-unlang_action_t eap_ttls_success(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session)
-{
-       eap_tls_session_t       *eap_tls_session = talloc_get_type_abort(eap_session->opaque, eap_tls_session_t);
-       fr_tls_session_t        *tls_session = eap_tls_session->tls_session;
-       eap_tls_prf_label_t prf_label;
-
-       eap_crypto_prf_label_init(&prf_label, eap_session,
-                                 "ttls keying material",
-                                 sizeof("ttls keying material") - 1);
-       /*
-        *      Success: Automatically return MPPE keys.
-        */
-       if (eap_tls_success(request, eap_session, &prf_label) < 0) RETURN_UNLANG_FAIL;
-
-       /*
-        *      Result is always OK, even if we fail to persist the
-        *      session data.
-        */
-       p_result->rcode = RLM_MODULE_OK;
-
-       /*
-        *      Write the session to the session cache
-        *
-        *      We do this here (instead of relying on OpenSSL to call the
-        *      session caching callback), because we only want to write
-        *      session data to the cache if all phases were successful.
-        *
-        *      If we wrote out the cache data earlier, and the server
-        *      exited whilst the session was in progress, the supplicant
-        *      could resume the session (and get access) even if phase2
-        *      never completed.
-        */
-       return fr_tls_cache_pending_push(request, tls_session);
-}
-
-/*
- *     Process the "diameter" contents of the tunneled data.
- */
-unlang_action_t eap_ttls_process(unlang_result_t *p_result, request_t *request, eap_session_t *eap_session, fr_tls_session_t *tls_session)
-{
-       fr_pair_t               *vp = NULL;
-       ttls_tunnel_t           *t;
-       uint8_t                 const *data;
-       size_t                  data_len;
-       chbind_packet_t         *chbind;
-       fr_pair_t               *username;
-
-       /*
-        *      Just look at the buffer directly, without doing
-        *      record_to_buff.
-        */
-       data_len = tls_session->clean_out.used;
-       tls_session->clean_out.used = 0;
-       data = tls_session->clean_out.data;
-
-       t = (ttls_tunnel_t *) tls_session->opaque;
-
-       /*
-        *      If there's no data, maybe this is an ACK to an
-        *      MS-CHAP2-Success.
-        */
-       if (data_len == 0) {
-               if (t->authenticated) {
-                       RDEBUG2("Got ACK, and the user was already authenticated");
-                       return eap_ttls_success(p_result, request, eap_session);
-               } /* else no session, no data, die. */
-
-               /*
-                *      FIXME: Call SSL_get_error() to see what went
-                *      wrong.
-                */
-               RDEBUG2("SSL_read Error");
-               return UNLANG_ACTION_FAIL;
-       }
-
-       if (!diameter_verify(request, data, data_len)) return UNLANG_ACTION_FAIL;
-
-       /*
-        *      Add the tunneled attributes to the request request.
-        */
-       if (eap_ttls_decode_pair(request, request->request_ctx, &request->request_pairs, fr_dict_root(fr_dict_internal()),
-                                data, data_len, tls_session->ssl) < 0) {
-               RPEDEBUG("Decoding TTLS TLVs failed");
-               return UNLANG_ACTION_FAIL;
-       }
-
-       /*
-        *      Update other items in the request_t data structure.
-        */
-
-       /*
-        *      No User-Name, try to create one from stored data.
-        */
-       username = fr_pair_find_by_da(&request->request_pairs, NULL, attr_user_name);
-       if (!username) {
-               /*
-                *      No User-Name in the stored data, look for
-                *      an EAP-Identity, and pull it out of there.
-                */
-               if (!t->username) {
-                       vp = fr_pair_find_by_da(&request->request_pairs, NULL, attr_eap_message);
-                       if (vp &&
-                           (vp->vp_length >= EAP_HEADER_LEN + 2) &&
-                           (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) &&
-                           (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) &&
-                           (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
-                               /*
-                                *      Create & remember a User-Name
-                                */
-                               MEM(t->username = fr_pair_afrom_da(t, attr_user_name));
-                               t->username->vp_tainted = true;
-
-                               fr_pair_value_bstrndup(t->username,
-                                                      (char const *)vp->vp_octets + 5, vp->vp_length - 5, true);
-
-                               RDEBUG2("Got tunneled identity of %pV", &t->username->data);
-                       } else {
-                               /*
-                                *      Don't reject the request outright,
-                                *      as it's permitted to do EAP without
-                                *      user-name.
-                                */
-                               RWDEBUG2("No EAP-Identity found to start EAP conversation");
-                       }
-               } /* else there WAS a t->username */
-
-               if (t->username) {
-                       vp = fr_pair_copy(request->request_ctx, t->username);
-                       fr_pair_append(&request->request_pairs, vp);
-               }
-       } /* else the request ALREADY had a User-Name */
-
-       /*
-        *      Process channel binding.
-        */
-       chbind = eap_chbind_vp2packet(request, &request->request_pairs);
-       if (chbind) {
-               fr_radius_packet_code_t chbind_code;
-               CHBIND_REQ *req = talloc_zero(request, CHBIND_REQ);
-
-               RDEBUG2("received chbind request");
-               req->request = chbind;
-               if (username) {
-                       req->username = username;
-               } else {
-                       req->username = NULL;
-               }
-               chbind_code = chbind_process(request, req);
-
-               /* encapsulate response here */
-               if (req->response) {
-                       RDEBUG2("sending chbind response");
-                       fr_pair_append(&request->reply_pairs,
-                                   eap_chbind_packet2vp(request->reply_ctx, req->response));
-               } else {
-                       RDEBUG2("no chbind response");
-               }
-
-               /* clean up chbind req */
-               talloc_free(req);
-
-               if (chbind_code != FR_RADIUS_CODE_ACCESS_ACCEPT) return UNLANG_ACTION_FAIL;
-       }
-
-       /*
-        *      For this round, when the virtual server returns
-        *      we run the process reply function.
-        */
-       if (unlikely(unlang_module_yield(request, process_reply, NULL, 0, eap_session) != UNLANG_ACTION_YIELD)) {
-               return UNLANG_ACTION_FAIL;
-       }
-
-       /*
-        *      Call authentication recursively, which will
-        *      do PAP, CHAP, MS-CHAP, etc.
-        */
-       return eap_virtual_server(request, eap_session, t->server_cs);
-}