]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Move the majority of the SIM/AKA/AKA' logic into process modules
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Mon, 29 Mar 2021 16:16:10 +0000 (17:16 +0100)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 31 Mar 2021 15:12:16 +0000 (16:12 +0100)
25 files changed:
share/dictionary/eap/aka-sim/dictionary.freeradius
src/lib/eap/base.h
src/lib/eap_aka_sim/all.mk
src/lib/eap_aka_sim/attrs.h
src/lib/eap_aka_sim/base.c
src/lib/eap_aka_sim/base.h
src/lib/eap_aka_sim/decode.c
src/lib/eap_aka_sim/encode.c
src/lib/eap_aka_sim/module.c [new file with mode: 0644]
src/lib/eap_aka_sim/module.h [new file with mode: 0644]
src/lib/eap_aka_sim/state_machine.c
src/lib/eap_aka_sim/state_machine.h
src/modules/rlm_eap/types/rlm_eap_aka/rlm_eap_aka.c
src/modules/rlm_eap/types/rlm_eap_aka_prime/rlm_eap_aka_prime.c
src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c
src/process/eap_aka/all.mk [new file with mode: 0644]
src/process/eap_aka/base.c [new file with mode: 0644]
src/process/eap_aka_prime/all.mk [new file with mode: 0644]
src/process/eap_aka_prime/base.c [new file with mode: 0644]
src/process/eap_sim/all.mk [new file with mode: 0644]
src/process/eap_sim/base.c [new file with mode: 0644]
src/tests/eapol_test/config/aka-prime/methods-enabled/aka-prime
src/tests/eapol_test/config/aka-prime/sites-enabled/aka-prime
src/tests/eapol_test/config/aka/methods-enabled/aka
src/tests/eapol_test/config/aka/sites-enabled/aka

index 41c6cba5151fb6bcda45ca63df00a3fead6f32fa..d91af9cee4fd63c1f60d5eafc0d55ebcc2bb59ac 100644 (file)
@@ -10,7 +10,7 @@
 #  Internal (Non-Protocol) attributes
 #
 FLAGS  internal
-ATTRIBUTE      Subtype                                 256     integer
+ATTRIBUTE      Subtype                                 260     integer
 
 VALUE  Subtype                         AKA-Challenge           1
 VALUE  Subtype                         AKA-Authentication-Reject 2
@@ -22,46 +22,52 @@ VALUE       Subtype                         AKA-SIM-Notification    12
 VALUE  Subtype                         AKA-SIM-Reauthentication 13
 VALUE  Subtype                         AKA-SIM-Client-Error    14
 
-ATTRIBUTE      HMAC                                    257     string
-ATTRIBUTE      Key                                     258     octets
-ATTRIBUTE      Extra                                   259     octets
+#
+#  Keying material exported by the state machine
+#  to allow the encoder to function.
+#
+ATTRIBUTE      HMAC-Extra-Request                      261     octets
+ATTRIBUTE      HMAC-Extra-Response                     262     octets
+ATTRIBUTE      K-Encr                                  263     octets
+ATTRIBUTE      K-Aut                                   264     octets
+
+#
+#  Keys passed to the session resumption sections
+#  for storage and retrieval.
+#
+ATTRIBUTE      MK                                      270     octets          # Master key
+ATTRIBUTE      AK                                      271     octets
+ATTRIBUTE      K-Re                                    272     octets          # AKA' KDF 1 reauthentication key.
 
 #
 #  UMTS vector
 #
-ATTRIBUTE      XRES                                    260     octets
-ATTRIBUTE      CK                                      261     octets
-ATTRIBUTE      IK                                      262     octets
-ATTRIBUTE      AUTHN                                   263     octets
+ATTRIBUTE      XRES                                    280     octets
+ATTRIBUTE      CK                                      281     octets
+ATTRIBUTE      IK                                      282     octets
+ATTRIBUTE      AUTHN                                   283     octets
 
 #
 #  SIM vector
 #
-ATTRIBUTE      SRES                                    270     octets
-ATTRIBUTE      KC                                      271     octets
+ATTRIBUTE      SRES                                    290     octets
+ATTRIBUTE      KC                                      291     octets
 
 #
 #  Identity negotiation
 #
-ATTRIBUTE      Method-Hint                             280     integer
+ATTRIBUTE      Method-Hint                             300     integer
 
 VALUE  Method-Hint                     AKA-Prime               0
 VALUE  Method-Hint                     AKA                     1
 VALUE  Method-Hint                     SIM                     2
 
-ATTRIBUTE      Identity-Type                           281     integer
+ATTRIBUTE      Identity-Type                           310     integer
 
 VALUE  Identity-Type                   Permanent               0
 VALUE  Identity-Type                   Pseudonym               1
 VALUE  Identity-Type                   Fastauth                2
 
-ATTRIBUTE      Permanent-Identity                      282     string
-ATTRIBUTE      KDF-Identity                            283     string  # Allow override of KDF Identity input
-
-#
-#  Reauthentication
-#
-ATTRIBUTE      MK                                      290     octets          # Master key
-ATTRIBUTE      AK                                      291     octets
-ATTRIBUTE      K-Re                                    292     octets          # AKA' KDF 1 reauthentication key.
+ATTRIBUTE      Permanent-Identity                      311     string
+ATTRIBUTE      KDF-Identity                            312     string  # Allow override of KDF Identity input
 
index c53da011b4509539c0907d6e1499a38a26935a6f..ddc6f08875b39a766e824508b2f1a7c83386bcf5 100644 (file)
@@ -45,15 +45,6 @@ RCSIDH(lib_eap_base_h, "$Id$")
 #define REQUEST_DATA_EAP_TUNNEL_CALLBACK FR_EAP_MESSAGE
 #define REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK ((FR_EAP_MESSAGE << 16) | FR_EAP_METHOD_MSCHAPV2)
 
-
-#define EAP_SECTION_DEFINE(_field, _verb, _name) \
-       { \
-               .name = _verb, \
-               .name2 = _name, \
-               .component = MOD_AUTHORIZE, \
-               .offset = offsetof(eap_aka_sim_actions_t, _field), \
-       }
-
 /*
  *     This is for tunneled callbacks
  */
index 35c59b429e358359d10e2113c4f825a196723160..757139411b9849a470baa75ed9346319c6f24a5d 100644 (file)
@@ -4,12 +4,13 @@ endif
 
 SOURCES        := \
        base.c \
-       state_machine.c \
        crypto.c \
        decode.c \
        encode.c \
        fips186prf.c \
        id.c \
+       module.c \
+       state_machine.c \
        vector.c \
        xlat.c
 
index 66d0932ad68820d2d2ec526bf6c1b2396fafbba2..6c700859b1abe35c843c6abad908eaa2c5642c2d 100644 (file)
@@ -38,19 +38,21 @@ extern fr_dict_attr_t const *attr_eap_aka_sim_bidding;
 extern fr_dict_attr_t const *attr_eap_aka_sim_checkcode;
 extern fr_dict_attr_t const *attr_eap_aka_sim_ck;
 extern fr_dict_attr_t const *attr_eap_aka_sim_client_error_code;
-extern fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small;
 extern fr_dict_attr_t const *attr_eap_aka_sim_counter;
+extern fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small;
 extern fr_dict_attr_t const *attr_eap_aka_sim_encr_data;
 extern fr_dict_attr_t const *attr_eap_aka_sim_fullauth_id_req;
-extern fr_dict_attr_t const *attr_eap_aka_sim_identity_type;
 extern fr_dict_attr_t const *attr_eap_aka_sim_identity;
+extern fr_dict_attr_t const *attr_eap_aka_sim_identity_type;
 extern fr_dict_attr_t const *attr_eap_aka_sim_ik;
 extern fr_dict_attr_t const *attr_eap_aka_sim_iv;
+extern fr_dict_attr_t const *attr_eap_aka_sim_k_aut;
+extern fr_dict_attr_t const *attr_eap_aka_sim_k_encr;
 extern fr_dict_attr_t const *attr_eap_aka_sim_k_re;
 extern fr_dict_attr_t const *attr_eap_aka_sim_kc;
+extern fr_dict_attr_t const *attr_eap_aka_sim_kdf;
 extern fr_dict_attr_t const *attr_eap_aka_sim_kdf_identity;
 extern fr_dict_attr_t const *attr_eap_aka_sim_kdf_input;
-extern fr_dict_attr_t const *attr_eap_aka_sim_kdf;
 extern fr_dict_attr_t const *attr_eap_aka_sim_mac;
 extern fr_dict_attr_t const *attr_eap_aka_sim_method_hint;
 extern fr_dict_attr_t const *attr_eap_aka_sim_mk;
@@ -63,11 +65,13 @@ extern fr_dict_attr_t const *attr_eap_aka_sim_padding;
 extern fr_dict_attr_t const *attr_eap_aka_sim_permanent_id_req;
 extern fr_dict_attr_t const *attr_eap_aka_sim_permanent_identity;
 extern fr_dict_attr_t const *attr_eap_aka_sim_rand;
+extern fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_request;
 extern fr_dict_attr_t const *attr_eap_aka_sim_res;
+extern fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_response;
 extern fr_dict_attr_t const *attr_eap_aka_sim_result_ind;
+extern fr_dict_attr_t const *attr_eap_aka_sim_selected_version;
 extern fr_dict_attr_t const *attr_eap_aka_sim_sres;
 extern fr_dict_attr_t const *attr_eap_aka_sim_subtype;
-extern fr_dict_attr_t const *attr_eap_aka_sim_selected_version;
 extern fr_dict_attr_t const *attr_eap_aka_sim_version_list;
 extern fr_dict_attr_t const *attr_eap_aka_sim_xres;
 
index 0008f02aa42fa408053674751c15f0c7a374cafb..41a1648914671e2a9f1a2c31ff095de5ffc5644f 100644 (file)
@@ -63,19 +63,21 @@ fr_dict_attr_t const *attr_eap_aka_sim_bidding;
 fr_dict_attr_t const *attr_eap_aka_sim_checkcode;
 fr_dict_attr_t const *attr_eap_aka_sim_ck;
 fr_dict_attr_t const *attr_eap_aka_sim_client_error_code;
-fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small;
 fr_dict_attr_t const *attr_eap_aka_sim_counter;
+fr_dict_attr_t const *attr_eap_aka_sim_counter_too_small;
 fr_dict_attr_t const *attr_eap_aka_sim_encr_data;
 fr_dict_attr_t const *attr_eap_aka_sim_fullauth_id_req;
-fr_dict_attr_t const *attr_eap_aka_sim_identity_type;
 fr_dict_attr_t const *attr_eap_aka_sim_identity;
+fr_dict_attr_t const *attr_eap_aka_sim_identity_type;
 fr_dict_attr_t const *attr_eap_aka_sim_ik;
 fr_dict_attr_t const *attr_eap_aka_sim_iv;
+fr_dict_attr_t const *attr_eap_aka_sim_k_aut;
+fr_dict_attr_t const *attr_eap_aka_sim_k_encr;
 fr_dict_attr_t const *attr_eap_aka_sim_k_re;
 fr_dict_attr_t const *attr_eap_aka_sim_kc;
+fr_dict_attr_t const *attr_eap_aka_sim_kdf;
 fr_dict_attr_t const *attr_eap_aka_sim_kdf_identity;
 fr_dict_attr_t const *attr_eap_aka_sim_kdf_input;
-fr_dict_attr_t const *attr_eap_aka_sim_kdf;
 fr_dict_attr_t const *attr_eap_aka_sim_mac;
 fr_dict_attr_t const *attr_eap_aka_sim_method_hint;
 fr_dict_attr_t const *attr_eap_aka_sim_mk;
@@ -88,10 +90,12 @@ fr_dict_attr_t const *attr_eap_aka_sim_padding;
 fr_dict_attr_t const *attr_eap_aka_sim_permanent_id_req;
 fr_dict_attr_t const *attr_eap_aka_sim_permanent_identity;
 fr_dict_attr_t const *attr_eap_aka_sim_rand;
+fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_request;
 fr_dict_attr_t const *attr_eap_aka_sim_res;
+fr_dict_attr_t const *attr_eap_aka_sim_hmac_extra_response;
 fr_dict_attr_t const *attr_eap_aka_sim_result_ind;
-fr_dict_attr_t const *attr_eap_aka_sim_sres;
 fr_dict_attr_t const *attr_eap_aka_sim_selected_version;
+fr_dict_attr_t const *attr_eap_aka_sim_sres;
 fr_dict_attr_t const *attr_eap_aka_sim_subtype;
 fr_dict_attr_t const *attr_eap_aka_sim_version_list;
 fr_dict_attr_t const *attr_eap_aka_sim_xres;
@@ -120,19 +124,21 @@ fr_dict_attr_autoload_t libfreeradius_aka_sim_dict_attr[] = {
        { .out = &attr_eap_aka_sim_checkcode, .name = "Checkcode", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_ck, .name = "CK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_client_error_code, .name = "Client-Error-Code", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
-       { .out = &attr_eap_aka_sim_counter_too_small, .name = "Encr-Data.Counter-Too-Small", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_counter, .name = "Encr-Data.Counter", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_counter_too_small, .name = "Encr-Data.Counter-Too-Small", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_encr_data, .name = "Encr-Data", .type = FR_TYPE_TLV, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_fullauth_id_req, .name = "Fullauth-ID-Req", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim },
-       { .out = &attr_eap_aka_sim_identity_type, .name = "Identity-Type", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_identity, .name = "Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_identity_type, .name = "Identity-Type", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_ik, .name = "IK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_iv, .name = "IV", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_k_aut, .name = "K-Aut", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_k_encr, .name = "K-Encr", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_k_re, .name = "K-Re", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_kc, .name = "KC", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_kdf, .name = "KDF", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_kdf_identity, .name = "KDF-Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_kdf_input, .name = "KDF-Input", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim },
-       { .out = &attr_eap_aka_sim_kdf, .name = "KDF", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_mac, .name = "MAC", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_method_hint, .name = "Method-Hint", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_mk, .name = "MK", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
@@ -145,10 +151,12 @@ fr_dict_attr_autoload_t libfreeradius_aka_sim_dict_attr[] = {
        { .out = &attr_eap_aka_sim_permanent_id_req, .name = "Permanent-Id-Req", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_permanent_identity, .name = "Permanent-Identity", .type = FR_TYPE_STRING, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_rand, .name = "RAND", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_hmac_extra_request, .name = "HMAC-Extra-Request", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_res, .name = "RES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_hmac_extra_response, .name = "HMAC-Extra-Response", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_result_ind, .name = "Result-Ind", .type = FR_TYPE_BOOL, .dict = &dict_eap_aka_sim },
-       { .out = &attr_eap_aka_sim_sres, .name = "RES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_selected_version, .name = "Selected-Version", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
+       { .out = &attr_eap_aka_sim_sres, .name = "SRES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_subtype, .name = "Subtype", .type = FR_TYPE_UINT32, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_version_list, .name = "Version-List", .type = FR_TYPE_UINT16, .dict = &dict_eap_aka_sim },
        { .out = &attr_eap_aka_sim_xres, .name = "XRES", .type = FR_TYPE_OCTETS, .dict = &dict_eap_aka_sim },
index 04ae1a1e91bdbcef086a1d4961a35cfd77b34105..e8381a643c04503610eebb01f9e139212fc9b7a8 100644 (file)
@@ -218,27 +218,27 @@ typedef struct {
        uint8_t         emsk[64];                               //!< Derived extended master session key.
 } fr_aka_sim_keys_t;
 
+/** Encoder/decoder ctx
+ *
+ */
 typedef struct {
-       fr_aka_sim_keys_t const         *keys;                  //!< From the EAP session.
-       uint8_t                         iv[AKA_SIM_IV_SIZE];    //!< From the current packet.
-       bool                            have_iv;                //!< Whether we found the IV already.
-} fr_aka_sim_decode_ctx_t;
+       uint8_t         iv[AKA_SIM_IV_SIZE];                    //!< From the current packet.
+       bool            have_iv;                                //!< Whether we found the IV already.
 
-typedef struct {
-       fr_dict_attr_t const            *root;                  //!< Root attribute of the dictionary.
-       fr_aka_sim_keys_t const *keys;                          //!< From the EAP session.
-       uint8_t                         iv[AKA_SIM_IV_SIZE];    //!< Generated by us using our PRNG.
-       bool                            iv_included;            //!< Whether we've already added an IV to this packet.
+       eap_packet_t    *eap_packet;                            //!< Needed for validating AT_MAC.
 
-       /*
-        *      Additional HMAC inputs
-        */
-       EVP_MD const                    *hmac_md;               //!< HMAC digest algorithm, usually EVP_sha1().
-       eap_packet_t                    *eap_packet;            //!< Needed for HMAC generation so we can construct
-                                                               ///< the EAP packet header.
-       uint8_t const                   *hmac_extra;            //!< Extra data for the HMAC function.
-       size_t                          hmac_extra_len;         //!< The length of the HMAC data.
-} fr_aka_sim_encode_ctx_t;
+       EVP_MD const    *hmac_md;                               //!< HMAC digest algorithm, usually EVP_sha1().
+       EVP_MD const    *checkcode_md;                          //!< HMAC we use for calculating the checkcode.
+
+       uint8_t const   *hmac_extra;                            //!< Extra data for the HMAC function.
+       size_t          hmac_extra_len;
+
+       uint8_t const   *k_encr;                                //!< The encryption key used for encrypting.
+                                                               ///< always 16 bytes.
+
+       uint8_t const   *k_aut;                                 //!< The authentication key used for signing.
+       size_t          k_aut_len;
+} fr_aka_sim_ctx_t;
 
 extern size_t const fr_aka_sim_attr_sizes[FR_TYPE_MAX + 1][2];
 
@@ -246,10 +246,10 @@ extern size_t const fr_aka_sim_attr_sizes[FR_TYPE_MAX + 1][2];
  *     decode.c
  */
 ssize_t                fr_aka_sim_decode_pair(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t const *dict,
-                                          uint8_t const *data, size_t data_len, void *decode_ctx);
+                                      uint8_t const *data, size_t data_len, void *decode_ctx);
 
 int            fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const *dict,
-                                     uint8_t const *data, size_t data_len, fr_aka_sim_decode_ctx_t *ctx);
+                                 uint8_t const *data, size_t data_len, fr_aka_sim_ctx_t *ctx);
 
 /*
  *     encode.c
@@ -280,9 +280,9 @@ int         fr_aka_sim_crypto_update_checkcode(fr_aka_sim_checkcode_t *checkcode, eap_p
 ssize_t                fr_aka_sim_crypto_finalise_checkcode(uint8_t *out, fr_aka_sim_checkcode_t **checkcode);
 
 ssize_t                fr_aka_sim_crypto_sign_packet(uint8_t out[static AKA_SIM_MAC_DIGEST_SIZE],
-                                                 eap_packet_t *eap_packet, bool zero_mac,
-                                                 EVP_MD const *md, uint8_t const *key, size_t const key_len,
-                                                 uint8_t const *hmac_extra, size_t const hmac_extra_len);
+                                             eap_packet_t *eap_packet, bool zero_mac,
+                                             EVP_MD const *md, uint8_t const *key, size_t const key_len,
+                                             uint8_t const *hmac_extra, size_t const hmac_extra_len);
 
 int            fr_aka_sim_crypto_gsm_kdf_0(fr_aka_sim_keys_t *keys);
 
index 55c8b5dfe67cb40d819f4e945ec045b0c799aefb..d2a618d1ca0ce04679099a08ac19f6ce270b4532 100644 (file)
@@ -116,7 +116,7 @@ static ssize_t sim_value_decrypt(TALLOC_CTX *ctx, uint8_t **out,
                                 uint8_t const *data, size_t const attr_len, size_t const data_len,
                                 void *decode_ctx)
 {
-       fr_aka_sim_decode_ctx_t *packet_ctx = decode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = decode_ctx;
        EVP_CIPHER_CTX          *evp_ctx;
        EVP_CIPHER const        *evp_cipher = EVP_aes_128_cbc();
        size_t                  block_size = EVP_CIPHER_block_size(evp_cipher);
@@ -532,7 +532,7 @@ static ssize_t sim_decode_pair_value(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_d
        size_t                  prefix = 0;
        fr_dict_attr_t          *unknown;
 
-       fr_aka_sim_decode_ctx_t *packet_ctx = decode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = decode_ctx;
 
        if (!fr_cond_assert(attr_len <= data_len)) return -1;
        if (!fr_cond_assert(parent)) return -1;
@@ -960,7 +960,7 @@ ssize_t fr_aka_sim_decode_pair(TALLOC_CTX *ctx, fr_dcursor_t *cursor, fr_dict_t
  *     - -1 on failure.
  */
 int fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const *dict,
-                 uint8_t const *data, size_t data_len, fr_aka_sim_decode_ctx_t *decode_ctx)
+                 uint8_t const *data, size_t data_len, fr_aka_sim_ctx_t *decode_ctx)
 {
        ssize_t                 ret;
        uint8_t const           *p = data;
@@ -1025,23 +1025,21 @@ int fr_aka_sim_decode(request_t *request, fr_dcursor_t *decoded, fr_dict_t const
        return 0;
 }
 
-static int _test_ctx_free(UNUSED fr_aka_sim_decode_ctx_t *ctx)
+static int _test_ctx_free(UNUSED fr_aka_sim_ctx_t *ctx)
 {
        fr_aka_sim_free();
 
        return 0;
 }
 
-static fr_aka_sim_decode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len)
+static fr_aka_sim_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len)
 {
-       fr_aka_sim_decode_ctx_t *test_ctx;
-       fr_aka_sim_keys_t               *keys;
+       fr_aka_sim_ctx_t        *test_ctx;
 
        if (fr_aka_sim_init() < 0) return NULL;
 
-       test_ctx = talloc_zero(ctx, fr_aka_sim_decode_ctx_t);
-       test_ctx->keys = keys = talloc_zero(test_ctx, fr_aka_sim_keys_t);
-       memcpy(keys->k_encr, k_encr, k_encr_len);
+       test_ctx = talloc_zero(ctx, fr_aka_sim_ctx_t);
+       test_ctx->k_encr = talloc_memdup(test_ctx, k_encr, k_encr_len);
        talloc_set_destructor(test_ctx, _test_ctx_free);
 
        return test_ctx;
@@ -1051,7 +1049,7 @@ static fr_aka_sim_decode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_
  */
 static int decode_test_ctx_sim(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_decode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t        *test_ctx;
        static uint8_t          k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07,
                                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
 
@@ -1068,7 +1066,7 @@ static int decode_test_ctx_sim(void **out, TALLOC_CTX *ctx)
 
 static int decode_test_ctx_aka(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_decode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t *test_ctx;
        static uint8_t          k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07,
                                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
 
@@ -1084,7 +1082,7 @@ static int decode_test_ctx_aka(void **out, TALLOC_CTX *ctx)
 
 static int decode_test_ctx_sim_rfc4186(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_decode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t *test_ctx;
        static uint8_t          k_encr[] = { 0x53, 0x6e, 0x5e, 0xbc, 0x44 ,0x65, 0x58, 0x2a,
                                             0xa6, 0xa8, 0xec, 0x99, 0x86, 0xeb, 0xb6, 0x20 };
 
index 8953cffdb2924a8ea89244707b3b3528a87e6f5c..e7b68eb424617b00a83f4e366d653d82a671caa0 100644 (file)
@@ -67,10 +67,9 @@ static ssize_t encode_tlv_hdr(fr_dbuff_t *dbuff,
  *
  * @return true if the underlying fr_pair_t is EAP_AKA encodable, false otherwise
  */
-static bool is_eap_aka_encodable(void const *item, void const *uctx)
+static bool is_eap_aka_encodable(void const *item, UNUSED void const *uctx)
 {
        fr_pair_t const         *vp = item;
-       fr_aka_sim_encode_ctx_t const *packet_ctx = uctx;
 
        if (!vp) return false;
        if (vp->da->flags.internal) return false;
@@ -79,7 +78,7 @@ static bool is_eap_aka_encodable(void const *item, void const *uctx)
         *      and absence is 'false'
         */
        if ((vp->da->type == FR_TYPE_BOOL) && (vp->vp_bool == false)) return false;
-       if (!fr_dict_attr_common_parent(packet_ctx->root, vp->da, true)) return false;
+       if (!fr_dict_attr_common_parent(fr_dict_root(dict_eap_aka_sim), vp->da, true)) return false;
 
        return true;
 }
@@ -101,14 +100,14 @@ static bool is_eap_aka_encodable(void const *item, void const *uctx)
  */
 static ssize_t encode_iv(fr_dbuff_t *dbuff, void *encode_ctx)
 {
-       fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = encode_ctx;
        uint32_t                iv[4];
        fr_dbuff_t              work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff);
 
        /*
         *      One IV per packet
         */
-       if (packet_ctx->iv_included) return 0;
+       if (packet_ctx->have_iv) return 0;
 
        /*
         *      Generate IV
@@ -125,7 +124,7 @@ static ssize_t encode_iv(fr_dbuff_t *dbuff, void *encode_ctx)
 
        FR_PROTO_HEX_DUMP(fr_dbuff_start(&work_dbuff), fr_dbuff_used(&work_dbuff), "Initialisation vector");
 
-       packet_ctx->iv_included = true;
+       packet_ctx->have_iv = true;
 
        return fr_dbuff_set(dbuff, &work_dbuff);
 }
@@ -156,10 +155,11 @@ static ssize_t encode_encrypted_value(fr_dbuff_t *dbuff,
        size_t                  total_len, pad_len, encr_len, len = 0;
        fr_dbuff_t              work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff);
        uint8_t                 *encr = NULL;
-       fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = encode_ctx;
        EVP_CIPHER_CTX          *evp_ctx;
        EVP_CIPHER const        *evp_cipher = EVP_aes_128_cbc();
        size_t                  block_size = EVP_CIPHER_block_size(evp_cipher);
+       ssize_t                 slen;
        /*
         *      Needs to be a multiple of 4 else we can't
         *      pad with AT_PADDING correctly as its
@@ -273,7 +273,7 @@ static ssize_t encode_value(fr_dbuff_t *dbuff,
        fr_dbuff_t              work_dbuff = FR_DBUFF_NO_ADVANCE(dbuff);
        fr_pair_t const         *vp = fr_dcursor_current(cursor);
        fr_dict_attr_t const    *da = da_stack->da[depth];
-       fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = encode_ctx;
 
        VP_VERIFY(vp);
        FR_PROTO_STACK_PRINT(da_stack, depth);
@@ -322,7 +322,7 @@ static ssize_t encode_value(fr_dbuff_t *dbuff,
                        return PAIR_ENCODE_FATAL_ERROR;
                }
                memcpy(packet_ctx->iv, vp->vp_octets, sizeof(packet_ctx->iv));
-               packet_ctx->iv_included = true;
+               packet_ctx->have_iv = true;
                break;  /* Encode IV */
 
        /*
@@ -662,7 +662,7 @@ static inline ssize_t encode_tlv_internal(fr_dbuff_t *dbuff,
        value_dbuff = FR_DBUFF_NO_ADVANCE(&work_dbuff);
        fr_dbuff_marker(&value_start, &value_dbuff);
 
-       for(;;) {
+       for (;;) {
                FR_PROTO_STACK_PRINT(da_stack, depth);
 
                /*
@@ -679,19 +679,28 @@ static inline ssize_t encode_tlv_internal(fr_dbuff_t *dbuff,
                        slen = encode_rfc_hdr(&work_dbuff, da_stack, depth + 1, cursor, encode_ctx);
                }
 
-               if (slen <= 0) return slen;
+               if (slen <= 0) {
+                       DEBUG("OUT OF BUFFER");
+                       return slen;
+               }
 
                /*
                 *      If nothing updated the attribute, stop
                 */
-               if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) break;
+               if (!fr_dcursor_current(cursor) || (vp == fr_dcursor_current(cursor))) {
+                       DEBUG("NO ADVNACE");
+                       break;
+               }
 
                /*
                 *      We can encode multiple sub TLVs, if after
                 *      rebuilding the TLV Stack, the attribute
                 *      at this depth is the same.
                 */
-               if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) break;
+               if ((da != da_stack->da[depth]) || (da_stack->depth < da->depth)) {
+                       DEBUG("WRONG DEPTH");
+                       break;
+               }
                vp = fr_dcursor_current(cursor);
        }
 
@@ -780,7 +789,6 @@ ssize_t fr_aka_sim_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *en
 
        fr_da_stack_t           da_stack;
        fr_dict_attr_t const    *da = NULL;
-       fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx;
 
        fr_dbuff_marker(&m, &work_dbuff);
        if (!cursor) return PAIR_ENCODE_FATAL_ERROR;
@@ -809,7 +817,7 @@ ssize_t fr_aka_sim_encode_pair(fr_dbuff_t *dbuff, fr_dcursor_t *cursor, void *en
        /*
         *      Fast path for the common case.
         */
-       if ((vp->da->parent == packet_ctx->root) && (vp->vp_type != FR_TYPE_TLV)) {
+       if ((vp->da->parent == fr_dict_root(dict_eap_aka_sim)) && (vp->vp_type != FR_TYPE_TLV)) {
                da_stack.da[0] = vp->da;
                da_stack.da[1] = NULL;
                da_stack.depth = 1;
@@ -868,7 +876,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e
 
        unsigned char           subtype;
        fr_dcursor_t            cursor;
-       fr_aka_sim_encode_ctx_t *packet_ctx = encode_ctx;
+       fr_aka_sim_ctx_t        *packet_ctx = encode_ctx;
        eap_packet_t            *eap_packet = packet_ctx->eap_packet;
 
        /*
@@ -876,7 +884,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e
         *      It might be too big for putting into an
         *      EAP packet.
         */
-       vp = fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_SUBTYPE);
+       vp = fr_pair_find_by_child_num(to_encode, fr_dict_root(dict_eap_aka_sim), FR_SUBTYPE);
        if (!vp) {
                REDEBUG("Missing subtype attribute");
                return PAIR_ENCODE_FATAL_ERROR;
@@ -892,7 +900,7 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e
        /*
         *      Will we need to generate a HMAC?
         */
-       if (fr_pair_find_by_child_num(to_encode, packet_ctx->root, FR_MAC)) do_hmac = true;
+       if (fr_pair_find_by_child_num(to_encode, fr_dict_root(dict_eap_aka_sim), FR_MAC)) do_hmac = true;
 
        /*
         *      Fast path, we just need to encode a subtype
@@ -948,9 +956,9 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e
         */
        if (do_hmac) {
                slen = fr_aka_sim_crypto_sign_packet(fr_dbuff_current(&hmac), eap_packet, false,
-                                                packet_ctx->hmac_md,
-                                                packet_ctx->keys->k_aut, packet_ctx->keys->k_aut_len,
-                                                packet_ctx->hmac_extra, packet_ctx->hmac_extra_len);
+                                                    packet_ctx->hmac_md,
+                                                    packet_ctx->k_aut, packet_ctx->k_aut_len,
+                                                    packet_ctx->hmac_extra, packet_ctx->hmac_extra_len);
                if (slen < 0) goto error;
                FR_PROTO_HEX_DUMP(fr_dbuff_current(&hmac) - 4, AKA_SIM_MAC_SIZE, "hmac attribute");
        }
@@ -974,21 +982,22 @@ ssize_t fr_aka_sim_encode(request_t *request, fr_pair_list_t *to_encode, void *e
 /*
  *     Test ctx data
  */
-static int _test_ctx_free(UNUSED fr_aka_sim_encode_ctx_t *ctx)
+static int _test_ctx_free(UNUSED fr_aka_sim_ctx_t *ctx)
 {
        fr_aka_sim_free();
 
        return 0;
 }
 
-static fr_aka_sim_encode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len)
+static fr_aka_sim_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_encr, size_t k_encr_len)
 {
-       fr_aka_sim_encode_ctx_t *test_ctx;
-       fr_aka_sim_keys_t               *keys;
+       fr_aka_sim_ctx_t        *test_ctx;
+
+       test_ctx = talloc_zero(ctx, fr_aka_sim_ctx_t);
+       test_ctx->k_encr = talloc_memdup(test_ctx, k_encr, k_encr_len);
+       test_ctx->k_aut = talloc_zero_array(test_ctx, uint8_t, 16);
+       test_ctx->k_aut_len = 16;
 
-       test_ctx = talloc_zero(ctx, fr_aka_sim_encode_ctx_t);
-       test_ctx->keys = keys = talloc_zero(test_ctx, fr_aka_sim_keys_t);
-       memcpy(keys->k_encr, k_encr, k_encr_len);
        talloc_set_destructor(test_ctx, _test_ctx_free);
 
        if (fr_aka_sim_init() < 0) {
@@ -1004,15 +1013,14 @@ static fr_aka_sim_encode_ctx_t *test_ctx_init(TALLOC_CTX *ctx, uint8_t const *k_
  */
 static int encode_test_ctx_sim(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_encode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t        *test_ctx;
        static uint8_t          k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07,
                                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
 
        test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr));
        if (!test_ctx) return -1;
 
-       test_ctx->root = fr_dict_root(dict_eap_aka_sim);
-       test_ctx->iv_included = true;   /* Ensures IV is all zeros */
+       test_ctx->have_iv = true;       /* Ensures IV is all zeros */
 
        *out = test_ctx;
 
@@ -1021,15 +1029,14 @@ static int encode_test_ctx_sim(void **out, TALLOC_CTX *ctx)
 
 static int encode_test_ctx_aka(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_encode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t        *test_ctx;
        static uint8_t          k_encr[] = { 0x00, 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07,
                                             0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
 
        test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr));
        if (!test_ctx) return -1;
 
-       test_ctx->root = fr_dict_root(dict_eap_aka_sim);
-       test_ctx->iv_included = true;   /* Ensures IV is all zeros */
+       test_ctx->have_iv = true;       /* Ensures IV is all zeros */
 
        *out = test_ctx;
 
@@ -1038,15 +1045,13 @@ static int encode_test_ctx_aka(void **out, TALLOC_CTX *ctx)
 
 static int encode_test_ctx_sim_rfc4186(void **out, TALLOC_CTX *ctx)
 {
-       fr_aka_sim_encode_ctx_t *test_ctx;
+       fr_aka_sim_ctx_t        *test_ctx;
        static uint8_t          k_encr[] = { 0x53, 0x6e, 0x5e, 0xbc, 0x44 ,0x65, 0x58, 0x2a,
                                             0xa6, 0xa8, 0xec, 0x99, 0x86, 0xeb, 0xb6, 0x20 };
 
        test_ctx = test_ctx_init(ctx, k_encr, sizeof(k_encr));
        if (!test_ctx) return -1;
 
-       test_ctx->root = fr_dict_root(dict_eap_aka_sim);
-
        *out = test_ctx;
 
        return 0;
diff --git a/src/lib/eap_aka_sim/module.c b/src/lib/eap_aka_sim/module.c
new file mode 100644 (file)
index 0000000..091e376
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ *   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
+ */
+
+/**
+ * @file src/lib/eap_aka_sim/module.c
+ * @brief Common encode/decode functions for EAP subtype modules
+ *
+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/eap/types.h>
+#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/server/pair.h>
+#include <freeradius-devel/server/virtual_servers.h>
+#include <freeradius-devel/unlang/interpret.h>
+#include <freeradius-devel/unlang/module.h>
+#include <freeradius-devel/util/rand.h>
+
+#include "attrs.h"
+#include "base.h"
+#include "module.h"
+
+/** Encode EAP session data from attributes
+ *
+ */
+static unlang_action_t mod_encode(rlm_rcode_t *p_result, module_ctx_t const *mctx,
+                                 request_t *request, UNUSED void *rctx)
+{
+       eap_aka_sim_module_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_module_conf_t);
+       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_mod_session_t       *mod_session = talloc_get_type_abort(eap_session->opaque,
+                                                                               eap_aka_sim_mod_session_t);
+       fr_pair_t                       *subtype_vp;
+
+       static eap_code_t               rcode_to_eap_code[RLM_MODULE_NUMCODES] = {
+                                               [RLM_MODULE_REJECT]     = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_FAIL]       = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_OK]         = FR_EAP_CODE_SUCCESS,
+                                               [RLM_MODULE_HANDLED]    = FR_EAP_CODE_REQUEST,
+                                               [RLM_MODULE_INVALID]    = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_DISALLOW]   = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_NOTFOUND]   = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_NOOP]       = FR_EAP_CODE_FAILURE,
+                                               [RLM_MODULE_UPDATED]    = FR_EAP_CODE_FAILURE
+                                       };
+       eap_code_t                      code;
+       rlm_rcode_t                     rcode = unlang_interpret_stack_result(request);
+       fr_aka_sim_ctx_t                encode_ctx;
+       uint8_t const                   *request_hmac_extra = NULL;
+       size_t                          request_hmac_extra_len = 0;
+       int                             ret;
+
+       /*
+        *      If there's no subtype vp, we look at the rcode
+        *      from the virtual server to determine what kind
+        *      of EAP response to send.
+        */
+       subtype_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_subtype);
+       if (!subtype_vp) {
+               eap_session->this_round->request->code = (rcode == RLM_MODULE_OK) ?
+                                                               FR_EAP_CODE_SUCCESS : FR_EAP_CODE_FAILURE;
+               /*
+                *      RFC 3748 requires the request and response
+                *      IDs to be identical for EAP-SUCCESS and
+                *      EAP-FAILURE.
+                *
+                *      The EAP common code will do the right thing
+                *      here if we just tell it we haven't se the
+                *      request ID.
+                */
+               eap_session->this_round->set_request_id = false;
+               eap_session->finished = true;
+               TALLOC_FREE(eap_session->opaque);
+
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
+
+       /*
+        *      If there is a subtype vp, verify the return
+        *      code allows us send EAP-SIM/AKA/AKA' data back.
+        */
+       code = rcode_to_eap_code[rcode];
+       if (code != FR_EAP_CODE_REQUEST) {
+               eap_session->this_round->request->code = code;
+               eap_session->this_round->set_request_id = false;
+               eap_session->finished = true;
+               TALLOC_FREE(eap_session->opaque);
+
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
+
+       RDEBUG2("Encoding attributes");
+       log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->reply_pairs, NULL);
+
+       /*
+        *      It's not an EAP-Success or an EAP-Failure
+        *      it's a real EAP-SIM/AKA/AKA' response.
+        */
+       eap_session->this_round->request->type.num = inst->type;
+       eap_session->this_round->request->code = code;
+       eap_session->this_round->set_request_id = true;
+
+       /*
+        *      RFC 3748 says this ID need only be different to
+        *      the previous ID.
+        *
+        *      We need to set the type, code, id here as the
+        *      HMAC operates on the complete packet we're
+        *      returning including the EAP headers, so the packet
+        *      fields must be filled in before we call encode.
+        */
+       eap_session->this_round->response->id++;
+
+       /*
+        *      Perform different actions depending on the type
+        *      of request we're sending.
+        */
+       switch (subtype_vp->vp_uint16) {
+       case FR_SUBTYPE_VALUE_SIM_START:
+       case FR_SUBTYPE_VALUE_AKA_IDENTITY:
+       {
+               fr_pair_t       *id_vp;
+
+               if (RDEBUG_ENABLED2) break;
+
+               /*
+                *      Figure out if the state machine is
+                *      requesting an ID.
+                */
+               if ((id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_any_id_req)) ||
+                   (id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_fullauth_id_req)) ||
+                   (id_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_permanent_id_req))) {
+                       RDEBUG2("Sending EAP-Request/%pV (%s)", &subtype_vp->data, id_vp->da->name);
+               } else {
+                       RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data);
+               }
+       }
+               break;
+
+       /*
+        *      Deal with sending bidding VP
+        *
+        *      This can either come from policy or be set by the default
+        *      virtual server.
+        *
+        *      We send AT_BIDDING in our EAP-Request/AKA-Challenge message
+        *      to tell the supplicant that if it has AKA' available/enabled
+        *      it should have used that.
+        */
+       case FR_SUBTYPE_VALUE_AKA_CHALLENGE:
+       {
+               fr_pair_t *bidding_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_bidding);
+
+               /*
+                *      Explicit NO
+                */
+               if (inst->aka.send_at_bidding_prefer_prime_is_set &&
+                   !inst->aka.send_at_bidding_prefer_prime) {
+                       if (bidding_vp) pair_delete_reply(attr_eap_aka_sim_bidding);
+               /*
+                *      Implicit or explicit YES
+                */
+               } else if (inst->aka.send_at_bidding_prefer_prime) {
+                       MEM(pair_append_reply(&bidding_vp, attr_eap_aka_sim_bidding) >= 0);
+                       bidding_vp->vp_uint16 = FR_BIDDING_VALUE_PREFER_AKA_PRIME;
+               }
+       }
+               FALL_THROUGH;
+
+       case FR_SUBTYPE_VALUE_SIM_CHALLENGE:
+       case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION:
+       {
+               fr_pair_t       *vp;
+
+               /*
+                *      Extra data to append to the packet when signing.
+                */
+               vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_hmac_extra_request);
+               if (vp) {
+                       request_hmac_extra = vp->vp_octets;
+                       request_hmac_extra_len = vp->vp_length;
+               }
+
+               /*
+                *      Extra data to append to the response packet when
+                *      validating the signature.
+                */
+               vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_hmac_extra_response);
+               if (vp) {
+                       fr_assert(!mod_session->response_hmac_extra);
+                       MEM(mod_session->response_hmac_extra = talloc_memdup(mod_session,
+                                                                               vp->vp_octets, vp->vp_length));
+                       mod_session->response_hmac_extra_len = vp->vp_length;
+               }
+               /*
+                *      Key we use for encrypting and decrypting attributes.
+                */
+               vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_k_encr);
+               if (vp) {
+                       fr_assert(!mod_session->ctx.k_encr);
+                       MEM(mod_session->ctx.k_encr = talloc_memdup(mod_session, vp->vp_octets, vp->vp_length));
+               }
+
+               /*
+                *      Key we use for signing and validating mac values.
+                */
+               vp = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_k_aut);
+               if (vp) {
+                       fr_assert(!mod_session->ctx.k_aut);
+                       MEM(mod_session->ctx.k_aut = talloc_memdup(mod_session, vp->vp_octets, vp->vp_length));
+                       mod_session->ctx.k_aut_len = vp->vp_length;
+               }
+
+               fr_assert(mod_session->ctx.k_encr && mod_session->ctx.k_aut);
+       }
+               FALL_THROUGH;
+
+       default:
+               RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data);
+               break;
+       }
+
+       encode_ctx = mod_session->ctx;
+       encode_ctx.eap_packet = eap_session->this_round->request;
+       encode_ctx.hmac_extra = request_hmac_extra;
+       encode_ctx.hmac_extra_len = request_hmac_extra_len;
+
+       ret = fr_aka_sim_encode(request, &request->reply_pairs, &encode_ctx);
+       if (ret <= 0) { /* No valid packets have length 0 */
+               RPEDEBUG("Failed encoding response");
+               RETURN_MODULE_FAIL;
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;  /* rcode is already correct */
+}
+
+/** Decode EAP session data into attribute
+ *
+ */
+unlang_action_t eap_aka_sim_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+       eap_aka_sim_module_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_module_conf_t);
+       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_mod_session_t       *mod_session = talloc_get_type_abort(eap_session->opaque,
+                                                                            eap_aka_sim_mod_session_t);
+       fr_pair_t                       *subtype_vp;
+       fr_dcursor_t                    cursor;
+       int                             ret;
+       fr_aka_sim_ctx_t                decode_ctx;
+
+       switch (eap_session->this_round->response->type.num) {
+       case FR_EAP_METHOD_IDENTITY:
+               break;
+
+       /*
+        *      Only decode data for EAP-SIM/AKA/AKA' responses
+        */
+       default:
+               fr_dcursor_init(&cursor, &request->request_pairs);
+
+               decode_ctx = mod_session->ctx;
+               decode_ctx.hmac_extra = mod_session->response_hmac_extra;
+               decode_ctx.hmac_extra_len = mod_session->response_hmac_extra_len;
+               decode_ctx.eap_packet = eap_session->this_round->response;
+
+               ret = fr_aka_sim_decode(request,
+                                       &cursor,
+                                       dict_eap_aka_sim,
+                                       eap_session->this_round->response->type.data,
+                                       eap_session->this_round->response->type.length,
+                                       &decode_ctx);
+
+               /*
+                *      Only good for one response packet
+                */
+               TALLOC_FREE(mod_session->response_hmac_extra);
+               mod_session->response_hmac_extra_len = 0;
+
+               /*
+                *      RFC 4187 says we *MUST* notify, not just send
+                *      an EAP-Failure in this case where we cannot
+                *      decode an EAP-AKA packet.
+                *
+                *      We instead call the state machine and allow it
+                *      to fail when it can't find the necessary
+                *      attributes.
+                */
+               if (ret < 0) {
+                       RPEDEBUG2("Failed decoding attributes");
+                       goto done;
+               }
+
+               if (!fr_pair_list_empty(&request->request_pairs) && RDEBUG_ENABLED2) {
+                       RDEBUG2("Decoded attributes");
+                       log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->request_pairs, NULL);
+               }
+
+               subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+               if (subtype_vp) {
+                       RDEBUG2("Received EAP-Response/%pV", &(subtype_vp)->data);
+               } else {
+                       REDEBUG2("Missing Sub-Type");
+               }
+               break;
+       }
+
+done:
+       /*
+        *      Setup our encode function as the resumption
+        *      frame when the state machine finishes with
+        *      this round.
+        */
+       (void)unlang_module_yield(request, mod_encode, NULL, NULL);
+
+       if (virtual_server_push(request, inst->virtual_server, UNLANG_SUB_FRAME) < 0) {
+               unlang_interpet_frame_discard(request);
+               RETURN_MODULE_FAIL;
+       }
+
+       return UNLANG_ACTION_PUSHED_CHILD;
+}
diff --git a/src/lib/eap_aka_sim/module.h b/src/lib/eap_aka_sim/module.h
new file mode 100644 (file)
index 0000000..0e99061
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ *   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 lib/eap_aka_sim/module.h
+ * @brief Declarations for the common module functions used by EAP-SIM/AKA submodules
+ *
+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ *
+ * @copyright 2016-2019 The FreeRADIUS server project
+ * @copyright 2016-2019 Network RADIUS SARL <sales@networkradius.com>
+ */
+RCSIDH(lib_eap_aka_sim_module_h, "$Id$")
+
+#include <freeradius-devel/server/cf_util.h>
+#include <freeradius-devel/eap/types.h>
+#include <openssl/evp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+       CONF_SECTION                    *virtual_server;                //!< Virtual server.
+
+       /** Whether we should include a bid-down prevention attribute by default
+        *
+        * Only used for EAP-AKA, as a signal that EAP-AKA' was available/enabled
+        * on the server, and if the client supports EAP-AKA', it should continue
+        * with that EAP-Method instead.
+        */
+       struct {
+               bool                            send_at_bidding_prefer_prime;   //!< Include the AT bidding attribute in
+                                                                               ///< challenge requests.
+               bool                            send_at_bidding_prefer_prime_is_set;    //!< Whether the user specified
+                                                                                       ///< a value.
+       } aka;
+
+       eap_type_t                      type;
+} eap_aka_sim_module_conf_t;
+
+/** Structure used to track session state at the module level
+ *
+ * The process module has a similar structure (eap_aka_sim_module_t) which tracks
+ * all of the cryptographic parameters for the session.
+ *
+ * The structure here stores copies of the cryptographic parameters used for
+ * validating incoming packets, and signing outgoing packets, from control attributes
+ * provided by the state machine.
+ *
+ * This separation is to allow the process module to be executed without the
+ * submodule, so that the state machine can be tested independently of the
+ * encode/decode/crypto code.
+ */
+typedef struct {
+       uint8_t                 id;                     //!< Last ID used, monotonically increments.
+
+       uint8_t                 *response_hmac_extra;   //!< Data to concatenate to response packet
+                                                       ///< before validating.
+       size_t                  response_hmac_extra_len;
+
+       fr_aka_sim_ctx_t        ctx;
+} eap_aka_sim_mod_session_t;
+
+unlang_action_t                eap_aka_sim_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
+
+#ifdef __cplusplus
+}
+#endif
index 9932a2b03879c8c030c562cda23ab94a2f786e97..b816d0d557cdb5a55189ae438dc01bcc01b9709b 100644 (file)
  * @file src/lib/eap_aka_sim/state_machine.c
  * @brief Implement a common state machine for EAP-SIM, EAP-AKA, EAP-AKA'.
  *
- * @author Arran Cudbard-Bell \<a.cudbardb@freeradius.org\>
+ * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  * @copyright 2019 The FreeRADIUS server project
- * @copyright 2019 Network RADIUS \<legal@networkradius.com\>
+ * @copyright 2019 Network RADIUS (legal@networkradius.com)
  */
 RCSID("$Id$")
 #include <freeradius-devel/eap/base.h>
 #include <freeradius-devel/eap/types.h>
+#include <freeradius-devel/unlang/interpret.h>
 #include <freeradius-devel/unlang/module.h>
+#include <freeradius-devel/util/table.h>
 
 #include "base.h"
 #include "state_machine.h"
@@ -36,54 +39,75 @@ RCSID("$Id$")
 #  define EAP_TLS_MPPE_KEY_LEN     32
 #endif
 
-/** A state transition function
- *
- * This is passed to sub-state machines that perform other actions, before
- * fully transitioning to a new state.
- *
- * Examples of these are the sub-state machines that deal with clearing
- * pseudonyms and reauthentication data.
- */
-typedef unlang_action_t(*aka_sim_state_enter_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                               request_t *request, eap_session_t *eap_session);
-
-static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_eap_success(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
-
-static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                        request_t *request, eap_session_t *eap_session);
-static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                          request_t *request, eap_session_t *eap_session);
-static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                          request_t *request, eap_session_t *eap_session);
-static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, eap_session_t *eap_session);
-static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session);
-static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request, eap_session_t *eap_session);
-static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                            request_t *request, eap_session_t *eap_session);
-
-static module_state_func_table_t const aka_sim_stable_table[] = {
-       { "FAILURE-NOTIFICATION",       common_failure_notification     },
-       { "EAP-FAILURE",                common_eap_failure              },
-       { "SUCCESS-NOTIFICATION",       common_success_notification     },
-       { "EAP-SUCCESS",                common_eap_success              },
-       { "REAUTHENTICATION",           common_reauthentication         },
-       { "AKA-CHALLENGE",              aka_challenge                   },
-       { "SIM-CHALLENGE",              sim_challenge                   },
-       { "IDENTITY",                   aka_identity                    },
-       { "SIM-START",                  sim_start                       },
-       { "EAP-IDENTITY",               aka_sim_state_machine_start     },
-
-       { NULL }
+#define STATE(_x) static inline unlang_action_t state_ ## _x(rlm_rcode_t *p_result, \
+                                                            module_ctx_t const *mctx, \
+                                                            request_t *request,\
+                                                            eap_aka_sim_session_t *eap_aka_sim_session)
+#define STATE_GUARD(_x)        static unlang_action_t guard_ ## _x(rlm_rcode_t *p_result, \
+                                                           module_ctx_t const *mctx, \
+                                                           request_t *request, \
+                                                           eap_aka_sim_session_t *eap_aka_sim_session)
+
+#define RESUME(_x) static inline unlang_action_t resume_ ## _x(rlm_rcode_t *p_result, \
+                                                              module_ctx_t const *mctx, \
+                                                              request_t *request, \
+                                                              void *rctx)
+
+#define STATE_TRANSITION(_x) guard_ ## _x(p_result, mctx, request, eap_aka_sim_session);
+
+#define CALL_SECTION(_x)       unlang_module_yield_to_section(p_result, \
+                                                              request, \
+                                                              inst->actions._x, \
+                                                              RLM_MODULE_NOOP, \
+                                                              resume_ ## _x, \
+                                                              mod_signal, \
+                                                              eap_aka_sim_session)
+
+/*
+ *     Declare all state and guard functions to
+ *     avoid ordering issues.
+ */
+STATE(eap_failure);
+STATE_GUARD(eap_failure);
+STATE(common_failure_notification);
+STATE_GUARD(common_failure_notification);
+STATE(eap_success);
+STATE_GUARD(eap_success);
+STATE(common_success_notification);
+STATE_GUARD(common_success_notification);
+STATE(common_reauthentication);
+STATE_GUARD(common_reauthentication);
+STATE(aka_challenge);
+STATE_GUARD(aka_challenge);
+STATE(sim_challenge);
+STATE_GUARD(sim_challenge);
+STATE_GUARD(common_challenge);
+STATE(aka_identity);
+STATE_GUARD(aka_identity);
+STATE(sim_start);
+STATE_GUARD(sim_start);
+STATE_GUARD(common_identity);
+STATE(init);
+
+static fr_table_ptr_ordered_t const aka_sim_state_table[] = {
+       { L("INIT"),                    NULL                                            },
+
+       { L("EAP-IDENTITY"),            (void *)state_init                              },
+       { L("SIM-START"),               (void *)state_sim_start                         },
+       { L("AKA-IDENTITY"),            (void *)state_aka_identity                      },
+
+       { L("SIM-CHALLENGE"),           (void *)state_sim_challenge                     },
+       { L("AKA-CHALLENGE"),           (void *)state_aka_challenge                     },
+
+       { L("SUCCESS-NOTIFICATION"),    (void *)state_common_success_notification       },
+       { L("FAILURE-NOTIFICATION"),    (void *)state_common_failure_notification       },
+
+       { L("REAUTHENTICATION"),        (void *)state_common_reauthentication           },
+
+       { L("EAP-SUCCESS"),             (void *)state_eap_success                       },
+       { L("EAP-FAILURE"),             (void *)state_eap_failure                       }
 };
+static size_t aka_sim_state_table_len = NUM_ELEMENTS(aka_sim_state_table);
 
 /** Cancel a call to a submodule
  *
@@ -92,65 +116,113 @@ static module_state_func_table_t const aka_sim_stable_table[] = {
  * @param[in] rctx     the eap_session_t
  * @param[in] action   to perform.
  */
-static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, UNUSED void *rctx,
+static void mod_signal(UNUSED module_ctx_t const *mctx, request_t *request, void *rctx,
                       fr_state_signal_t action)
 {
-       eap_session_t   *eap_session = eap_session_get(request->parent);
-
        if (action != FR_SIGNAL_CANCEL) return;
 
        RDEBUG2("Request cancelled - Destroying session");
 
-       TALLOC_FREE(eap_session->opaque);
+       /*
+        *      Remove data from the request to
+        *      avoid double free.
+        */
+       if (!fr_cond_assert(request_data_get(request, (void *)eap_aka_sim_state_machine_process, 0) == rctx)) return;
+
+       TALLOC_FREE(rctx);
 }
 
 /** Warn the user that the rcode they provided is being ignored in this section
  *
  */
-static inline void section_rcode_ignored(request_t *request)
-{
-       switch (request->rcode) {
-       case RLM_MODULE_USER_SECTION_REJECT:
-               RWDEBUG("Ignoring rcode (%s)",
-                       fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
-               break;
-
-       default:
-               break;
-       }
-}
+#define SECTION_RCODE_IGNORED \
+do { \
+       switch (request->rcode) { \
+       case RLM_MODULE_USER_SECTION_REJECT: \
+               RWDEBUG("Ignoring rcode (%s)", \
+                       fr_table_str_by_value(rcode_table, request->rcode, "<invalid>")); \
+               break; \
+       default: \
+               break; \
+       } \
+} while(0)
 
 /** Trigger a state transition to FAILURE-NOTIFICATION if the section returned a failure code
  *
  */
-#define section_rcode_process(_p_result, _inst, _request, _eap_session, _eap_aka_sim_session) \
-{ \
-       if (after_authentication(_eap_aka_sim_session)) { \
-               switch ((_request)->rcode) { \
+#define SECTION_RCODE_PROCESS \
+do { \
+       if (after_authentication(eap_aka_sim_session)) { \
+               switch (unlang_interpret_stack_result(request)) { \
                case RLM_MODULE_REJECT:  \
                case RLM_MODULE_DISALLOW: \
                        eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED; \
-                       return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+                       return STATE_TRANSITION(common_failure_notification); \
                case RLM_MODULE_NOTFOUND: \
                        eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED; \
-                       return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+                       return STATE_TRANSITION(common_failure_notification); \
                case RLM_MODULE_INVALID: \
                case RLM_MODULE_FAIL: \
                        eap_aka_sim_session->failure_type = FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION;\
-                       return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+                       return STATE_TRANSITION(common_failure_notification); \
                default: \
                        break; \
                } \
        } else { \
-               switch ((_request)->rcode) { \
+               switch (unlang_interpret_stack_result(request)) { \
                case RLM_MODULE_USER_SECTION_REJECT: \
                        REDEBUG("Section rcode (%s) indicates we should reject the user", \
                                fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>")); \
-                       return common_failure_notification_enter(_p_result, _inst, _request, _eap_session); \
+                       return STATE_TRANSITION(common_failure_notification); \
                default: \
                        break; \
                } \
        } \
+} while(0)
+
+/** Print debugging information, and write new state to eap_aka_sim_session->state
+ *
+ */
+static inline CC_HINT(always_inline) void state_set(request_t *request,
+                                                   eap_aka_sim_session_t *eap_aka_sim_session,
+                                                   eap_aka_sim_state_t new_state)
+{
+       eap_aka_sim_state_t     old_state = eap_aka_sim_session->state;
+
+       if (new_state != old_state) {
+               RDEBUG2("Changed state %s -> %s",
+                       fr_table_str_by_value(aka_sim_state_table, (void *)old_state, "<unknown>"),
+                       fr_table_str_by_value(aka_sim_state_table, (void *)new_state, "<unknown>"));
+       } else {
+               RDEBUG2("Reentering state %s",
+                       fr_table_str_by_value(aka_sim_state_table, (void *)old_state, "<unknown>"));
+       }
+
+       eap_aka_sim_session->state = new_state;
+}
+#define STATE_SET(_new_state) state_set(request, eap_aka_sim_session, state_ ## _new_state)
+
+/** Determine if we're after authentication
+ *
+ */
+static inline CC_HINT(always_inline) bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session)
+{
+       return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success;
+}
+
+/** Print out the error the client returned
+ *
+ */
+static inline CC_HINT(always_inline) void client_error_debug(request_t *request)
+{
+       fr_pair_t *vp;
+
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_client_error_code);
+       if (!vp) {
+               REDEBUG("Peer has not supplied a AT_ERROR_CODE");
+       } else {
+               REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data);
+       }
 }
 
 /** Sync up what identity we're requesting with attributes in the reply
@@ -267,21 +339,6 @@ static void identity_hint_pairs_add(fr_aka_sim_id_type_t *type_p, fr_aka_sim_met
        if (method_p) *method_p = method;
 }
 
-/** Print out the error the client returned
- *
- */
-static inline void client_error_debug(request_t *request, fr_pair_list_t *from_peer)
-{
-       fr_pair_t *vp;
-
-       vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_client_error_code);
-       if (!vp) {
-               REDEBUG("Peer has not supplied a AT_ERROR_CODE");
-       } else {
-               REDEBUG("Peer rejected request with error: %i (%pV)", vp->vp_uint16, &vp->data);
-       }
-}
-
 /** Add an Identity Request attribute to the reply
  *
  * Verify the progression of identity requests is valid.
@@ -447,8 +504,8 @@ static int identity_to_permanent_identity(request_t *request, fr_pair_t *in, eap
 /** Set the crypto identity from a received identity
  *
  */
-static void identity_to_crypto_identity(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
-                                       uint8_t const *identity, size_t len)
+static void crypto_identity_set(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
+                               uint8_t const *identity, size_t len)
 {
        RDEBUG3("Setting cryptographic identity to \"%pV\"", fr_box_strvalue_len((char const *)identity, len));
 
@@ -458,21 +515,12 @@ static void identity_to_crypto_identity(request_t *request, eap_aka_sim_session_
 
 }
 
-/** Determine if we're after authentication
- *
- */
-static inline bool after_authentication(eap_aka_sim_session_t *eap_aka_sim_session)
-{
-       return eap_aka_sim_session->challenge_success || eap_aka_sim_session->reauthentication_success;
-}
-
 /** Resume after 'store session { ... }'
  *
  */
-static unlang_action_t session_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                           request_t *request, void *rctx)
+RESUME(store_session)
 {
-       aka_sim_state_enter_t   state_enter = (aka_sim_state_enter_t)rctx;
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
        switch (request->rcode) {
        /*
@@ -488,20 +536,17 @@ static unlang_action_t session_store_resume(rlm_rcode_t *p_result, module_ctx_t
 
        pair_delete_request(attr_eap_aka_sim_next_reauth_id);
 
-       return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+       return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
 }
 
 /** Resume after 'store pseudonym { ... }'
  *
+ * Stores session data if required.
  */
-static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, void *rctx)
+RESUME(store_pseudonym)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-       aka_sim_state_enter_t           state_enter = (aka_sim_state_enter_t)rctx;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
        fr_pair_t                       *vp;
        fr_pair_t                       *new;
 
@@ -521,6 +566,11 @@ static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_
 
        pair_delete_request(attr_eap_aka_sim_next_pseudonym);
 
+       /*
+        *      Generate fast-reauth data if we
+        *      find a next_reauth_id pair in the
+        *      reply list.
+        */
        vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_next_reauth_id);
        if (vp) {
                /*
@@ -608,17 +658,14 @@ static unlang_action_t pseudonym_store_resume(rlm_rcode_t *p_result, module_ctx_
                        vp->vp_uint16 = 0;
                }
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.store_session,
-                                                     RLM_MODULE_NOOP,
-                                                     session_store_resume,
-                                                     mod_signal,
-                                                     rctx);
+               return CALL_SECTION(store_session);
        }
 
+       /*
+        *      We didn't store any fast-reauth data
+        */
 done:
-       return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+       return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
 }
 
 /** Implements a set of states for storing pseudonym and fastauth identities
@@ -628,23 +675,24 @@ done:
  *
  * Call the appropriate sections to persist those values.
  *
- * @param[out] p_result                Result of calling the module.
- * @param[in] mctx             Module calling ctx.
- * @param[in] request          the current request.
- * @param[in] eap_session      the EAP session
- * @param[in] state_enter      state entry function for the
- *                             state to transition to *after* the current
- *                             state.
+ * @param[out] p_result                        Result of calling the module.
+ * @param[in] mctx                     Module calling ctx.
+ * @param[in] request                  the current request.
+ * @param[in] eap_aka_sim_session      the EAP session
+ * @param[in] next                     function to call after storing sessions and pseudonyms.
  */
 static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                  request_t *request, eap_session_t *eap_session,
-                                                  aka_sim_state_enter_t state_enter)
+                                                  request_t *request,
+                                                  eap_aka_sim_session_t *eap_aka_sim_session,
+                                                  eap_aka_sim_next_t next)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       fr_pair_t               *vp;
-       fr_pair_t               *new;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *vp;
+       fr_pair_t                       *new;
+
+       if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification);
+
+       eap_aka_sim_session->next = next;
 
        request->rcode = RLM_MODULE_NOOP;       /* Needed because we may call resume functions directly */
 
@@ -660,7 +708,7 @@ static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module
                                RWDEBUG("Found empty Pseudonym-Id, and told not to generate one.  "
                                        "Skipping store pseudonym { ... } section");
 
-                               return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter);
+                               return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session);
                        }
 
                        MEM(identity = talloc_array(vp, char, inst->ephemeral_id_length + 2));
@@ -689,48 +737,36 @@ static unlang_action_t session_and_pseudonym_store(rlm_rcode_t *p_result, module
 
                MEM(eap_aka_sim_session->pseudonym_sent = talloc_bstrndup(eap_aka_sim_session,
                                                                          vp->vp_strvalue, vp->vp_length));
-
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.store_pseudonym,
-                                                     RLM_MODULE_NOOP,
-                                                     pseudonym_store_resume,
-                                                     mod_signal,
-                                                     (void *)state_enter);
+               return CALL_SECTION(store_pseudonym);
        }
 
-       return pseudonym_store_resume(p_result, mctx, request, (void *)state_enter);
+       return resume_store_pseudonym(p_result, mctx, request, eap_aka_sim_session);
 }
 
 /** Resume after 'clear session { ... }'
  *
  */
-static unlang_action_t session_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                           request_t *request, void *rctx)
+RESUME(clear_session)
 {
-       aka_sim_state_enter_t   state_enter = (aka_sim_state_enter_t)rctx;
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
        pair_delete_request(attr_session_id);
 
-       return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+       return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
 }
 
 /** Resume after 'clear pseudonym { ... }'
  *
  */
-static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, void *rctx)
+RESUME(clear_pseudonym)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       aka_sim_state_enter_t   state_enter = (aka_sim_state_enter_t)rctx;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
        pair_delete_request(attr_eap_aka_sim_next_pseudonym);
 
        /*
-        *      Clear session
+        *      Clear session (if one was stored)
         */
        if (eap_aka_sim_session->fastauth_sent) {
                fr_pair_t *vp;
@@ -743,16 +779,10 @@ static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_
                                    talloc_array_length(eap_aka_sim_session->fastauth_sent) - 1, true);
                TALLOC_FREE(eap_aka_sim_session->fastauth_sent);
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.clear_session,
-                                                     RLM_MODULE_NOOP,
-                                                     session_clear_resume,
-                                                     mod_signal,
-                                                     rctx);
+               return CALL_SECTION(clear_session);
        }
 
-       return state_enter(p_result, mctx, request, eap_session_get(request->parent));
+       return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
 }
 
 /** Implements a set of states for clearing out pseudonym and fastauth identities
@@ -761,24 +791,27 @@ static unlang_action_t pseudonym_clear_resume(rlm_rcode_t *p_result, module_ctx_
  * any identities that were provided during those rounds, as the supplicant
  * will have discarded them.
  *
- * @param[out] p_result                Result of calling the module.
- * @param[in] mctx             module calling ctx.
- * @param[in] request          the current request.
- * @param[in] eap_session      the current EAP session
- * @param[in] state_enter      state entry function for the
- *                             state to transition to *after* the current
- *                             state.
+ * @param[out] p_result                        Result of calling the module.
+ * @param[in] mctx                     module calling ctx.
+ * @param[in] request                  the current request.
+ * @param[in] eap_aka_sim_session      the current EAP session
+ * @param[in] next                     function to call after clearing sessions and pseudonyms.
  */
 static unlang_action_t session_and_pseudonym_clear(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                  request_t *request, eap_session_t *eap_session,
-                                                  aka_sim_state_enter_t state_enter)
+                                                  request_t *request,
+                                                  eap_aka_sim_session_t *eap_aka_sim_session,
+                                                  eap_aka_sim_next_t next)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+       if (!fr_cond_assert(next)) return STATE_TRANSITION(common_failure_notification);
+
+       eap_aka_sim_session->next = next;
+
+       request->rcode = RLM_MODULE_NOOP;       /* Needed because we may call resume functions directly */
 
        /*
-        *      Clear out pseudonyms
+        *      Clear out pseudonyms (if we sent one)
         */
        if (eap_aka_sim_session->pseudonym_sent) {
                fr_pair_t *vp;
@@ -787,756 +820,908 @@ static unlang_action_t session_and_pseudonym_clear(rlm_rcode_t *p_result, module
                fr_value_box_bstrdup_buffer(vp, &vp->data, NULL, eap_aka_sim_session->pseudonym_sent, true);
                TALLOC_FREE(eap_aka_sim_session->pseudonym_sent);
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.clear_pseudonym,
-                                                     RLM_MODULE_NOOP,
-                                                     session_clear_resume,
-                                                     mod_signal,
-                                                     (void *)state_enter);
+               return CALL_SECTION(clear_pseudonym);
        }
 
-       return pseudonym_clear_resume(p_result, mctx, request, (void *)state_enter);
+       return resume_clear_pseudonym(p_result, mctx, request, eap_aka_sim_session);
 }
 
-/** Encode EAP-SIM/AKA['] attributes
+/** Export EAP-SIM/AKA['] attributes
  *
+ * Makes any internal data available as attributes in the response.
+ * This allows test frameworks and the encoder to access any data they need without
+ * needing to look at the eap_aka_session_t.
  */
-static int common_encode(request_t *request, eap_session_t *eap_session, uint16_t subtype,
-                        uint8_t const *hmac_extra, size_t hmac_extra_len)
+static void common_reply(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session, uint16_t subtype)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       fr_dcursor_t            cursor;
-       fr_dcursor_t            to_encode;
-       fr_pair_list_t          head;
-       fr_pair_t               *vp, *subtype_vp;
-       ssize_t                 ret;
-       fr_aka_sim_encode_ctx_t encode_ctx = {
-                                       .root = fr_dict_root(dict_eap_aka_sim),
-                                       .keys = &eap_aka_sim_session->keys,
-
-                                       .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-                                               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
-                                       .iv_included = false,
-
-                                       .hmac_md = eap_aka_sim_session->mac_md,
-                                       .eap_packet = eap_session->this_round->request,
-                                       .hmac_extra = hmac_extra,
-                                       .hmac_extra_len = hmac_extra_len
-                               };
-
-       fr_pair_list_init(&head);
+       fr_pair_t               *vp = NULL, *subtype_vp;
+
        /*
         *      Set the subtype to identity request
         */
        MEM(pair_update_reply(&subtype_vp, attr_eap_aka_sim_subtype) >= 0);
        subtype_vp->vp_uint16 = subtype;
 
-       /*
-        *      State what kind of request we're sending
-        */
-       if (RDEBUG_ENABLED2) {
-               switch (subtype) {
-               case FR_SUBTYPE_VALUE_SIM_START:
-               case FR_SUBTYPE_VALUE_AKA_IDENTITY:
-                       RDEBUG2("Sending EAP-Request/%pV (%s)", &subtype_vp->data,
-                               fr_table_str_by_value(fr_aka_sim_id_request_table,
-                                                     eap_aka_sim_session->id_req, "<INVALID>"));
-                       break;
-
-               default:
-                       RDEBUG2("Sending EAP-Request/%pV", &subtype_vp->data);
-                       break;
+       if (!eap_aka_sim_session->allow_encrypted) {
+               while ((vp = fr_pair_list_next(&request->reply_pairs, vp))) {
+                       if (fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) {
+                               RWDEBUG("Silently discarding &reply.%pP: Encrypted attributes not "
+                                       "allowed in this round", vp);
+                               vp = fr_pair_list_free_item(&request->reply_pairs, vp);
+                               continue;
+                       }
                }
        }
+}
 
-       fr_dcursor_init(&cursor, &request->reply_pairs);
-       fr_dcursor_init(&to_encode, &head);
+static void CC_HINT(nonnull(1,2))
+common_crypto_export(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session,
+                    uint8_t const *hmac_extra_request, size_t hmac_extra_request_len,
+                    uint8_t const *hmac_extra_response, size_t hmac_extra_response_len)
+{
+       fr_pair_t *vp;
 
        /*
-        *      We have to be *SUPER* careful here not to reorder
-        *      attributes, because for EAP-SIM the RAND values
-        *      must be inserted into the packet in exactly the
-        *      same order as they appear in fr_sim_keys_t
-        *      otherwise the KDF will produce a different
-        *      result.
+        *      Export keying material necessary for
+        *      the encoder to encrypt and sign
+        *      packets.
         */
-       while ((vp = fr_dcursor_current(&cursor))) {
-               if (!fr_dict_attr_common_parent(encode_ctx.root, vp->da, true)) {
-                       fr_dcursor_next(&cursor);
-                       continue;
-               }
-               vp = fr_dcursor_remove(&cursor);
-
-               /*
-                *      Silently discard encrypted attributes until
-                *      the peer should have k_encr.  These can be
-                *      added by policy, and seem to cause
-                *      wpa_supplicant to fail if sent before the challenge.
-                */
-               if (!eap_aka_sim_session->allow_encrypted &&
-                   fr_dict_attr_common_parent(attr_eap_aka_sim_encr_data, vp->da, true)) {
-                       RWDEBUG("Silently discarding &reply.%s: Encrypted attributes not allowed in this round",
-                               vp->da->name);
-                       talloc_free(vp);
-                       continue;
-               }
-
-               fr_dcursor_prepend(&to_encode, vp);
+       if (hmac_extra_request && hmac_extra_request_len) {
+               MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_request) >= 0);
+               MEM(fr_pair_value_memdup(vp, hmac_extra_request, hmac_extra_request_len, true) == 0);
        }
 
+       if (hmac_extra_response && hmac_extra_response_len) {
+               MEM(pair_update_control(&vp, attr_eap_aka_sim_hmac_extra_response) >= 0);
+               MEM(fr_pair_value_memdup(vp, hmac_extra_response, hmac_extra_response_len, true) == 0);
+       }
 
-       RDEBUG2("Encoding attributes");
-       log_request_pair_list(L_DBG_LVL_2, request, NULL, &head, NULL);
-
-       eap_session->this_round->request->type.num = eap_aka_sim_session->type;
-       eap_session->this_round->request->id = eap_aka_sim_session->id++ & 0xff;
-       eap_session->this_round->set_request_id = true;
-
-       ret = fr_aka_sim_encode(request, &head, &encode_ctx);
-       fr_dcursor_head(&to_encode);
-       fr_dcursor_free_list(&to_encode);
+       MEM(pair_update_control(&vp, attr_eap_aka_sim_k_aut) >= 0);
+       MEM(fr_pair_value_memdup(vp,
+                                eap_aka_sim_session->keys.k_aut,
+                                eap_aka_sim_session->keys.k_aut_len,
+                                true) == 0);
 
-       if (ret < 0) {
-               RPEDEBUG("Failed encoding attributes");
-               return -1;
-       }
-       return 0;
+       MEM(pair_update_control(&vp, attr_eap_aka_sim_k_encr) >= 0);
+       MEM(fr_pair_value_memdup(vp,
+                                eap_aka_sim_session->keys.k_encr,
+                                sizeof(eap_aka_sim_session->keys.k_encr),
+                                true) == 0);
 }
 
-/** Send a EAP-Failure message to the supplicant
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
  *
  */
-static unlang_action_t common_eap_failure_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session)
+static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+                                                           request_t *request,
+                                                           eap_aka_sim_session_t *eap_aka_sim_session)
 {
-       RDEBUG2("Sending EAP-Failure");
+       /*
+        *      Return reply attributes - AT_IV is handled automatically by the encoder
+        */
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION);
 
-       eap_session->this_round->request->code = FR_EAP_CODE_FAILURE;
-       eap_session->finished = true;
+       /*
+        *      9.5.  EAP-Request/SIM/Re-authentication
+        *
+        *      AT_MAC MUST be included.  No message-specific data is included in the
+        *      MAC calculation.  See Section 10.14.
+        *
+        *      9.6.  EAP-Response/SIM/Re-authentication
+        *
+        *      The AT_MAC attribute MUST be included.  For
+        *      EAP-Response/SIM/Re-authentication, the MAC code is calculated over
+        *      the following data: EAP packet| NONCE_S
+        *
+        *      9.7.  EAP-Request/AKA-Reauthentication
+        *
+        *      The AT_MAC attribute MUST be included.  No message-specific data is
+        *      included in the MAC calculation, see Section 10.15.
+        *
+        *      9.8.  EAP-Response/AKA-Reauthentication
+        *
+        *      The AT_MAC attribute MUST be included.  For
+        *      EAP-Response/AKA-Reauthentication, the MAC code is calculated over
+        *      the following data:  EAP packet| NONCE_S.
+        */
+       common_crypto_export(request, eap_aka_sim_session,
+                            NULL, 0,
+                            eap_aka_sim_session->keys.reauth.nonce_s,
+                            sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
 
-       RETURN_MODULE_REJECT;
+       RETURN_MODULE_HANDLED;
 }
 
-/** Send a EAP-Request/(AKA|SIM)-Notification (Failure) message to the supplicant
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
  *
  */
-static unlang_action_t common_failure_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                       request_t *request, eap_session_t *eap_session)
+static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+                                                 request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
 {
-       fr_pair_t               *vp, *notification_vp;
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
        /*
-        *      Allow the user to specify specific failure notification
-        *      types.  We assume the user knows what they're doing and
-        *      only toggle success and phase bits.
-        *
-        *      This allows custom notification schemes to be implemented.
-        *
-        *      If this is prior to authentication, valid values are:
-        *      - FR_NOTIFICATION_VALUE_GENERAL_FAILURE
-        *
-        *      If this is after authentication, valid values are:
-        *      - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION
-        *      - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been
-        *        temporarily denied access to the requested service.
-        *      - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED
-        *        User has not subscribed to the requested service.
+        *      Encode the packet - AT_IV is handled automatically
+        *      by the encoder.
         */
-       notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification);
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE);
 
        /*
-        *      Change the failure notification depending where
-        *      we are in the eap_aka_state machine.
+        *      9.3.  EAP-Request/AKA-Challenge
+        *
+        *      AT_MAC MUST be included.  In EAP-Request/AKA-Challenge, there is no
+        *      message-specific data covered by the MAC, see Section 10.15.
+        *
+        *      9.4.  EAP-Response/AKA-Challenge
+        *
+        *      The AT_MAC attribute MUST be included.  In
+        *      EAP-Response/AKA-Challenge, there is no message-specific data covered
+        *      by the MAC, see Section 10.15.
         */
-       if (after_authentication(eap_aka_sim_session)) {
-               if (!notification_vp) {
-                       MEM(pair_append_reply(&notification_vp, attr_eap_aka_sim_notification) >= 0);
-                       notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */
-               }
+       common_crypto_export(request, eap_aka_sim_session, NULL, 0, NULL, 0);
 
-               notification_vp->vp_uint16 &= ~0x4000;  /* Unset phase bit */
+       RETURN_MODULE_HANDLED;
+}
 
-               /*
-                *      Include the counter attribute if we're failing
-                *      after a reauthentication success.
-                *
-                *      RFC 4187 Section #9.10
-                *
-                *      If EAP-Request/AKA-Notification is used on
-                *      a fast re-authentication exchange, and if
-                *      the P bit in AT_NOTIFICATION is set to zero,
-                *      then AT_COUNTER is used for replay protection.
-                *      In this case, the AT_ENCR_DATA and AT_IV
-                *      attributes MUST be included, and the
-                *      encapsulated plaintext attributes MUST include
-                *      the AT_COUNTER attribute.  The counter value
-                *      included in AT_COUNTER MUST be the same
-                *      as in the EAP-Request/AKA-Reauthentication
-                *      packet on the same fast re-authentication
-                *      exchange.
-                *
-                *      If the counter is used it should never be zero,
-                *      as it's incremented on first reauthentication
-                *      request.
-                */
-               if (eap_aka_sim_session->reauthentication_success) {
-                       MEM(pair_update_reply(&notification_vp, attr_eap_aka_sim_counter) >= 0);
-                       notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
-               }
+/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+ *
+ */
+static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
+                                                 request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
+{
+       uint8_t                 sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
+       uint8_t                 *p = sres_cat;
 
-               /*
-                *      If we're after the challenge phase
-                *      then we need to include a MAC to
-                *      protect notifications.
-                */
-               MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
-               fr_pair_value_memdup(vp, NULL, 0, false);
-       } else {
-               /*
-                *      Only valid code is general failure
-                */
-               if (!notification_vp) {
-                       MEM(pair_append_reply(&notification_vp, attr_eap_aka_sim_notification) >= 0);
-                       notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE;
-               /*
-                *      User supplied failure code
-                */
-               } else {
-                       notification_vp->vp_uint16 |= 0x4000;   /* Set phase bit */
-               }
-       }
-       notification_vp->vp_uint16 &= ~0x8000;          /* In both cases success bit should be low */
+       /*
+        *      Encode the packet - AT_IV is handled automatically
+        *      by the encoder.
+        */
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE);
 
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
 
        /*
-        *      Encode the packet
+        *      9.3.  EAP-Request/SIM/Challenge
+        *
+        *      The AT_MAC attribute MUST be included.  For
+        *      EAP-Request/SIM/Challenge, the MAC code is calculated over the
+        *      following data: EAP packet| NONCE_MT
+        *
+        *      9.4.  EAP-Response/SIM/Challenge
+        *
+        *      The AT_MAC attribute MUST be included.  For EAP-
+        *      Response/SIM/Challenge, the MAC code is calculated over the following
+        *      data: EAP packet| n*SRES
         */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) {
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
+       common_crypto_export(request, eap_aka_sim_session,
+                            eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt),
+                            sres_cat, sizeof(sres_cat));
 
        RETURN_MODULE_HANDLED;
 }
 
-/** Add MPPE keys to the request being sent to the supplicant
+/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure
  *
- * The only work to be done is the add the appropriate SEND/RECV
- * attributes derived from the MSK.
+ * Also checks the version matches one of the ones we advertised in our version list,
+ * which is a bit redundant seeing as there's only one version of EAP-SIM.
  */
-static unlang_action_t common_eap_success_send(rlm_rcode_t *p_result, request_t *request, eap_session_t *eap_session)
+static int sim_start_selected_version_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
 {
-       uint8_t                 *p;
-       eap_aka_sim_session_t   *eap_aka_sim_session;
-
-       RDEBUG2("Sending EAP-Success");
-
-       eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS;
-       eap_session->finished = true;
+       fr_pair_t               *selected_version_vp;
 
-       eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
+       /*
+        *      Check that we got an AT_SELECTED_VERSION
+        */
+       selected_version_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version);
+       if (!selected_version_vp) {
+               REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION");
+               return -1;
+       }
 
-       RDEBUG2("Adding attributes for MSK");
-       p = eap_aka_sim_session->keys.msk;
-       eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN);
-       p += EAP_TLS_MPPE_KEY_LEN;
-       eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN);
+       /*
+        *      See if the selected version was in our list
+        */
+       {
+               uint8_t selected_version[2];
+               uint8_t *p, *end;
+               bool found = false;
 
-       RETURN_MODULE_OK;
+               p = eap_aka_sim_session->keys.gsm.version_list;
+               end = p + eap_aka_sim_session->keys.gsm.version_list_len;
+
+               selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8;
+               selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff);
+
+               while (p < end) {
+                       if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) {
+                               found = true;
+                               /*
+                                *      Update our keying material
+                                */
+                               eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0];
+                               eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1];
+                               break;
+                       }
+               }
+
+               if (!found) {
+                       REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list",
+                               selected_version_vp->vp_uint16);
+                       return -1;
+               }
+       }
+
+       return 0;
 }
 
-/** Send a EAP-Request/(AKA|SIM)-Notification (Success) message to the supplicant
+/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure
  *
+ * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later.
  */
-static unlang_action_t common_success_notification_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                       request_t *request, eap_session_t *eap_session)
+static int sim_start_nonce_mt_check(request_t *request, eap_aka_sim_session_t *eap_aka_sim_session)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-       fr_pair_t               *vp;
-
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
-
-       if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) {
-               RETURN_MODULE_FAIL;
-       }
+       fr_pair_t       *nonce_mt_vp;
 
        /*
-        *      If we're in this state success bit is
-        *      high phase bit is low.
+        *      Copy nonce_mt to the keying material
         */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0);
-       vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS;
+       nonce_mt_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt);
+       if (!nonce_mt_vp) {
+               REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT");
+               return -1;
+       }
 
-       /*
-        *      RFC 4187 section #9.10
-        *
-        *      If EAP-Request/AKA-Notification is used on
-        *      a fast re-authentication exchange, and if
-        *      the P bit in AT_NOTIFICATION is set to zero,
-        *      then AT_COUNTER is used for replay protection.
-        *      In this case, the AT_ENCR_DATA and AT_IV
-        *      attributes MUST be included, and the
-        *      encapsulated plaintext attributes MUST include
-        *      the AT_COUNTER attribute.  The counter value
-        *      included in AT_COUNTER MUST be the same
-        *      as in the EAP-Request/AKA-Reauthentication
-        *      packet on the same fast re-authentication
-        *      exchange.
-        *
-        *      If the counter is used it should never be zero,
-        *      as it's incremented on first reauthentication
-        *      request.
-        */
-       if (eap_aka_sim_session->keys.reauth.counter > 0) {
-               MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
-               vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+       if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) {
+               REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes",
+                       sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length);
+               return -1;
        }
+       memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets,
+              sizeof(eap_aka_sim_session->keys.gsm.nonce_mt));
 
-       /*
-        *      Need to include an AT_MAC attribute so that
-        *      it will get calculated.
-        */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
-       fr_pair_value_memdup(vp, NULL, 0, false);
+       return 0;
+}
 
-       /*
-        *      Encode the packet
-        */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION, NULL, 0) < 0) {
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
+/** FAILURE state - State machine exit point after sending EAP-Failure
+ *
+ * Should never actually be called. Is just a placeholder function to represent the FAILURE
+ * termination state.  Could equally be a NULL pointer, but then on a logic error
+ * we'd get a SEGV instead of a more friendly assert/failure rcode.
+ */
+STATE(eap_failure)
+{
+       if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL;        /* unused args */
 
-       RETURN_MODULE_HANDLED;
+       fr_assert(0);   /* Should never actually be called */
+
+       RETURN_MODULE_FAIL;
 }
 
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** Resume after 'send EAP-Failure { ... }'
  *
  */
-static unlang_action_t common_reauthentication_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                           request_t *request, eap_session_t *eap_session)
+RESUME(send_eap_failure)
 {
-       /*
-        *      Encode the packet - AT_IV is handled automatically
-        *      by the encoder.
-        */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION, NULL, 0) < 0) {
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
+       if (!fr_cond_assert(mctx && rctx)) RETURN_MODULE_FAIL;  /* unused args */
 
-       RETURN_MODULE_HANDLED;
+       SECTION_RCODE_IGNORED;
+
+       RDEBUG2("Sending EAP-Failure");
+
+       RETURN_MODULE_REJECT;
 }
 
-/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant
+/** Enter EAP-FAILURE state
  *
  */
-static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result,
-                                                              module_ctx_t const *mctx,
-                                                              request_t *request,
-                                                              eap_session_t *eap_session)
+STATE_GUARD(eap_failure)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       fr_pair_list_t          *to_peer = &request->reply_pairs;
-       fr_pair_t               *vp;
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
 
-       fr_pair_t               *kdf_id;
+       /*
+        *      Free anything we were going to send out...
+        */
+       fr_pair_list_free(&request->reply_pairs);
 
        /*
-        *      Allow override of KDF Identity
-        *
-        *      Because certain handset manufacturers don't
-        *      implement RFC 4187 correctly and use the
-        *      wrong identity as input the the PRF/KDF.
-        *
-        *      Not seen any doing this for re-authentication
-        *      but you never know...
+        *      If we're failing, then any identities
+        *      we sent are now invalid.
         */
-       kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
-       if (kdf_id) {
-               identity_to_crypto_identity(request, eap_aka_sim_session,
-                                           (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
-               fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+               return session_and_pseudonym_clear(p_result, mctx,
+                                                  request, eap_aka_sim_session, guard_eap_failure);
+                                                  /* come back when we're done */
        }
 
-       RDEBUG2("Generating new session keys");
+       STATE_SET(eap_failure);
+
+       return CALL_SECTION(send_eap_failure);
+}
+
+/** Resume after 'recv Failure-Notification-Ack { ... }'
+ *
+ * - Enter the EAP-FAILURE state.
+ */
+RESUME(recv_common_failure_notification_ack)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+       SECTION_RCODE_IGNORED;
 
-       switch (eap_aka_sim_session->type) {
        /*
-        *      The GSM and UMTS KDF_0 mutate their keys using
-        *      and identical algorithm.
+        *      Case 2 where we're allowed to send an EAP-Failure
         */
-       case FR_EAP_METHOD_SIM:
-       case FR_EAP_METHOD_AKA:
-               if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs,
-                                                                      &eap_aka_sim_session->keys) != 0) {
-               request_new_id:
-                       switch (eap_aka_sim_session->last_id_req) {
-                       /*
-                        *      Got here processing EAP-Identity-Response
-                        *      If this is the *true* reauth ID, then
-                        *      there's no point in setting AKA_SIM_ANY_ID_REQ.
-                        */
-                       case AKA_SIM_NO_ID_REQ:
-                       case AKA_SIM_ANY_ID_REQ:
-                               RDEBUG2("Composing EAP-Request/Reauthentication failed.  Clearing reply attributes and "
-                                       "requesting additional Identity");
-                               fr_pair_list_free(&request->reply_pairs);
-                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
-                               return common_identity_enter(p_result, mctx, request, eap_session);
+       return STATE_TRANSITION(eap_failure);
+}
 
-                       case AKA_SIM_FULLAUTH_ID_REQ:
-                       case AKA_SIM_PERMANENT_ID_REQ:
-                               REDEBUG("Last requested fullauth or permanent ID, "
-                                       "but received, or were told we received (by policy), "
-                                       "a fastauth ID.  Cannot continue");
-                       failure:
-                               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-                       }
-               }
-               if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
-               break;
+/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ *
+ * - Continue based on received AT_SUBTYPE value:
+ *   - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }'
+ *   - Anything else, enter the FAILURE-NOTIFICATION state.
+ */
+STATE(common_failure_notification)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *subtype_vp = NULL;
 
-       case FR_EAP_METHOD_AKA_PRIME:
-               switch (eap_aka_sim_session->kdf) {
-               case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
-                       if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs,
-                                                                          &eap_aka_sim_session->keys) != 0) {
-                               goto request_new_id;
-                       }
-                       if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
-                       break;
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+       if (!subtype_vp) goto fail;
 
-               default:
-                       fr_assert(0);
-                       break;
-               }
-               break;
+       switch (subtype_vp->vp_uint16) {
+       case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION:
+               RDEBUG2("Failure-Notification ACKed, sending EAP-Failure");
+               return CALL_SECTION(recv_common_failure_notification_ack);
 
        default:
-               fr_assert(0);
-               break;
+       fail:
+               RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway");
+               return STATE_TRANSITION(eap_failure)
        }
+}
 
-       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+/** Resume after 'send Failure-Notification { ... }'
+ *
+ * Ignores return code from send Failure-Notification { ... } section.
+ */
+RESUME(send_common_failure_notification)
+{
+       fr_pair_t               *vp, *notification_vp;
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx,
+                                                                            eap_aka_sim_session_t);
 
-       /*
-        *      Add AT_IV, AT_COUNTER, AT_NONCE_S, and AT_MAC to to reply
-        *      The user may have added AT_NEXT_REAUTH_ID, in which case
-        *      we'll have sent that too.
-        */
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       if (!fr_cond_assert(mctx)) RETURN_MODULE_FAIL;  /* quiet unused warning */
 
-       /*
-        *      Indicate we'd like to use protected success messages
-        *      with AT_RESULT_IND
-        *
-        *      Use our default, but allow user override too.
-        */
-       vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
-       if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
+       SECTION_RCODE_IGNORED;
 
        /*
-        *      RFC 5448 says AT_BIDDING is only sent in the challenge
-        *      not in reauthentication, so don't add that here.
+        *      Free anything we were going to send out...
         */
-
-        /*
-         *     Add AT_NONCE_S
-         */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s,
-                            sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false);
+       fr_pair_list_free(&request->reply_pairs);
 
        /*
-        *      Add AT_COUNTER
-        */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
-       vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
-
-       /*
-        *      need to include an empty AT_MAC attribute so that
-        *      the mac will get calculated.
+        *      Allow the user to specify specific failure notification
+        *      types.  We assume the user knows what they're doing and
+        *      only toggle success and phase bits.
+        *
+        *      This allows custom notification schemes to be implemented.
+        *
+        *      If this is prior to authentication, valid values are:
+        *      - FR_NOTIFICATION_VALUE_GENERAL_FAILURE
+        *
+        *      If this is after authentication, valid values are:
+        *      - FR_NOTIFICATION_VALUE_GENERAL_FAILURE_AFTER_AUTHENTICATION
+        *      - FR_NOTIFICATION_VALUE_TEMPORARILY_DENIED - User has been
+        *        temporarily denied access to the requested service.
+        *      - FR_NOTIFICATION_VALUE_NOT_SUBSCRIBED
+        *        User has not subscribed to the requested service.
         */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
-       fr_pair_value_memdup(vp, NULL, 0, false);
+       notification_vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_notification);
 
        /*
-        *      If there's no checkcode_md we're not doing
-        *      checkcodes.
+        *      Change the failure notification depending where
+        *      we are in the eap_aka_state machine.
         */
-       if (eap_aka_sim_session->checkcode_md) {
+       if (after_authentication(eap_aka_sim_session)) {
+               if (!notification_vp) {
+                       MEM(pair_append_reply(&notification_vp, attr_eap_aka_sim_notification) >= 0);
+                       notification_vp->vp_uint16 = eap_aka_sim_session->failure_type; /* Default will be zero */
+               }
+
+               notification_vp->vp_uint16 &= ~0x4000;  /* Unset phase bit */
+
                /*
-                *      If we have checkcode data, send that to the peer
-                *      in AT_CHECKCODE for validation.
+                *      Include the counter attribute if we're failing
+                *      after a reauthentication success.
+                *
+                *      RFC 4187 Section #9.10
+                *
+                *      If EAP-Request/AKA-Notification is used on
+                *      a fast re-authentication exchange, and if
+                *      the P bit in AT_NOTIFICATION is set to zero,
+                *      then AT_COUNTER is used for replay protection.
+                *      In this case, the AT_ENCR_DATA and AT_IV
+                *      attributes MUST be included, and the
+                *      encapsulated plaintext attributes MUST include
+                *      the AT_COUNTER attribute.  The counter value
+                *      included in AT_COUNTER MUST be the same
+                *      as in the EAP-Request/AKA-Reauthentication
+                *      packet on the same fast re-authentication
+                *      exchange.
+                *
+                *      If the counter is used it should never be zero,
+                *      as it's incremented on first reauthentication
+                *      request.
                 */
-               if (eap_aka_sim_session->checkcode_state) {
-                       ssize_t slen;
-
-                       slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
-                                                                   &eap_aka_sim_session->checkcode_state);
-                       if (slen < 0) {
-                               RPEDEBUG("Failed calculating checkcode");
-                               goto failure;
-                       }
-                       eap_aka_sim_session->checkcode_len = slen;
+               if (eap_aka_sim_session->reauthentication_success) {
+                       MEM(pair_update_reply(&notification_vp, attr_eap_aka_sim_counter) >= 0);
+                       notification_vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+               }
 
-                       MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
-                       fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
                /*
-                *      If we don't have checkcode data, then we exchanged
-                *      no identity packets, so checkcode is zero.
+                *      If we're after the challenge phase
+                *      then we need to include a MAC to
+                *      protect notifications.
+                */
+               MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+               fr_pair_value_memdup(vp, NULL, 0, false);
+       } else {
+               /*
+                *      Only valid code is general failure
+                */
+               if (!notification_vp) {
+                       MEM(pair_append_reply(&notification_vp, attr_eap_aka_sim_notification) >= 0);
+                       notification_vp->vp_uint16 = FR_NOTIFICATION_VALUE_GENERAL_FAILURE;
+               /*
+                *      User supplied failure code
                 */
                } else {
-                       MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
-                       fr_pair_value_memdup(vp, NULL, 0, false);
-                       eap_aka_sim_session->checkcode_len = 0;
+                       notification_vp->vp_uint16 |= 0x4000;   /* Set phase bit */
                }
        }
+       notification_vp->vp_uint16 &= ~0x8000;          /* In both cases success bit should be low */
 
        /*
-        *      We've sent the challenge so the peer should now be able
-        *      to accept encrypted attributes.
+        *      Send a response
         */
-       eap_aka_sim_session->allow_encrypted = true;
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION);
 
-       return session_and_pseudonym_store(p_result, mctx,request, eap_session, common_reauthentication_request_send);
+       RETURN_MODULE_HANDLED;
 }
 
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** Enter the FAILURE-NOTIFICATION state
  *
  */
-static unlang_action_t aka_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, eap_session_t *eap_session)
+STATE_GUARD(common_failure_notification)
 {
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
        /*
-        *      Encode the packet - AT_IV is handled automatically
-        *      by the encoder.
+        *      If we're failing, then any identities
+        *      we sent are now invalid.
         */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_CHALLENGE, NULL, 0) < 0) {
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+               return session_and_pseudonym_clear(p_result, mctx, request, eap_aka_sim_session,
+                                                  guard_common_failure_notification); /* come back when we're done */
        }
 
-       RETURN_MODULE_HANDLED;
+       /*
+        *      We've already sent a failure notification
+        *      Now we just fail as it means something
+        *      went wrong processing the ACK or we got
+        *      garbage from the supplicant.
+        */
+       if (eap_aka_sim_session->state == state_common_failure_notification) {
+               return STATE_TRANSITION(eap_failure);
+       }
+
+       /*
+        *      Otherwise just transition as normal...
+        */
+       STATE_SET(common_failure_notification);
+
+       return CALL_SECTION(send_common_failure_notification);
 }
 
-/** Send a EAP-Request/AKA-Challenge message to the supplicant
+/** SUCCESS state - State machine exit point after sending EAP-Success
  *
+ * Should never actually be called. Is just a placeholder function to represent the SUCCESS
+ * termination state.  Could equally be a NULL pointer, but then on a logic error
+ * we'd get a SEGV instead of a more friendly assert/failure rcode.
  */
-static unlang_action_t aka_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                    request_t *request, eap_session_t *eap_session)
+STATE(eap_success)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-       fr_pair_list_t          *to_peer = &request->reply_pairs;
-       fr_pair_t               *vp;
-       fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
+       if (!fr_cond_assert(request && mctx && eap_aka_sim_session)) RETURN_MODULE_FAIL;        /* unused args */
 
-       fr_pair_t               *kdf_id;
+       fr_assert(0);   /* Should never actually be called */
+
+       RETURN_MODULE_FAIL;
+}
+
+/** Resume after 'send EAP-Success { ... }'
+ *
+ * Add MPPE keys to the request being sent to the supplicant
+ *
+ * The only work to be done is the add the appropriate SEND/RECV
+ * attributes derived from the MSK.
+ */
+RESUME(send_eap_success)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       uint8_t                 *p;
+
+       RDEBUG2("Sending EAP-Success");
 
        /*
-        *      Allow override of KDF Identity
+        *      If this is true we're entering this state
+        *      after sending a AKA-Success-Notification
         *
-        *      Because certain handset manufacturers don't
-        *      implement RFC 4187 correctly and use the
-        *      wrong identity as input the the PRF/KDF.
+        *      Is seems like a really bad idea to allow the
+        *      user to send a protected success to the
+        *      supplicant and then force a failure using
+        *      the send EAP-Success { ... } section.
         */
-       kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
-       if (kdf_id) {
-               identity_to_crypto_identity(request, eap_aka_sim_session,
-                                           (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
-               fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
-       }
-
-       RDEBUG2("Acquiring UMTS vector(s)");
+       if (eap_aka_sim_session->send_result_ind) {
+               switch (request->rcode) {
+               case RLM_MODULE_USER_SECTION_REJECT:
+                       RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } "
+                               "as we already sent a Success-Notification",
+                               fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
+                       RWDEBUG("If you need to force a failure, return an error code from "
+                               "send Success-Notification { ... }");
+                       break;
 
-       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) {
-               /*
-                *      Copy the network name the user specified for
-                *      key derivation purposes.
-                */
-               vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_kdf_input);
-               if (vp) {
-                       talloc_free(eap_aka_sim_session->keys.network);
-                       eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session,
-                                                                         (uint8_t const *)vp->vp_strvalue,
-                                                                         vp->vp_length);
-                       eap_aka_sim_session->keys.network_len = vp->vp_length;
-               } else {
-                       REDEBUG("No network name available, can't set AT_KDF_INPUT");
-               failure:
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
+               default:
+                       break;
                }
 
-               /*
-                *      We don't allow the user to specify
-                *      the KDF currently.
-                */
-               MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0);
-               vp->vp_uint16 = eap_aka_sim_session->kdf;
-       }
-
        /*
-        *      Get vectors from attribute or generate
-        *      them using COMP128-* or Milenage.
+        *      But... if we're not working with protected
+        *      success indication, this is the only
+        *      opportunity the user has to force a failure at
+        *      the end of authentication.
         */
-       if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs, &eap_aka_sim_session->keys, &src) != 0) {
-               REDEBUG("Failed retrieving UMTS vectors");
-               goto failure;
+       } else {
+               SECTION_RCODE_PROCESS;
        }
 
-       /*
-        *      Don't leave the AMF hanging around
-        */
-       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf);
+       RDEBUG2("Adding attributes for MSK");
+       p = eap_aka_sim_session->keys.msk;
+       eap_add_reply(request->parent, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN);
+       p += EAP_TLS_MPPE_KEY_LEN;
+       eap_add_reply(request->parent, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN);
 
-       /*
-        *      All set, calculate keys!
-        */
-       switch (eap_aka_sim_session->type) {
-       default:
-       case FR_EAP_METHOD_SIM:
-               fr_assert(0);   /* EAP-SIM has its own Challenge state */
-               break;
+       RETURN_MODULE_OK;
+}
 
-       case FR_EAP_METHOD_AKA:
-               fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys);
-               break;
+/** Enter EAP-SUCCESS state
+ *
+ */
+STATE_GUARD(eap_success)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
 
-       case FR_EAP_METHOD_AKA_PRIME:
-               switch (eap_aka_sim_session->kdf) {
-               case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
-                       fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys);
-                       break;
+       STATE_SET(eap_success);
 
-               default:
-                       fr_assert(0);
-                       break;
-               }
-       }
-       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+       return CALL_SECTION(send_eap_success);
+}
 
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+/** Resume after 'recv Success-Notification-Ack { ... }'
+ *
+ * - Enter the EAP-SUCCESS state.
+ */
+RESUME(recv_common_success_notification_ack)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
-       /*
-        *      Indicate we'd like to use protected success messages
-        *      with AT_RESULT_IND
-        *
-        *      Use our default, but allow user override too.
-        */
-       vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
-       if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
+       SECTION_RCODE_IGNORED;
 
        /*
-        *      See if we're indicating we want EAP-AKA'
-        *      If so include AT_BIDDING with the correct
-        *      value.
+        *      RFC 4187 says we ignore the contents of the
+        *      next packet after we send our success notification
+        *      and always send a success.
         */
-       vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_bidding);
-       if (vp) {
-               eap_aka_sim_session->send_at_bidding_prefer_prime =
-                       (vp->vp_uint16 == FR_BIDDING_VALUE_PREFER_AKA_PRIME);
-       }
+       return STATE_TRANSITION(eap_success);
+}
+
+/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ *
+ * - Call 'recv Success-Notification-Ack { ... }'
+ */
+STATE(common_success_notification)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
 
        /*
-        *      These attributes are only allowed with
-        *      EAP-AKA', protect users from themselves.
-        */
-       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) {
-               pair_delete_reply(attr_eap_aka_sim_kdf_input);
-               pair_delete_reply(attr_eap_aka_sim_kdf);
-       }
+        *      Because the server uses the AT_NOTIFICATION code "Success" (32768) to
+        *      indicate that the EAP exchange has completed successfully, the EAP
+        *      exchange cannot fail when the server processes the EAP-AKA response
+        *      to this notification.  Hence, the server MUST ignore the contents of
+        *      the EAP-AKA response it receives to the EAP-Request/AKA-Notification
+        *      with this code.  Regardless of the contents of the EAP-AKA response,
+        *      the server MUST send EAP-Success as the next packet.
+        */
+       return CALL_SECTION(recv_common_success_notification_ack);
+}
+
+/** Resume after 'send Success-Notification { ... }'
+ *
+ */
+RESUME(send_common_success_notification)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       fr_pair_t               *vp;
+
+       SECTION_RCODE_PROCESS;
+
+       if (!fr_cond_assert(after_authentication(eap_aka_sim_session))) RETURN_MODULE_FAIL;
 
        /*
-        *      Okay, we got the challenge! Put it into an attribute.
+        *      If we're in this state success bit is
+        *      high phase bit is low.
         */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false);
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_notification) >= 0);
+       vp->vp_uint16 = FR_NOTIFICATION_VALUE_SUCCESS;
 
        /*
-        *      Send the AUTN value to the client, so it can authenticate
-        *      whoever has knowledge of the Ki.
+        *      RFC 4187 section #9.10
+        *
+        *      If EAP-Request/AKA-Notification is used on
+        *      a fast re-authentication exchange, and if
+        *      the P bit in AT_NOTIFICATION is set to zero,
+        *      then AT_COUNTER is used for replay protection.
+        *      In this case, the AT_ENCR_DATA and AT_IV
+        *      attributes MUST be included, and the
+        *      encapsulated plaintext attributes MUST include
+        *      the AT_COUNTER attribute.  The counter value
+        *      included in AT_COUNTER MUST be the same
+        *      as in the EAP-Request/AKA-Reauthentication
+        *      packet on the same fast re-authentication
+        *      exchange.
+        *
+        *      If the counter is used it should never be zero,
+        *      as it's incremented on first reauthentication
+        *      request.
         */
-       MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false);
+       if (eap_aka_sim_session->keys.reauth.counter > 0) {
+               MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
+               vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
+       }
 
        /*
-        *      need to include an AT_MAC attribute so that it will get
-        *      calculated.
+        *      Need to include an AT_MAC attribute so that
+        *      it will get calculated.
         */
        MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
        fr_pair_value_memdup(vp, NULL, 0, false);
 
        /*
-        *      If we have checkcode data, send that to the peer
-        *      in AT_CHECKCODE for validation.
+        *      Return reply attributes
         */
-       if (eap_aka_sim_session->checkcode_state) {
-               ssize_t slen;
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION);
 
-               slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
-                                                           &eap_aka_sim_session->checkcode_state);
-               if (slen < 0) {
-                       RPEDEBUG("Failed calculating checkcode");
+       RETURN_MODULE_HANDLED;
+}
+
+/** Enter the SUCCESS-NOTIFICATION state
+ *
+ */
+STATE_GUARD(common_success_notification)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+       STATE_SET(common_success_notification);
+
+       return CALL_SECTION(send_common_success_notification);
+}
+
+/** Resume after 'recv Client-Error { ... }'
+ *
+ * - Enter the EAP-FAILURE state.
+ */
+RESUME(recv_common_client_error)
+{
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+       SECTION_RCODE_IGNORED;
+
+       return STATE_TRANSITION(eap_failure);
+}
+
+/** Resume after 'recv Reauthentication-Response { ... }'
+ *
+ * - If 'recv Reauthentication-Response { ... }' returned a failure
+ *   rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call the EAP-Request/Reauthentication-Response function to act on the
+ *   contents of the response.
+ */
+RESUME(recv_common_reauthentication_response)
+{
+       eap_session_t           *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+       ssize_t                 slen;
+       fr_pair_t               *mac;
+
+#if 0
+       *checkcode;
+#endif
+
+       SECTION_RCODE_PROCESS;
+
+       mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+       if (!mac) {
+               REDEBUG("Missing AT_MAC attribute");
+       failure:
+               return STATE_TRANSITION(common_failure_notification);
+       }
+       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+               goto failure;
+       }
+
+       // TODO: MAC validation should happen in the decoder not the module
+       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+                                            eap_aka_sim_session->mac_md,
+                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+                                            eap_aka_sim_session->keys.reauth.nonce_s,
+                                            sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
+       if (slen < 0) {
+               RPEDEBUG("Failed calculating MAC");
+               goto failure;
+       } else if (slen == 0) {
+               REDEBUG("Zero length AT_MAC attribute");
+               goto failure;
+       }
+
+       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+               RDEBUG2("Received MAC matches calculated MAC");
+       } else {
+               REDEBUG("Received MAC does not match calculated MAC");
+               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+               goto failure;
+       }
+
+#if 0
+       /*
+        *      If the peer doesn't include a checkcode then that
+        *      means they don't support it, and we can't validate
+        *      their view of the identity packets.
+        */
+       checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode);
+       if (checkcode) {
+               if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
+                       REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)",
+                               checkcode->vp_length, eap_aka_sim_session->checkcode_len);
                        goto failure;
                }
-               eap_aka_sim_session->checkcode_len = slen;
 
-               MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
-               fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+               if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode,
+                          eap_aka_sim_session->checkcode_len) == 0) {
+                       RDEBUG2("Received checkcode matches calculated checkcode");
+               } else {
+                       REDEBUG("Received checkcode does not match calculated checkcode");
+                       RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
+                       RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
+                                        eap_aka_sim_session->checkcode_len, "Expected");
+                       goto failure;
+               }
        /*
-        *      If we don't have checkcode data, then we exchanged
-        *      no identity packets, so AT_CHECKCODE is zero.
+        *      Only print something if we calculated a checkcode
         */
-       } else {
-               MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
-               fr_pair_value_memdup(vp, NULL, 0, false);
-               eap_aka_sim_session->checkcode_len = 0;
+       } else if (eap_aka_sim_session->checkcode_len > 0){
+               RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
        }
+#endif
 
        /*
-        *      We've sent the challenge so the peer should now be able
-        *      to accept encrypted attributes.
+        *      Check to see if the supplicant sent
+        *      AT_COUNTER_TOO_SMALL, if they did then we
+        *      clear out reauth information and enter the
+        *      challenge state.
         */
-       eap_aka_sim_session->allow_encrypted = true;
+       if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_counter_too_small)) {
+               RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)",
+                       eap_aka_sim_session->keys.reauth.counter);
+
+               fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys);
+               eap_aka_sim_session->allow_encrypted = false;
+
+               return STATE_TRANSITION(aka_challenge);
+       }
+
+       /*
+        *      If the peer wants a Success notification, and
+        *      we included AT_RESULT_IND then send a success
+        *      notification, otherwise send a normal EAP-Success.
+        *
+        *      RFC 4187 Section #6.2. Result Indications
+        */
+       if (eap_aka_sim_session->send_result_ind) {
+               if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+                       RDEBUG("We wanted to use protected result indications, but peer does not");
+                       eap_aka_sim_session->send_result_ind = false;
+               } else {
+                       return STATE_TRANSITION(common_success_notification);
+               }
+       } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+               RDEBUG("Peer wanted to use protected result indications, but we do not");
+       }
+
+       eap_aka_sim_session->reauthentication_success = true;
 
-       return session_and_pseudonym_store(p_result, mctx,request, eap_session, aka_challenge_request_send);
+       return STATE_TRANSITION(eap_success);
 }
 
-/** Called after 'store session { ... }' and 'store pseudonym { ... }'
+/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
  *
+ * - Continue based on received AT_SUBTYPE value:
+ *   - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }'
+ *   - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that
+ *     send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure.
+ *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t sim_challenge_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, eap_session_t *eap_session)
+STATE(common_reauthentication)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *subtype_vp = NULL;
+
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+       if (!subtype_vp) {
+               REDEBUG("Missing AT_SUBTYPE");
+               goto fail;
+       }
 
        /*
-        *      Encode the packet - AT_IV is handled automatically
-        *      by the encoder.
+        *      These aren't allowed in Reauthentication responses as they don't apply:
+        *
+        *      EAP_AKA_AUTHENTICATION_REJECT   - We didn't provide an AUTN value
+        *      EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors.
         */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_CHALLENGE,
-                         eap_aka_sim_session->keys.gsm.nonce_mt, sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) < 0) {
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
+       switch (subtype_vp->vp_uint16) {
+       case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION:
+               /*
+                *      AT_COUNTER_TOO_SMALL is handled
+                *      in common_reauthentication_response_process.
+                */
+               return CALL_SECTION(recv_common_reauthentication_response);
 
-       RETURN_MODULE_HANDLED;
+       /*
+        *      Case 1 where we're allowed to send an EAP-Failure
+        */
+       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+               client_error_debug(request);
+
+               eap_aka_sim_session->allow_encrypted = false;
+
+               return CALL_SECTION(recv_common_client_error);
+       /*
+        *      RFC 4187 says we *MUST* notify, not just
+        *      send an EAP-Failure in this case.
+        */
+       default:
+               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+       fail:
+               eap_aka_sim_session->allow_encrypted = false;
+
+               return STATE_TRANSITION(common_failure_notification);
+       }
 }
 
-/** Send a EAP-Request/SIM-Challenge message to the supplicant
+
+/** Send a EAP-Request/(AKA|SIM)-Reauthenticate message to the supplicant
  *
  */
-static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                    request_t *request, eap_session_t *eap_session)
+static unlang_action_t common_reauthentication_request_compose(rlm_rcode_t *p_result,
+                                                              module_ctx_t const *mctx,
+                                                              request_t *request,
+                                                              eap_aka_sim_session_t *eap_aka_sim_session)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       fr_pair_list_t          *to_peer = &request->reply_pairs;
        fr_pair_t               *vp;
-       fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
-
        fr_pair_t               *kdf_id;
 
        /*
@@ -1545,30 +1730,76 @@ static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, modu
         *      Because certain handset manufacturers don't
         *      implement RFC 4187 correctly and use the
         *      wrong identity as input the the PRF/KDF.
+        *
+        *      Not seen any doing this for re-authentication
+        *      but you never know...
         */
        kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
        if (kdf_id) {
-               identity_to_crypto_identity(request, eap_aka_sim_session,
-                                           (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+               crypto_identity_set(request, eap_aka_sim_session,
+                                   (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
                fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
        }
 
-       RDEBUG2("Acquiring GSM vector(s)");
-       if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0,
-                                             &eap_aka_sim_session->keys, &src) != 0) ||
-           (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1,
-                                             &eap_aka_sim_session->keys, &src) != 0) ||
-           (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2,
-                                             &eap_aka_sim_session->keys, &src) != 0)) {
-               REDEBUG("Failed retrieving SIM vectors");
-               RETURN_MODULE_FAIL;
-       }
+       RDEBUG2("Generating new session keys");
 
-       fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys);
+       switch (eap_aka_sim_session->type) {
+       /*
+        *      The GSM and UMTS KDF_0 mutate their keys using
+        *      and identical algorithm.
+        */
+       case FR_EAP_METHOD_SIM:
+       case FR_EAP_METHOD_AKA:
+               if (fr_aka_sim_vector_gsm_umts_kdf_0_reauth_from_attrs(request, &request->session_state_pairs,
+                                                                      &eap_aka_sim_session->keys) != 0) {
+               request_new_id:
+                       switch (eap_aka_sim_session->last_id_req) {
+                       /*
+                        *      Got here processing EAP-Identity-Response
+                        *      If this is the *true* reauth ID, then
+                        *      there's no point in setting AKA_SIM_ANY_ID_REQ.
+                        */
+                       case AKA_SIM_NO_ID_REQ:
+                       case AKA_SIM_ANY_ID_REQ:
+                               RDEBUG2("Composing EAP-Request/Reauthentication failed.  Clearing reply attributes and "
+                                       "requesting additional Identity");
+                               fr_pair_list_free(&request->reply_pairs);
+                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+                               return STATE_TRANSITION(common_identity);
 
-       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
+                       case AKA_SIM_FULLAUTH_ID_REQ:
+                       case AKA_SIM_PERMANENT_ID_REQ:
+                               REDEBUG("Last requested fullauth or permanent ID, "
+                                       "but received, or were told we received (by policy), "
+                                       "a fastauth ID.  Cannot continue");
+                               return STATE_TRANSITION(common_failure_notification);
+                       }
+               }
+               if (fr_aka_sim_crypto_kdf_0_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
+               break;
+
+       case FR_EAP_METHOD_AKA_PRIME:
+               switch (eap_aka_sim_session->kdf) {
+               case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
+                       if (fr_aka_sim_vector_umts_kdf_1_reauth_from_attrs(request, &request->session_state_pairs,
+                                                                          &eap_aka_sim_session->keys) != 0) {
+                               goto request_new_id;
+                       }
+                       if (fr_aka_sim_crypto_umts_kdf_1_reauth(&eap_aka_sim_session->keys) < 0) goto request_new_id;
+                       break;
+
+               default:
+                       fr_assert(0);
+                       break;
+               }
+               break;
+
+       default:
+               fr_assert(0);
+               break;
+       }
 
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
 
        /*
         *      Indicate we'd like to use protected success messages
@@ -1576,486 +1807,178 @@ static unlang_action_t sim_challenge_request_compose(rlm_rcode_t *p_result, modu
         *
         *      Use our default, but allow user override too.
         */
-       vp = fr_pair_find_by_da(to_peer, attr_eap_aka_sim_result_ind);
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
        if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
 
        /*
-        *      Okay, we got the challenges! Put them into attributes.
+        *      RFC 5448 says AT_BIDDING is only sent in the challenge
+        *      not in reauthentication, so don't add that here.
         */
-       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
 
-       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
+        /*
+         *     Add AT_NONCE_S
+         */
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_nonce_s) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.reauth.nonce_s,
+                            sizeof(eap_aka_sim_session->keys.reauth.nonce_s), false);
 
-       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
+       /*
+        *      Add AT_COUNTER
+        */
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_counter) >= 0);
+       vp->vp_uint16 = eap_aka_sim_session->keys.reauth.counter;
 
        /*
-        *      need to include an AT_MAC attribute so that it will get
-        *      calculated.
+        *      need to include an empty AT_MAC attribute so that
+        *      the mac will get calculated.
         */
        MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
        fr_pair_value_memdup(vp, NULL, 0, false);
 
+#if 0
+       /*
+        *      If there's no checkcode_md we're not doing
+        *      checkcodes.
+        */
+       if (eap_aka_sim_session->checkcode_md) {
+               /*
+                *      If we have checkcode data, send that to the peer
+                *      in AT_CHECKCODE for validation.
+                */
+               if (eap_aka_sim_session->checkcode_state) {
+                       ssize_t slen;
+
+                       slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
+                                                                   &eap_aka_sim_session->checkcode_state);
+                       if (slen < 0) {
+                               RPEDEBUG("Failed calculating checkcode");
+                               goto failure;
+                       }
+                       eap_aka_sim_session->checkcode_len = slen;
+
+                       MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+                       fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+               /*
+                *      If we don't have checkcode data, then we exchanged
+                *      no identity packets, so checkcode is zero.
+                */
+               } else {
+                       MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+                       fr_pair_value_memdup(vp, NULL, 0, false);
+                       eap_aka_sim_session->checkcode_len = 0;
+               }
+       }
+#endif
+
        /*
         *      We've sent the challenge so the peer should now be able
         *      to accept encrypted attributes.
         */
        eap_aka_sim_session->allow_encrypted = true;
 
-       return session_and_pseudonym_store(p_result, mctx,request, eap_session, sim_challenge_request_send);
+       return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session,
+                                          common_reauthentication_request_send);
 }
 
-/** Send a EAP-Request/AKA-Identity message to the supplicant
- *
- * There are three types of user identities that can be implemented
- * - Permanent identities such as 0123456789098765@myoperator.com
- *   Permanent identities can be identified by the leading zero followed by
- *   by 15 digits (the IMSI number).
- * - Ephemeral identities (pseudonyms).  These are identities assigned for
- *   identity privacy so the user can't be tracked.  These can identities
- *   can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
- *   document section 14, where a set of up to 16 encryption keys are used
- *   to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
- *   randomised and stored in a datastore.
- * - A fast resumption ID which resolves to data used for fast resumption.
- *
- * In order to perform full authentication the original IMSI is required for
- * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
- * or can't perform fast resumption, we need to request the full identity from
- * the supplicant.
+/** Resume after 'send Reauthentication-Request { ... }'
  *
- * @param[out] p_result                Result of calling the module, one of:
- *                             - RLM_MODULE_HANDLED on success.
- *                             - anything else on failure.
- * @param[in] mctx             module calling ctx.
- * @param[in] request          The current subrequest.
- * @param[in] eap_session      to continue.
  */
-static unlang_action_t aka_identity_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                request_t *request, eap_session_t *eap_session)
+RESUME(send_common_reauthentication_request)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
+       switch (request->rcode) {
        /*
-        *      Update eap_aka_sim_session->id_req in case the the
-        *      user set attributes in `send Identity-Request { ... }`
-        *      Also removes all existing id_req attributes
-        *      from the reply.
+        *      Failed getting the values we need for resumption
+        *      Request a different identity.
         */
-       identity_req_set_by_user(request, eap_aka_sim_session);
+       default:
+               switch (eap_aka_sim_session->last_id_req) {
+               /*
+                *      Got here processing EAP-Identity-Response
+                *      If this is the *true* reauth ID, then
+                *      there's no point in setting AKA_SIM_ANY_ID_REQ.
+                */
+               case AKA_SIM_NO_ID_REQ:
+               case AKA_SIM_ANY_ID_REQ:
+                       RDEBUG2("Previous section returned (%s), clearing reply attributes and "
+                               "requesting additional identity",
+                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
+                       fr_pair_list_free(&request->reply_pairs);
+                       eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
 
-       /*
-        *      Select the right type of identity request attribute
-        *
-        *      Implement checks on identity request order described
-        *      by RFC4187 section #4.1.5.
-        *
-        *      The internal state machine should always handle this
-        *      correctly, but the user may have other ideas...
-        */
-       if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+                       return STATE_TRANSITION(common_identity);
+
+               case AKA_SIM_FULLAUTH_ID_REQ:
+               case AKA_SIM_PERMANENT_ID_REQ:
+               default:
+                       break;
+               }
+               REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
+                       "but received a Fast-Auth-Id.  Cannot continue");
        failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+               return STATE_TRANSITION(common_failure_notification);
 
        /*
-        *      Encode the packet
+        *      Policy rejected the user
         */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_AKA_IDENTITY, NULL, 0) < 0) goto failure;
+       case RLM_MODULE_REJECT:
+       case RLM_MODULE_DISALLOW:
+               goto failure;
 
        /*
-        *      Digest the packet contents, updating our checkcode.
+        *      Everything looks ok, send the EAP-Request/reauthentication message
+        *      After storing any new pseudonyms or session information.
         */
-       if (eap_aka_sim_session->checkcode_md) {
-               if (!eap_aka_sim_session->checkcode_state &&
-                   fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state,
-                                                    eap_aka_sim_session->checkcode_md) < 0) {
-                       RPEDEBUG("Failed initialising checkcode");
-                       goto failure;
-               }
-               if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
-                                                      eap_session->this_round->request) < 0) {
-                       RPEDEBUG("Failed updating checkcode");
-                       goto failure;
-               }
+       case RLM_MODULE_NOOP:
+       case RLM_MODULE_OK:
+       case RLM_MODULE_UPDATED:
+               return common_reauthentication_request_compose(p_result, mctx, request, eap_aka_sim_session);
        }
-
-       RETURN_MODULE_HANDLED;
 }
 
-/** Send a EAP-Request/SIM-Start message to the supplicant
- *
- * There are three types of user identities that can be implemented
- * - Permanent identities such as 0123456789098765@myoperator.com
- *   Permanent identities can be identified by the leading zero followed by
- *   by 15 digits (the IMSI number).
- * - Ephemeral identities (pseudonyms).  These are identities assigned for
- *   identity privacy so the user can't be tracked.  These can identities
- *   can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
- *   document section 14, where a set of up to 16 encryption keys are used
- *   to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
- *   randomised and stored in a datastore.
- * - A fast resumption ID which resolves to data used for fast resumption.
- *
- * In order to perform full authentication the original IMSI is required for
- * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
- * or can't perform fast resumption, we need to request the full identity from
- * the supplicant.
+/** Resume after 'load pseudonym { ... }'
  *
- * @param[out] p_result                Result of calling the module, one of:
- *                             - RLM_MODULE_HANDLED on success.
- *                             - anything else on failure.
- * @param[in] mctx             module calling context.
- * @param[in] request          The current subrequest.
- * @param[in] eap_session      to continue.
  */
-static unlang_action_t sim_start_request_send(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, eap_session_t *eap_session)
+RESUME(load_pseudonym)
 {
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       fr_pair_t               *vp;
-       uint8_t                 *p, *end;
-
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
-       p = eap_aka_sim_session->keys.gsm.version_list;
-       end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list);
-       eap_aka_sim_session->keys.gsm.version_list_len = 0;
+       pair_delete_request(attr_eap_aka_sim_next_reauth_id);
 
        /*
-        *      If the user provided no versions, then
-        *      just add the default (1).
+        *      Control attributes required could have been specified
+        *      in another section.
         */
-       if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) {
-               MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0);
-               vp->vp_uint16 = EAP_SIM_VERSION;
+       if (!inst->actions.load_pseudonym) {
+       next_state:
+               return eap_aka_sim_session->next(p_result, mctx, request, eap_aka_sim_session);
        }
 
+       switch (request->rcode) {
        /*
-        *      Iterate over the the versions adding them
-        *      to the version list we use for keying.
-        */
-       for (vp = fr_pair_list_head(&request->reply_pairs); vp; vp = fr_pair_list_next(&request->reply_pairs, vp)) {
-               if (vp->da != attr_eap_aka_sim_version_list) continue;
-
-               if ((end - p) < 2) break;
-
-               /*
-                *      Store as big endian
-                */
-               *p++ = (vp->vp_uint16 & 0xff00) >> 8;
-               *p++ = (vp->vp_uint16 & 0x00ff);
-               eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t);
-       }
-
-       /*
-        *      Update eap_aka_sim_session->id_req in case the the
-        *      user set attributes in `send Identity-Request { ... }`
-        *      Also removes all existing id_req attributes
-        *      from the reply.
-        */
-       identity_req_set_by_user(request, eap_aka_sim_session);
-
-       /*
-        *      Select the right type of identity request attribute
-        *
-        *      Implement checks on identity request order described
-        *      by RFC4186 section #4.2.5.
-        *
-        *      The internal state machine should always handle this
-        *      correctly, but the user may have other ideas...
-        */
-       if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
-
-       /*
-        *      Encode the packet
-        */
-       if (common_encode(request, eap_session, FR_SUBTYPE_VALUE_SIM_START, NULL, 0) < 0) goto failure;
-
-       RETURN_MODULE_HANDLED;
-}
-
-/** Print debugging information, and write new state to eap_session->process
- *
- */
-static inline void state_transition(request_t *request, eap_session_t *eap_session, module_method_t new_state)
-{
-       module_method_t         old_state = eap_session->process;
-
-       if (new_state != old_state) {
-               RDEBUG2("Changed state %s -> %s",
-                       module_state_method_to_str(aka_sim_stable_table, old_state, "<unknown>"),
-                       module_state_method_to_str(aka_sim_stable_table, new_state, "<unknown>"));
-       } else {
-               RDEBUG2("Reentering state %s",
-                       module_state_method_to_str(aka_sim_stable_table, old_state, "<unknown>"));
-       }
-
-       eap_session->process = new_state;
-}
-
-/** Resume after 'send EAP-Failure { ... }'
- *
- */
-static unlang_action_t common_eap_failure_enter_resume(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
-                                                      request_t *request, UNUSED void *rctx)
-{
-       eap_session_t   *eap_session = eap_session_get(request->parent);
-
-       section_rcode_ignored(request);
-
-       return common_eap_failure_send(p_result, request, eap_session);
-}
-
-/** Enter EAP-FAILURE state
- *
- */
-static unlang_action_t common_eap_failure_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                               request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-
-       /*
-        *      Free anything we were going to send out...
-        */
-       fr_pair_list_free(&request->reply_pairs);
-
-       /*
-        *      If we're failing, then any identities
-        *      we sent are now invalid.
-        */
-       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
-               return session_and_pseudonym_clear(p_result, mctx,
-                                                  request, eap_session, common_eap_failure_enter);
-                                                  /* come back when we're done */
-       }
-
-       state_transition(request, eap_session, common_eap_failure);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_eap_failure,
-                                             RLM_MODULE_NOOP,
-                                             common_eap_failure_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Resume after 'send Failure-Notification { ... }'
- *
- * Ignores return code from send Failure-Notification { ... } section.
- */
-static unlang_action_t common_failure_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                               request_t *request, UNUSED void *rctx)
-{
-       eap_session_t   *eap_session = eap_session_get(request->parent);
-
-       section_rcode_ignored(request);
-
-       /*
-        *      Free anything we were going to send out...
-        */
-       fr_pair_list_free(&request->reply_pairs);
-
-       /*
-        *      If there's an issue composing the failure
-        *      message just send an EAP-Failure instead.
-        */
-       return common_failure_notification_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the FAILURE-NOTIFICATION state
- *
- */
-static unlang_action_t common_failure_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                        request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-
-       /*
-        *      If we're failing, then any identities
-        *      we sent are now invalid.
-        */
-       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
-               return session_and_pseudonym_clear(p_result, mctx,request, eap_session,
-                                                  common_failure_notification_enter); /* come back when we're done */
-       }
-
-       /*
-        *      We've already sent a failure notification
-        *      Now we just fail as it means something
-        *      went wrong processing the ACK or we got
-        *      garbage from the supplicant.
-        */
-       if (eap_session->process == common_failure_notification) {
-               return common_eap_failure_enter(p_result, mctx, request, eap_session);
-       }
-
-       /*
-        *      Otherwise just transition as normal...
-        */
-       state_transition(request, eap_session, common_failure_notification);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_failure_notification,
-                                             RLM_MODULE_NOOP,
-                                             common_failure_notification_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Resume after 'send EAP-Success { ... }'
- *
- */
-static unlang_action_t common_eap_success_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                      request_t *request, UNUSED void *rctx)
-{
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
-       /*
-        *      If this is true we're entering this state
-        *      after sending a AKA-Success-Notification
-        *
-        *      Is seems like a really bad idea to allow the
-        *      user to send a protected success to the
-        *      supplicant and then force a failure using
-        *      the send EAP-Success { ... } section.
-        */
-       if (eap_aka_sim_session->send_result_ind) {
-               switch (request->rcode) {
-               case RLM_MODULE_USER_SECTION_REJECT:
-                       RWDEBUG("Ignoring rcode (%s) from send EAP-Success { ... } "
-                               "as we already sent a Success-Notification",
-                               fr_table_str_by_value(rcode_table, request->rcode, "<invalid>"));
-                       RWDEBUG("If you need to force a failure, return an error code from "
-                               "send Success-Notification { ... }");
-                       break;
-
-               default:
-                       break;
-               }
-
-       /*
-        *      But... if we're not working with protected
-        *      success indication, this is the only
-        *      opportunity the user has to force a failure at
-        *      the end of authentication.
-        */
-       } else {
-               section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-       }
-
-       return common_eap_success_send(p_result, request, eap_session);
-}
-
-/** Enter EAP-SUCCESS state
- *
- */
-static unlang_action_t common_eap_success_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request,
-                                               eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       state_transition(request, eap_session, common_eap_success);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_eap_success,
-                                             RLM_MODULE_NOOP,
-                                             common_eap_success_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Resume after 'send Success-Notification { ... }'
- *
- */
-static unlang_action_t common_success_notification_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                               request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return common_success_notification_send(p_result, mctx, request, eap_session);
-}
-
-/** Enter the SUCCESS-NOTIFICATION state
- *
- */
-static unlang_action_t common_success_notification_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                        request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       state_transition(request, eap_session, common_success_notification);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_success_notification,
-                                             RLM_MODULE_NOOP,
-                                             common_success_notification_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Resume after 'send Reauthentication-Request { ... }'
- *
- */
-static unlang_action_t common_reauthentication_send_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                          request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       switch (request->rcode) {
-       /*
-        *      Failed getting the values we need for resumption
-        *      Request a different identity.
+        *      Failed resolving the pseudonym
+        *      request a different identity.
         */
        default:
                switch (eap_aka_sim_session->last_id_req) {
-               /*
-                *      Got here processing EAP-Identity-Response
-                *      If this is the *true* reauth ID, then
-                *      there's no point in setting AKA_SIM_ANY_ID_REQ.
-                */
                case AKA_SIM_NO_ID_REQ:
                case AKA_SIM_ANY_ID_REQ:
+               case AKA_SIM_FULLAUTH_ID_REQ:
                        RDEBUG2("Previous section returned (%s), clearing reply attributes and "
                                "requesting additional identity",
                                fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
                        fr_pair_list_free(&request->reply_pairs);
                        eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+                       return STATE_TRANSITION(common_identity);
 
-                       return common_identity_enter(p_result, mctx, request, eap_session);
-
-               case AKA_SIM_FULLAUTH_ID_REQ:
                case AKA_SIM_PERMANENT_ID_REQ:
-               default:
-                       break;
+                       REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym.  Cannot continue");
+               failure:
+                       return STATE_TRANSITION(common_failure_notification);
                }
-               REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
-                       "but received a Fast-Auth-Id.  Cannot continue");
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+               break;
 
        /*
         *      Policy rejected the user
@@ -2065,26 +1988,23 @@ static unlang_action_t common_reauthentication_send_resume(rlm_rcode_t *p_result
                goto failure;
 
        /*
-        *      Everything looks ok, send the EAP-Request/reauthentication message
-        *      After storing any new pseudonyms or session information.
+        *      Everything OK
         */
-       case RLM_MODULE_NOOP:
        case RLM_MODULE_OK:
        case RLM_MODULE_UPDATED:
-               return common_reauthentication_request_compose(p_result, mctx, request, eap_session);
+               goto next_state;
        }
+
+       goto failure;
 }
 
 /** Resume after 'load session { ... }'
  *
  */
-static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                          request_t *request, UNUSED void *rctx)
+RESUME(load_session)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
        pair_delete_request(attr_session_id);
 
@@ -2113,14 +2033,13 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c
                                fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
                        fr_pair_list_free(&request->reply_pairs);
                        eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
-                       return common_identity_enter(p_result, mctx, request, eap_session);
+                       return STATE_TRANSITION(common_identity);
 
                case AKA_SIM_FULLAUTH_ID_REQ:
                case AKA_SIM_PERMANENT_ID_REQ:
                        REDEBUG("Last requested Full-Auth-Id or Permanent-Identity, "
                                "but received a Fast-Auth-Id.  Cannot continue");
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-
+                       return STATE_TRANSITION(common_failure_notification);
                }
                break;
 
@@ -2130,7 +2049,7 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c
        case RLM_MODULE_REJECT:
        case RLM_MODULE_DISALLOW:
        reject:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+               return STATE_TRANSITION(common_failure_notification);
 
        /*
         *      Everything OK
@@ -2138,1639 +2057,930 @@ static unlang_action_t session_load_resume(rlm_rcode_t *p_result, module_ctx_t c
        case RLM_MODULE_OK:
        case RLM_MODULE_UPDATED:
        reauthenticate:
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.send_reauthentication_request,
-                                                     RLM_MODULE_NOOP,
-                                                     common_reauthentication_send_resume,
-                                                     mod_signal,
-                                                     NULL);
+               return CALL_SECTION(send_common_reauthentication_request);
        }
 
        goto reject;
 }
 
-/** Resume after 'load pseudonym { ... }'
+/** Enter the REAUTHENTICATION state
  *
  */
-static unlang_action_t pseudonym_load_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                            request_t *request, void *rctx)
+STATE_GUARD(common_reauthentication)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       aka_sim_state_enter_t   state_enter = (aka_sim_state_enter_t)rctx;
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t               *vp = NULL;
 
-       pair_delete_request(attr_eap_aka_sim_next_reauth_id);
+       STATE_SET(common_reauthentication);
 
        /*
-        *      Control attributes required could have been specified
-        *      in another section.
+        *      Add the current identity as session_id
+        *      to make it easier to load/store things from
+        *      the cache module.
         */
-       if (!inst->actions.load_pseudonym) {
-       next_state:
-               return state_enter(p_result, mctx, request, eap_session);
-       }
+       MEM(pair_update_request(&vp, attr_session_id) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true);
 
-       switch (request->rcode) {
-       /*
-        *      Failed resolving the pseudonym
-        *      request a different identity.
-        */
-       default:
-               switch (eap_aka_sim_session->last_id_req) {
-               case AKA_SIM_NO_ID_REQ:
-               case AKA_SIM_ANY_ID_REQ:
-               case AKA_SIM_FULLAUTH_ID_REQ:
-                       RDEBUG2("Previous section returned (%s), clearing reply attributes and "
-                               "requesting additional identity",
-                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"));
-                       fr_pair_list_free(&request->reply_pairs);
-                       eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
-                       return common_identity_enter(p_result, mctx, request, eap_session);
+       return CALL_SECTION(load_session);
+}
 
-               case AKA_SIM_PERMANENT_ID_REQ:
-                       REDEBUG("Last requested a Permanent-Identity, but received a Pseudonym.  Cannot continue");
-               failure:
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-               }
-               break;
+/** Resume after 'recv Synchronization-Failure { ... }'
+ *
+ * - If 'recv Synchronization-Failure { ... }' returned a failure
+ *   rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or if no 'recv Syncronization-Failure { ... }' section was
+ *   defined, then enter the FAILURE-NOTIFICATION state.
+ * - ...or if the user didn't provide a new SQN value in &control.SQN
+ *   then enter the FAILURE-NOTIFICATION state.
+ * - ...or enter the AKA-CHALLENGE state.
+ */
+RESUME(recv_aka_syncronization_failure)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       fr_pair_t                       *vp;
+
+       SECTION_RCODE_PROCESS;
 
        /*
-        *      Policy rejected the user
+        *      If there's no section to handle this, then no resynchronisation
+        *      can't have occurred and we just send a reject.
+        *
+        *      Similarly, if we've already received one synchronisation failure
+        *      then it's highly likely whatever user configured action was
+        *      configured was unsuccessful, and we should just give up.
         */
-       case RLM_MODULE_REJECT:
-       case RLM_MODULE_DISALLOW:
-               goto failure;
+       if (!inst->actions.recv_aka_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) {
+       failure:
+               return STATE_TRANSITION(common_failure_notification);
+       }
 
        /*
-        *      Everything OK
+        *      We couldn't generate an SQN and the user didn't provide one,
+        *      so we need to fail.
         */
-       case RLM_MODULE_OK:
-       case RLM_MODULE_UPDATED:
-               goto next_state;
+       vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn);
+       if (!vp) {
+               REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue");
+               goto failure;
        }
 
-       goto failure;
+       /*
+        *      RFC 4187 Section #6.3.1
+        *
+        *      "if the peer detects that the
+        *      sequence number in AUTN is not correct, the peer responds with
+        *      EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the
+        *      server proceeds with a new EAP-Request/AKA-Challenge."
+        */
+       return STATE_TRANSITION(aka_challenge);
 }
 
-/** Enter the REAUTHENTICATION state
+/** Resume after 'recv Authentication-Reject { ... }'
  *
+ * - Enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t common_reauthentication_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                    request_t *request, eap_session_t *eap_session)
+RESUME(recv_aka_authentication_reject)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       fr_pair_t               *vp = NULL;
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
-       state_transition(request, eap_session, common_reauthentication);
+       SECTION_RCODE_IGNORED;
 
        /*
-        *      Add the current identity as session_id
-        *      to make it easier to load/store things from
-        *      the cache module.
+        *      Case 2 where we're allowed to send an EAP-Failure
         */
-       MEM(pair_update_request(&vp, attr_session_id) >= 0);
-       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.identity, eap_aka_sim_session->keys.identity_len, true);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.load_session,
-                                             RLM_MODULE_NOOP,
-                                             session_load_resume,
-                                             mod_signal,
-                                             NULL);
+       return STATE_TRANSITION(eap_failure);
 }
 
-/** Resume after 'send Challenge-Request { ... }'
+/** Resume after 'recv Challenge-Response { ... }'
+ *
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the AKA-Challenge message.
  *
+ * Verify that MAC, and RES match what we expect.
  */
-static unlang_action_t aka_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, UNUSED void *rctx)
+RESUME(recv_aka_challenge_response)
 {
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+       eap_session_t           *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+       ssize_t                 slen;
+       fr_pair_t               *vp = NULL, *mac;
+#if 0
+       *checkcode;
+#endif
+       SECTION_RCODE_PROCESS;
 
-       return aka_challenge_request_compose(p_result, mctx, request, eap_session);
-}
 
-/** Enter the AKA-CHALLENGE state
- *
- */
-static unlang_action_t aka_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                          request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque, eap_aka_sim_session_t);
-       fr_pair_t               *vp;
+       mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+       if (!mac) {
+               REDEBUG("Missing AT_MAC attribute");
+       failure:
+               return STATE_TRANSITION(common_failure_notification);
+       }
+       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+               goto failure;
+       }
 
-       /*
-        *      If we've sent either of these identities it
-        *      means we've come here form a Reauthentication-Request
-        *      that failed.
-        */
-       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
-               return session_and_pseudonym_clear(p_result, mctx,request, eap_session, aka_challenge_enter);   /* come back when we're done */
+       // TODO: MAC validation should happen in the decoder not the state machine
+       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+                                            eap_aka_sim_session->mac_md,
+                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+                                            NULL, 0);
+       if (slen < 0) {
+               RPEDEBUG("Failed calculating MAC");
+               goto failure;
+       } else if (slen == 0) {
+               REDEBUG("Zero length AT_MAC attribute");
+               goto failure;
        }
 
-       state_transition(request, eap_session, aka_challenge);
+       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+               RDEBUG2("Received MAC matches calculated MAC");
+       } else {
+               REDEBUG("Received MAC does not match calculated MAC");
+               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+               goto failure;
+       }
 
        /*
-        *      Set some default attributes, giving the user a
-        *      chance to modify them.
+        *      If the peer doesn't include a checkcode then that
+        *      means they don't support it, and we can't validate
+        *      their view of the identity packets.
         */
-       switch (eap_session->type) {
-       case FR_EAP_METHOD_AKA_PRIME:
-       {
-               uint8_t         amf_buff[2] = { 0x80, 0x00 };   /* Set the AMF separation bit high */
+#if 0
+       if (eap_aka_sim_session->checkcode_md) {
+               checkcode = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_checkcode);
+               if (checkcode) {
+                       if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
+                               REDEBUG("Received checkcode's length (%zu) does not match "
+                                       "calculated checkcode's length (%zu)",
+                                       checkcode->vp_length, eap_aka_sim_session->checkcode_len);
+                               goto failure;
+                       }
 
+                       if (memcmp(checkcode->vp_octets,
+                                  eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) {
+                               RDEBUG2("Received checkcode matches calculated checkcode");
+                       } else {
+                               REDEBUG("Received checkcode does not match calculated checkcode");
+                               RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
+                               RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
+                                                eap_aka_sim_session->checkcode_len, "Expected");
+                               goto failure;
+                       }
                /*
-                *      Toggle the AMF high bit to indicate we're doing AKA'
+                *      Only print something if we calculated a checkcode
                 */
-               MEM(pair_update_control(&vp, attr_sim_amf) >= 0);
-               fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false);
-
-               /*
-                *      Use the default network name we have configured
-                *      and send it to the peer.
-                */
-               if (inst->network_name &&
-                   !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) {
-                       MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0);
-                       fr_pair_value_bstrdup_buffer(vp, inst->network_name, false);
+               } else if (eap_aka_sim_session->checkcode_len > 0){
+                       RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
                }
        }
-               break;
+#endif
 
-       default:
-               /*
-                *      Use the default bidding value we have configured
-                */
-               if (eap_aka_sim_session->send_at_bidding_prefer_prime &&
-                   !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_bidding)) {
-                       MEM(pair_append_reply(&vp, attr_eap_aka_sim_bidding) >= 0);
-                       vp->vp_uint16 = FR_BIDDING_VALUE_PREFER_AKA_PRIME;
-               }
-               break;
+       vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_res);
+       if (!vp) {
+               REDEBUG("AT_RES missing from challenge response");
+               goto failure;
+       }
 
+       if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) {
+               REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)",
+                       vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len);
+               goto failure;
        }
 
-       /*
-        *      Set the defaults for protected result indicator
-        */
-       if (eap_aka_sim_session->send_result_ind &&
-           !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
-               MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
-               vp->vp_bool = true;
+       if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) {
+               REDEBUG("Received RES does not match calculated XRES");
+               RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES  :");
+               RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres,
+                               eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :");
+               goto failure;
        }
 
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_challenge_request,
-                                             RLM_MODULE_NOOP,
-                                             aka_challenge_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
+       RDEBUG2("Received RES matches calculated XRES");
 
-/** Resume after 'send Challenge-Request { ... }'
- *
- */
-static unlang_action_t sim_challenge_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
+       eap_aka_sim_session->challenge_success = true;
 
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+       /*
+        *      If the peer wants a Success notification, and
+        *      we included AT_RESULT_IND then send a success
+        *      notification, otherwise send a normal EAP-Success.
+        *
+        *      RFC 4187 Section #6.2. Result Indications
+        */
+       if (eap_aka_sim_session->send_result_ind) {
+               if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+                       RDEBUG("We wanted to use protected result indications, but peer does not");
+                       eap_aka_sim_session->send_result_ind = false;
+               } else {
+                       return STATE_TRANSITION(common_success_notification);
+               }
+       } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+               RDEBUG("Peer wanted to use protected result indications, but we do not");
+       }
 
-       return sim_challenge_request_compose(p_result, mctx, request, eap_session);
+       return STATE_TRANSITION(eap_success);
 }
 
-/** Enter the SIM-CHALLENGE state
+/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
  *
+ * - Continue based on received AT_SUBTYPE value:
+ *   - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'.
+ *   - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }'  and after that
+ *     send a EAP-Request/SIM-Notification indicating a General Failure.
+ *   - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'.
+ *   - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that
+ *     send a EAP-Request/AKA-Notification indicating a General Failure.
+ *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t sim_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                          request_t *request, eap_session_t *eap_session)
+STATE(aka_challenge)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *subtype_vp = NULL;
        fr_pair_t                       *vp;
 
-       /*
-        *      If we've sent either of these identities it
-        *      means we've come here form a Reauthentication-Request
-        *      that failed.
-        */
-       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
-               return session_and_pseudonym_clear(p_result, mctx,request, eap_session, sim_challenge_enter);   /* come back when we're done */
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
+       if (!subtype_vp) {
+               REDEBUG("Missing AT_SUBTYPE");
+               goto fail;
        }
 
-       state_transition(request, eap_session, sim_challenge);
+       switch (subtype_vp->vp_uint16) {
+       case FR_SUBTYPE_VALUE_AKA_CHALLENGE:
+               return CALL_SECTION(recv_aka_challenge_response);
 
        /*
-        *      Set the defaults for protected result indicator
+        *      Case 2 where we're allowed to send an EAP-Failure
         */
-       if (eap_aka_sim_session->send_result_ind &&
-           !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
-               MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
-               vp->vp_bool = true;
-       }
+       case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT:
+               eap_aka_sim_session->allow_encrypted = false;
+               return CALL_SECTION(recv_aka_authentication_reject);
 
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.send_challenge_request,
-                                             RLM_MODULE_NOOP,
-                                             sim_challenge_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state
- *
- * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
- * to enter the correct challenge state.
- */
-static unlang_action_t common_challenge_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, eap_session_t *eap_session)
-{
-       switch (eap_session->type) {
-       case FR_EAP_METHOD_SIM:
-               return sim_challenge_enter(p_result, mctx, request, eap_session);
-
-       case FR_EAP_METHOD_AKA:
-       case FR_EAP_METHOD_AKA_PRIME:
-               return aka_challenge_enter(p_result, mctx, request, eap_session);
-
-       default:
-               break;
-       }
-
-       fr_assert(0);
-       RETURN_MODULE_FAIL;
-}
-
-/** Resume after 'send Identity-Request { ... }'
- *
- */
-static unlang_action_t aka_identity_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return aka_identity_request_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the AKA-IDENTITY state
- *
- */
-static unlang_action_t aka_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                         request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-
-       state_transition(request, eap_session, aka_identity);
-
-       /*
-        *      If we have an send_aka_identity_request section
-        *      then run that, otherwise just run the normal
-        *      identity request section.
-        */
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.aka.send_aka_identity_request ?
-                                                       inst->actions.aka.send_aka_identity_request:
-                                                       inst->actions.send_identity_request,
-                                             RLM_MODULE_NOOP,
-                                             aka_identity_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Resume after 'send Start { ... }'
- *
- */
-static unlang_action_t sim_start_enter_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                             request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return sim_start_request_send(p_result, mctx,request, eap_session);
-}
-
-/** Enter the SIM-START state
- *
- */
-static unlang_action_t sim_start_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                      request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-
-       state_transition(request, eap_session, sim_start);
-
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.sim.send_sim_start_request ?
-                                                       inst->actions.sim.send_sim_start_request:
-                                                       inst->actions.send_identity_request,
-                                             RLM_MODULE_NOOP,
-                                             sim_start_enter_resume,
-                                             mod_signal,
-                                             NULL);
-}
-
-/** Enter the SIM-START or AKA-IDENTITY state
- *
- * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
- * to enter the correct Identity-Request state.
- */
-static unlang_action_t common_identity_enter(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                            request_t *request, eap_session_t *eap_session)
-{
-       switch (eap_session->type) {
-       case FR_EAP_METHOD_SIM:
-               return sim_start_enter(p_result, mctx, request, eap_session);
-
-       case FR_EAP_METHOD_AKA:
-       case FR_EAP_METHOD_AKA_PRIME:
-               return aka_identity_enter(p_result, mctx, request, eap_session);
-
-       default:
-               break;
-       }
-
-       fr_assert(0);
-       RETURN_MODULE_FAIL;
-}
-
-/** Process a EAP-Response/(AKA|SIM)-Reauthentication message - The response to our EAP-Request/(AKA|SIM)-Reauthentication message
- *
- */
-static unlang_action_t common_reauthentication_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                               request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
-       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
-       ssize_t                 slen;
-       fr_pair_t               *mac, *checkcode;
-       fr_pair_list_t          *from_peer = &request->request_pairs;
-
-       mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
-       if (!mac) {
-               REDEBUG("Missing AT_MAC attribute");
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
-               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
-                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
-               goto failure;
-       }
-
-       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
-                                            eap_aka_sim_session->mac_md,
-                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
-                                            eap_aka_sim_session->keys.reauth.nonce_s,
-                                            sizeof(eap_aka_sim_session->keys.reauth.nonce_s));
-       if (slen < 0) {
-               RPEDEBUG("Failed calculating MAC");
-               goto failure;
-       } else if (slen == 0) {
-               REDEBUG("Zero length AT_MAC attribute");
-               goto failure;
-       }
-
-       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
-               RDEBUG2("Received MAC matches calculated MAC");
-       } else {
-               REDEBUG("Received MAC does not match calculated MAC");
-               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
-               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
-               goto failure;
-       }
-
-       /*
-        *      If the peer doesn't include a checkcode then that
-        *      means they don't support it, and we can't validate
-        *      their view of the identity packets.
-        */
-       checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode);
-       if (checkcode) {
-               if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
-                       REDEBUG("Received checkcode's length (%zu) does not match calculated checkcode's length (%zu)",
-                               checkcode->vp_length, eap_aka_sim_session->checkcode_len);
-                       goto failure;
-               }
-
-               if (memcmp(checkcode->vp_octets, eap_aka_sim_session->checkcode,
-                          eap_aka_sim_session->checkcode_len) == 0) {
-                       RDEBUG2("Received checkcode matches calculated checkcode");
-               } else {
-                       REDEBUG("Received checkcode does not match calculated checkcode");
-                       RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
-                       RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
-                                        eap_aka_sim_session->checkcode_len, "Expected");
-                       goto failure;
-               }
-       /*
-        *      Only print something if we calculated a checkcode
-        */
-       } else if (eap_aka_sim_session->checkcode_len > 0){
-               RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
-       }
-
-       /*
-        *      Check to see if the supplicant sent
-        *      AT_COUNTER_TOO_SMALL, if they did then we
-        *      clear out reauth information and enter the
-        *      challenge state.
-        */
-       if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_counter_too_small)) {
-               RWDEBUG("Peer sent AT_COUNTER_TOO_SMALL (indicating our AT_COUNTER value (%u) wasn't fresh)",
-                       eap_aka_sim_session->keys.reauth.counter);
-
-               fr_aka_sim_vector_umts_reauth_clear(&eap_aka_sim_session->keys);
-               eap_aka_sim_session->allow_encrypted = false;
-
-               return aka_challenge_enter(p_result, mctx, request, eap_session);
-       }
-
-       /*
-        *      If the peer wants a Success notification, and
-        *      we included AT_RESULT_IND then send a success
-        *      notification, otherwise send a normal EAP-Success.
-        *
-        *      RFC 4187 Section #6.2. Result Indications
-        */
-       if (eap_aka_sim_session->send_result_ind) {
-               if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-                       RDEBUG("We wanted to use protected result indications, but peer does not");
-                       eap_aka_sim_session->send_result_ind = false;
-               } else {
-                       return common_success_notification_enter(p_result, mctx, request, eap_session);
-               }
-       } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-               RDEBUG("Peer wanted to use protected result indications, but we do not");
-       }
-
-       eap_aka_sim_session->reauthentication_success = true;
-
-       return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/AKA-Challenge message - The response to our EAP-Request/AKA-Challenge message
- *
- * Verify that MAC, and RES match what we expect.
- */
-static unlang_action_t aka_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                     request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
-       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
-       ssize_t                 slen;
-       fr_pair_t               *vp = NULL, *mac, *checkcode;
-       fr_pair_list_t          *from_peer = &request->request_pairs;
-
-       mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
-       if (!mac) {
-               REDEBUG("Missing AT_MAC attribute");
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
-               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
-                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
-               goto failure;
-       }
-
-       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
-                                            eap_aka_sim_session->mac_md,
-                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
-                                            NULL, 0);
-       if (slen < 0) {
-               RPEDEBUG("Failed calculating MAC");
-               goto failure;
-       } else if (slen == 0) {
-               REDEBUG("Zero length AT_MAC attribute");
-               goto failure;
-       }
-
-       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
-               RDEBUG2("Received MAC matches calculated MAC");
-       } else {
-               REDEBUG("Received MAC does not match calculated MAC");
-               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
-               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
-               goto failure;
-       }
-
-       /*
-        *      If the peer doesn't include a checkcode then that
-        *      means they don't support it, and we can't validate
-        *      their view of the identity packets.
-        */
-       if (eap_aka_sim_session->checkcode_md) {
-               checkcode = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_checkcode);
-               if (checkcode) {
-                       if (checkcode->vp_length != eap_aka_sim_session->checkcode_len) {
-                               REDEBUG("Received checkcode's length (%zu) does not match "
-                                       "calculated checkcode's length (%zu)",
-                                       checkcode->vp_length, eap_aka_sim_session->checkcode_len);
-                               goto failure;
-                       }
-
-                       if (memcmp(checkcode->vp_octets,
-                                  eap_aka_sim_session->checkcode, eap_aka_sim_session->checkcode_len) == 0) {
-                               RDEBUG2("Received checkcode matches calculated checkcode");
-                       } else {
-                               REDEBUG("Received checkcode does not match calculated checkcode");
-                               RHEXDUMP_INLINE2(checkcode->vp_octets, checkcode->vp_length, "Received");
-                               RHEXDUMP_INLINE2(eap_aka_sim_session->checkcode,
-                                               eap_aka_sim_session->checkcode_len, "Expected");
-                               goto failure;
-                       }
-               /*
-                *      Only print something if we calculated a checkcode
-                */
-               } else if (eap_aka_sim_session->checkcode_len > 0){
-                       RDEBUG2("Peer didn't include AT_CHECKCODE, skipping checkcode validation");
-               }
-       }
-
-       vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_res);
-       if (!vp) {
-               REDEBUG("AT_RES missing from challenge response");
-               goto failure;
-       }
-
-       if (vp->vp_length != eap_aka_sim_session->keys.umts.vector.xres_len) {
-               REDEBUG("Received RES' length (%zu) does not match calculated XRES' length (%zu)",
-                       vp->vp_length, eap_aka_sim_session->keys.umts.vector.xres_len);
-               goto failure;
-       }
-
-       if (memcmp(vp->vp_octets, eap_aka_sim_session->keys.umts.vector.xres, vp->vp_length)) {
-               REDEBUG("Received RES does not match calculated XRES");
-               RHEXDUMP_INLINE2(vp->vp_octets, vp->vp_length, "RES  :");
-               RHEXDUMP_INLINE2(eap_aka_sim_session->keys.umts.vector.xres,
-                               eap_aka_sim_session->keys.umts.vector.xres_len, "XRES :");
-               goto failure;
-       }
-
-       RDEBUG2("Received RES matches calculated XRES");
-
-       eap_aka_sim_session->challenge_success = true;
-
-       /*
-        *      If the peer wants a Success notification, and
-        *      we included AT_RESULT_IND then send a success
-        *      notification, otherwise send a normal EAP-Success.
-        *
-        *      RFC 4187 Section #6.2. Result Indications
-        */
-       if (eap_aka_sim_session->send_result_ind) {
-               if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-                       RDEBUG("We wanted to use protected result indications, but peer does not");
-                       eap_aka_sim_session->send_result_ind = false;
-               } else {
-                       return common_success_notification_enter(p_result, mctx, request, eap_session);
-               }
-       } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-               RDEBUG("Peer wanted to use protected result indications, but we do not");
-       }
-
-       return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/SIM-Challenge message - The response to our EAP-Request/SIM-Challenge message
- *
- * Verify that MAC, and RES match what we expect.
- */
-static unlang_action_t sim_challenge_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                     request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
-       uint8_t                 sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
-       uint8_t                 *p = sres_cat;
-
-       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
-       ssize_t                 slen;
-       fr_pair_t               *mac;
-       fr_pair_list_t          *from_peer = &request->request_pairs;
-
-       memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
-       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
-       memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
-       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
-       memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
-
-       mac = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_mac);
-       if (!mac) {
-               REDEBUG("Missing AT_MAC attribute");
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
-               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
-                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
-               goto failure;
-       }
-
-       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
-                                            eap_aka_sim_session->mac_md,
-                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
-                                            sres_cat, sizeof(sres_cat));
-       if (slen < 0) {
-               RPEDEBUG("Failed calculating MAC");
-               goto failure;
-       } else if (slen == 0) {
-               REDEBUG("Zero length AT_MAC attribute");
-               goto failure;
-       }
-
-       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
-               RDEBUG2("Received MAC matches calculated MAC");
-       } else {
-               REDEBUG("Received MAC does not match calculated MAC");
-               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
-               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
-               goto failure;
-       }
-
-       eap_aka_sim_session->challenge_success = true;
-
-       /*
-        *      If the peer wants a Success notification, and
-        *      we included AT_RESULT_IND then send a success
-        *      notification, otherwise send a normal EAP-Success.
-        */
-       if (eap_aka_sim_session->send_result_ind) {
-               if (!fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-                       RDEBUG("We wanted to use protected result indications, but peer does not");
-                       eap_aka_sim_session->send_result_ind = false;
-               } else {
-                       return common_success_notification_enter(p_result, mctx, request, eap_session);
-               }
-       } else if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_result_ind)) {
-               RDEBUG("Peer wanted to use protected result indications, but we do not");
-       }
-
-       return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Process a EAP-Response/AKA-Identity message i.e. an EAP-AKA Identity-Response
- *
- * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
- * - If the user requested another identity, re-enter the AKA-Identity sate.
- * - ...or continue based on the value of &Identity-Type which was added by #aka_identity,
- *   and possibly modified by the user.
- *   - Fastauth - Enter the REAUTHENTICATION state.
- *   - Pseudonym - Call 'load pseudonym { ... }'
- *   - Permanent - Enter the CHALLENGE state.
- */
-static unlang_action_t aka_identity_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                    request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       bool                    user_set_id_req;
-       fr_pair_t               *identity_type;
-       fr_pair_list_t          *from_peer = &request->request_pairs;
-       /*
-        *      Digest the identity response
-        */
-       if (eap_aka_sim_session->checkcode_md) {
-               if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
-                                                      eap_session->this_round->response) < 0) {
-                       RPEDEBUG("Failed updating checkcode");
-               failure:
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-               }
-       }
-
-       /*
-        *      See if the user wants us to request another
-        *      identity.
-        *
-        *      If they set one themselves don't override
-        *      what they set.
-        */
-       user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
-       if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
-               if (!user_set_id_req) {
-                       switch (eap_aka_sim_session->last_id_req) {
-                       case AKA_SIM_ANY_ID_REQ:
-                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
-                               break;
-
-                       case AKA_SIM_FULLAUTH_ID_REQ:
-                               eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
-                               break;
-
-                       case AKA_SIM_NO_ID_REQ: /* Should not happen */
-                               fr_assert(0);
-                               FALL_THROUGH;
-
-                       case AKA_SIM_PERMANENT_ID_REQ:
-                               REDEBUG("Peer sent no usable identities");
-                               goto failure;
-
-                       }
-                       RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
-                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
-                               fr_table_str_by_value(fr_aka_sim_id_request_table,
-                                                     eap_aka_sim_session->id_req, "<INVALID>"));
-               }
-               return aka_identity_enter(p_result, mctx, request, eap_session);
-       }
-
-       /*
-        *      If the identity looks like a fast re-auth id
-        *      run fast re-auth, otherwise do fullauth.
-        */
-       identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
-       if (identity_type) switch (identity_type->vp_uint32) {
-       case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
-               return common_reauthentication_enter(p_result, mctx, request, eap_session);
-
-       /*
-        *      It's a pseudonym, which now needs resolving.
-        *      The resume function here calls aka_challenge_enter
-        *      if pseudonym resolution went ok.
-        */
-       case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.load_pseudonym,
-                                                     RLM_MODULE_NOOP,
-                                                     pseudonym_load_resume,
-                                                     mod_signal,
-                                                     (void *)aka_challenge_enter);
-       default:
-               break;
-       }
-
-       return aka_challenge_enter(p_result, mctx, request, eap_session);
-}
-
-/** Helper function to check for the presence and length of AT_SELECTED_VERSION and copy its value into the keys structure
- *
- * Also checks the version matches one of the ones we advertised in our version list,
- * which is a bit redundant seeing as there's only one version of EAP-SIM.
- */
-static int sim_start_selected_version_check(request_t *request, fr_pair_list_t *from_peer,
-                                           eap_aka_sim_session_t *eap_aka_sim_session)
-{
-       fr_pair_t               *selected_version_vp;
-
-       /*
-        *      Check that we got an AT_SELECTED_VERSION
-        */
-       selected_version_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version);
-       if (!selected_version_vp) {
-               REDEBUG("EAP-Response/SIM/Start does not contain AT_SELECTED_VERSION");
-               return -1;
-       }
-
-       /*
-        *      See if the selected version was in our list
-        */
-       {
-               uint8_t selected_version[2];
-               uint8_t *p, *end;
-               bool found = false;
-
-               p = eap_aka_sim_session->keys.gsm.version_list;
-               end = p + eap_aka_sim_session->keys.gsm.version_list_len;
-
-               selected_version[0] = (selected_version_vp->vp_uint16 & 0xff00) >> 8;
-               selected_version[1] = (selected_version_vp->vp_uint16 & 0x00ff);
-
-               while (p < end) {
-                       if ((p[0] == selected_version[0]) && (p[1] == selected_version[1])) {
-                               found = true;
-                               /*
-                                *      Update our keying material
-                                */
-                               eap_aka_sim_session->keys.gsm.version_select[0] = selected_version[0];
-                               eap_aka_sim_session->keys.gsm.version_select[1] = selected_version[1];
-                               break;
-                       }
-               }
-
-               if (!found) {
-                       REDEBUG("AT_SELECTED_VERSION (%u) does not match a value in our version list",
-                               selected_version_vp->vp_uint16);
-                       return -1;
-               }
-       }
-
-       return 0;
-}
-
-/** Helper function to check for the presence and length of AT_NONCE_MT and copy its value into the keys structure
- *
- * Does not actually perform cryptographic validation of AT_NONCE_MT, this is done later.
- */
-static int sim_start_nonce_mt_check(request_t *request, fr_pair_list_t *from_peer,
-                                   eap_aka_sim_session_t *eap_aka_sim_session)
-{
-       fr_pair_t       *nonce_mt_vp;
-
-       /*
-        *      Copy nonce_mt to the keying material
-        */
-       nonce_mt_vp = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt);
-       if (!nonce_mt_vp) {
-               REDEBUG("EAP-Response/SIM/Start does not contain AT_NONCE_MT");
-               return -1;
-       }
-
-       if (nonce_mt_vp->vp_length != sizeof(eap_aka_sim_session->keys.gsm.nonce_mt)) {
-               REDEBUG("AT_NONCE_MT must be exactly %zu bytes, not %zu bytes",
-                       sizeof(eap_aka_sim_session->keys.gsm.nonce_mt), nonce_mt_vp->vp_length);
-               return -1;
-       }
-       memcpy(eap_aka_sim_session->keys.gsm.nonce_mt, nonce_mt_vp->vp_octets,
-              sizeof(eap_aka_sim_session->keys.gsm.nonce_mt));
+       case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE:
+       {
+               uint64_t        new_sqn;
 
-       return 0;
-}
+               eap_aka_sim_session->allow_encrypted = false;
 
-/** Process a EAP-Response/SIM-Start message i.e. an EAP-SIM Identity-Response
- *
- * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
- * - If the user requested another identity, re-enter the SIM-START sate.
- * - ...or continue based on the value of &Identity-Type which was added by #sim_start,
- *   and possibly modified by the user.
- *   - Fastauth
- *     - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state.
- *     - ...or enter the REAUTHENTICATION state.
- *   - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }'
- *   - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state.
- */
-static unlang_action_t sim_start_response_process(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, eap_session_t *eap_session)
-{
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-       bool                    user_set_id_req;
-       fr_pair_t               *identity_type;
+               vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_auts);
+               if (!vp) {
+                       REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS");
+               failure:
+                       return STATE_TRANSITION(common_failure_notification);
+               }
 
-       fr_pair_list_t          *from_peer = &request->request_pairs;
+               switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn,
+                                                         request, vp, &eap_aka_sim_session->keys)) {
+               /*
+                *      Add everything back that we'll need in the
+                *      next challenge round.
+                */
+               case 0:
+                       MEM(pair_append_control(&vp, attr_sim_sqn) >= 0);
+                       vp->vp_uint64 = new_sqn;
 
-       /*
-        *      See if the user wants us to request another
-        *      identity.
-        *
-        *      If they set one themselves don't override
-        *      what they set.
-        */
-       user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
-       if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
-               if (!user_set_id_req) {
-                       switch (eap_aka_sim_session->last_id_req) {
-                       case AKA_SIM_ANY_ID_REQ:
-                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
-                               break;
+                       MEM(pair_append_control(&vp, attr_sim_ki) >= 0);
+                       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki,
+                                            sizeof(eap_aka_sim_session->keys.auc.ki), false);
 
-                       case AKA_SIM_FULLAUTH_ID_REQ:
-                               eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
-                               break;
+                       MEM(pair_append_control(&vp, attr_sim_opc) >= 0);
+                       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc,
+                                            sizeof(eap_aka_sim_session->keys.auc.opc), false);
+                       break;
 
-                       case AKA_SIM_NO_ID_REQ: /* Should not happen */
-                               fr_assert(0);
-                               FALL_THROUGH;
+               case 1: /* Don't have Ki or OPc so something else will need to deal with this */
+                       break;
 
-                       case AKA_SIM_PERMANENT_ID_REQ:
-                               REDEBUG("Peer sent no usable identities");
-                       failure:
-                               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-                       }
-                       RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
-                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
-                               fr_table_str_by_value(fr_aka_sim_id_request_table,
-                                                     eap_aka_sim_session->id_req, "<INVALID>"));
+               default:
+               case -1:
+                       goto failure;
                }
-               return sim_start_enter(p_result, mctx, request, eap_session);
+
+               return CALL_SECTION(recv_aka_syncronization_failure);
        }
 
        /*
-        *      If the identity looks like a fast re-auth id
-        *      run fast re-auth, otherwise do fullauth.
+        *      Case 1 where we're allowed to send an EAP-Failure
         */
-       identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
-       if (identity_type) switch (identity_type->vp_uint32) {
-       case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
-               /*
-                *  RFC 4186 Section #9.2
-                *
-                *  The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY
-                *  with a fast re-authentication identity is present for fast
-                *  re-authentication
-                */
-               if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_nonce_mt)) {
-                       REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages");
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-               }
-
-               /*
-                *  RFC 4186 Section #9.2
-                *
-                *  The AT_SELECTED_VERSION attribute MUST NOT be included if the
-                *  AT_IDENTITY attribute with a fast re-authentication identity is
-                *  present for fast re-authentication.
-                */
-               if (fr_pair_find_by_da(from_peer, attr_eap_aka_sim_selected_version)) {
-                       REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages");
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-               }
-
-               return common_reauthentication_enter(p_result, mctx, request, eap_session);
+       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+               client_error_debug(request);
 
-       /*
-        *      It's a pseudonym, which now needs resolving.
-        *      The resume function here calls aka_challenge_enter
-        *      if pseudonym resolution went ok.
-        */
-       case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
-               if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
-               if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
+               eap_aka_sim_session->allow_encrypted = false;
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.load_pseudonym,
-                                                     RLM_MODULE_NOOP,
-                                                     pseudonym_load_resume,
-                                                     mod_signal,
-                                                     (void *)sim_challenge_enter);
+               return CALL_SECTION(recv_common_client_error);
 
        /*
-        *      If it's a permanent ID, copy it over to
-        *      the session state list for use in the
-        *      store pseudonym/store session sections
-        *      later.
+        *      RFC 4187 says we *MUST* notify, not just
+        *      send an EAP-Failure in this case.
         */
-       case FR_IDENTITY_TYPE_VALUE_PERMANENT:
-               if (sim_start_selected_version_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
-               if (sim_start_nonce_mt_check(request, from_peer, eap_aka_sim_session) < 0) goto failure;
-
-               FALL_THROUGH;
        default:
-               break;
+               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+       fail:
+               eap_aka_sim_session->allow_encrypted = false;
+               goto failure;
        }
-
-       return sim_challenge_enter(p_result, mctx, request, eap_session);
 }
 
-/** Resume after 'recv Failure-Notification-Ack { ... }'
+/** Resume after 'send Challenge-Request { ... }'
  *
- * - Enter the EAP-FAILURE state.
  */
-static unlang_action_t common_failure_notification_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                              request_t *request, UNUSED void *rctx)
+RESUME(send_aka_challenge_request)
 {
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-
-       section_rcode_ignored(request);
-
-       /*
-        *      Case 2 where we're allowed to send an EAP-Failure
-        */
-       return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       fr_pair_t               *vp;
+       fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
 
-/** Resume after 'recv Success-Notification-Ack { ... }'
- *
- * - Enter the EAP-SUCCESS state.
- */
-static unlang_action_t common_success_notification_ack_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                                  request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       fr_pair_t               *kdf_id;
 
-       section_rcode_ignored(request);
+       SECTION_RCODE_PROCESS;
 
        /*
-        *      RFC 4187 says we ignore the contents of the
-        *      next packet after we send our success notification
-        *      and always send a success.
+        *      Allow override of KDF Identity
+        *
+        *      Because certain handset manufacturers don't
+        *      implement RFC 4187 correctly and use the
+        *      wrong identity as input the the PRF/KDF.
         */
-       return common_eap_success_enter(p_result, mctx, request, eap_session);
-}
-
-/** Resume after 'recv Challenge-Response { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the AKA-Challenge message.
- */
-static unlang_action_t aka_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                         request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return aka_challenge_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Challenge-Response { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the SIM-Challenge message.
- */
-static unlang_action_t sim_challenge_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                         request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return sim_challenge_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value.
- */
-static unlang_action_t aka_identity_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                        request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
-
-       return aka_identity_response_process(p_result, mctx,request, eap_session);
-}
-
-/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }'
- *
- * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value.
- */
-static unlang_action_t sim_start_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                     request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
+       kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+       if (kdf_id) {
+               crypto_identity_set(request, eap_aka_sim_session,
+                                   (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+               fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+       }
 
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+       RDEBUG2("Acquiring UMTS vector(s)");
 
-       return sim_start_response_process(p_result, mctx,request, eap_session);
-}
+       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) {
+               /*
+                *      Copy the network name the user specified for
+                *      key derivation purposes.
+                */
+               vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input);
+               if (vp) {
+                       talloc_free(eap_aka_sim_session->keys.network);
+                       eap_aka_sim_session->keys.network = talloc_memdup(eap_aka_sim_session,
+                                                                         (uint8_t const *)vp->vp_strvalue,
+                                                                         vp->vp_length);
+                       eap_aka_sim_session->keys.network_len = vp->vp_length;
+               } else {
+                       REDEBUG("No network name available, can't set AT_KDF_INPUT");
+               failure:
+                       return STATE_TRANSITION(common_failure_notification);
+               }
 
-/** Resume after 'recv Authentication-Reject { ... }'
- *
- * - Enter the FAILURE-NOTIFICATION state.
- */
-static unlang_action_t aka_authentication_reject_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                            request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
+               /*
+                *      We don't allow the user to specify
+                *      the KDF currently.
+                */
+               MEM(pair_update_reply(&vp, attr_eap_aka_sim_kdf) >= 0);
+               vp->vp_uint16 = eap_aka_sim_session->kdf;
+       }
 
-       section_rcode_ignored(request);
+       /*
+        *      Get vectors from attribute or generate
+        *      them using COMP128-* or Milenage.
+        */
+       if (fr_aka_sim_vector_umts_from_attrs(request, &request->control_pairs,
+                                             &eap_aka_sim_session->keys, &src) != 0) {
+               REDEBUG("Failed retrieving UMTS vectors");
+               goto failure;
+       }
 
        /*
-        *      Case 2 where we're allowed to send an EAP-Failure
+        *      Don't leave the AMF hanging around
         */
-       return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA_PRIME) pair_delete_control(attr_sim_amf);
 
-/** Resume after 'recv Synchronization-Failure { ... }'
- *
- * - If 'recv Synchronization-Failure { ... }' returned a failure
- *   rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or if no 'recv Syncronization-Failure { ... }' section was
- *   defined, then enter the FAILURE-NOTIFICATION state.
- * - ...or if the user didn't provide a new SQN value in &control.SQN
- *   then enter the FAILURE-NOTIFICATION state.
- * - ...or enter the AKA-CHALLENGE state.
- */
-static unlang_action_t aka_synchronization_failure_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                          request_t *request, UNUSED void *rctx)
-{
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-       fr_pair_t                       *vp;
+       /*
+        *      All set, calculate keys!
+        */
+       switch (eap_aka_sim_session->type) {
+       default:
+       case FR_EAP_METHOD_SIM:
+               fr_assert(0);   /* EAP-SIM has its own Challenge state */
+               break;
+
+       case FR_EAP_METHOD_AKA:
+               fr_aka_sim_crypto_umts_kdf_0(&eap_aka_sim_session->keys);
+               break;
+
+       case FR_EAP_METHOD_AKA_PRIME:
+               switch (eap_aka_sim_session->kdf) {
+               case FR_KDF_VALUE_PRIME_WITH_CK_PRIME_IK_PRIME:
+                       fr_aka_sim_crypto_umts_kdf_1(&eap_aka_sim_session->keys);
+                       break;
 
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+               default:
+                       fr_assert(0);
+                       break;
+               }
+       }
+       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
 
        /*
-        *      If there's no section to handle this, then no resynchronisation
-        *      can't have occurred and we just send a reject.
+        *      Indicate we'd like to use protected success messages
+        *      with AT_RESULT_IND
         *
-        *      Similarly, if we've already received one synchronisation failure
-        *      then it's highly likely whatever user configured action was
-        *      configured was unsuccessful, and we should just give up.
+        *      Use our default, but allow user override too.
         */
-       if (!inst->actions.aka.recv_syncronization_failure || eap_aka_sim_session->prev_recv_sync_failure) {
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
+       if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
 
        /*
-        *      We couldn't generate an SQN and the user didn't provide one,
-        *      so we need to fail.
+        *      These attributes are only allowed with
+        *      EAP-AKA', protect users from themselves.
         */
-       vp = fr_pair_find_by_da(&request->control_pairs, attr_sim_sqn);
-       if (!vp) {
-               REDEBUG("No &control.SQN value provided after resynchronisation, cannot continue");
-               goto failure;
+       if (eap_aka_sim_session->type == FR_EAP_METHOD_AKA) {
+               pair_delete_reply(attr_eap_aka_sim_kdf_input);
+               pair_delete_reply(attr_eap_aka_sim_kdf);
        }
 
        /*
-        *      RFC 4187 Section #6.3.1
-        *
-        *      "if the peer detects that the
-        *      sequence number in AUTN is not correct, the peer responds with
-        *      EAP-Response/AKA-Synchronization-Failure (Section 9.6), and the
-        *      server proceeds with a new EAP-Request/AKA-Challenge."
+        *      Okay, we got the challenge! Put it into an attribute.
         */
-       return aka_challenge_enter(p_result, mctx, request, eap_session);
-}
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.rand, AKA_SIM_VECTOR_UMTS_RAND_SIZE, false);
 
-/** Resume after 'recv Client-Error { ... }'
- *
- * - Enter the EAP-FAILURE state.
- */
-static unlang_action_t common_client_error_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                  request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       /*
+        *      Send the AUTN value to the client, so it can authenticate
+        *      whoever has knowledge of the Ki.
+        */
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_autn) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.umts.vector.autn, AKA_SIM_VECTOR_UMTS_AUTN_SIZE, false);
+
+       /*
+        *      need to include an AT_MAC attribute so that it will get
+        *      calculated.
+        */
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+       fr_pair_value_memdup(vp, NULL, 0, false);
 
-       section_rcode_ignored(request);
+#if 0
+       /*
+        *      If we have checkcode data, send that to the peer
+        *      in AT_CHECKCODE for validation.
+        */
+       if (eap_aka_sim_session->checkcode_state) {
+               ssize_t slen;
 
-       return common_eap_failure_enter(p_result, mctx, request, eap_session);
-}
+               slen = fr_aka_sim_crypto_finalise_checkcode(eap_aka_sim_session->checkcode,
+                                                           &eap_aka_sim_session->checkcode_state);
+               if (slen < 0) {
+                       RPEDEBUG("Failed calculating checkcode");
+                       goto failure;
+               }
+               eap_aka_sim_session->checkcode_len = slen;
 
-/** Resume after 'recv Reauthentication-Response { ... }'
- *
- * - If 'recv Reauthentication-Response { ... }' returned a failure
- *   rcode, enter the FAILURE-NOTIFICATION state.
- * - ...or call the EAP-Request/Reauthentication-Response function to act on the
- *   contents of the response.
- */
-static unlang_action_t common_reauthentication_response_recv_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                                   request_t *request, UNUSED void *rctx)
-{
-       eap_session_t                           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t                   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                            eap_aka_sim_session_t);
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+               MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+               fr_pair_value_memdup(vp, eap_aka_sim_session->checkcode, slen, false);
+       /*
+        *      If we don't have checkcode data, then we exchanged
+        *      no identity packets, so AT_CHECKCODE is zero.
+        */
+       } else {
+               MEM(pair_update_reply(&vp, attr_eap_aka_sim_checkcode) >= 0);
+               fr_pair_value_memdup(vp, NULL, 0, false);
+               eap_aka_sim_session->checkcode_len = 0;
+       }
+#endif
+
+       /*
+        *      We've sent the challenge so the peer should now be able
+        *      to accept encrypted attributes.
+        */
+       eap_aka_sim_session->allow_encrypted = true;
 
-       return common_reauthentication_response_process(p_result, mctx,request, eap_session);
+       return session_and_pseudonym_store(p_result, mctx, request, eap_aka_sim_session, aka_challenge_request_send);
 }
 
-/** Decode the peer's response
+/** Enter the AKA-CHALLENGE state
  *
- * This is called by the state_* functions to decode the peer's response.
  */
-static unlang_action_t common_decode(fr_pair_t **subtype_vp, fr_pair_list_t *vps,
-                                    rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE_GUARD(aka_challenge)
 {
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
-
-       fr_aka_sim_decode_ctx_t ctx = {
-                                       .keys = &eap_aka_sim_session->keys,
-                               };
-       fr_pair_t               *aka_vp;
-       fr_pair_list_t          aka_vps;
-       fr_dcursor_t            cursor;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *vp;
 
-       int                     ret;
+       /*
+        *      If we've sent either of these identities it
+        *      means we've come here form a Reauthentication-Request
+        *      that failed.
+        */
+       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+               return session_and_pseudonym_clear(p_result, mctx, request,
+                                                  eap_aka_sim_session, guard_aka_challenge);
+                                                  /* come back when we're done */
+       }
 
-       fr_pair_list_init(&aka_vps);
-       fr_dcursor_init(&cursor, &request->request_pairs);
-       fr_dcursor_tail(&cursor);
+       STATE_SET(aka_challenge);
 
-       ret = fr_aka_sim_decode(request,
-                               &cursor,
-                               dict_eap_aka_sim,
-                               eap_session->this_round->response->type.data,
-                               eap_session->this_round->response->type.length,
-                               &ctx);
        /*
-        *      RFC 4187 says we *MUST* notify, not just
-        *      send an EAP-Failure in this case where
-        *      we cannot decode an EAP-AKA packet.
+        *      Set some default attributes, giving the user a
+        *      chance to modify them.
         */
-       if (ret < 0) {
-               RPEDEBUG2("Failed decoding attributes");
-       failure:
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
-       }
-       /* vps is the data from the client */
-       aka_vp = fr_dcursor_next(&cursor);
-       fr_pair_sublist_copy(request, &aka_vps, &request->request_pairs, aka_vp);
-       if (!fr_pair_list_empty(&aka_vps) && RDEBUG_ENABLED2) {
-               RDEBUG2("Decoded attributes");
-               log_request_pair_list(L_DBG_LVL_2, request, NULL, &aka_vps, NULL);
+       switch (eap_aka_sim_session->type) {
+       case FR_EAP_METHOD_AKA_PRIME:
+       {
+               uint8_t         amf_buff[2] = { 0x80, 0x00 };   /* Set the AMF separation bit high */
+
+               /*
+                *      Toggle the AMF high bit to indicate we're doing AKA'
+                */
+               MEM(pair_update_control(&vp, attr_sim_amf) >= 0);
+               fr_pair_value_memdup(vp, amf_buff, sizeof(amf_buff), false);
+
+               /*
+                *      Use the default network name we have configured
+                *      and send it to the peer.
+                */
+               if (inst->network_name &&
+                   !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_kdf_input)) {
+                       MEM(pair_append_reply(&vp, attr_eap_aka_sim_kdf_input) >= 0);
+                       fr_pair_value_bstrdup_buffer(vp, inst->network_name, false);
+               }
        }
+               break;
+
+       default:
+               break;
 
-       *subtype_vp = fr_pair_find_by_da(&aka_vps, attr_eap_aka_sim_subtype);
-       if (!*subtype_vp) {
-               REDEBUG("Missing AT_SUBTYPE");
-               goto failure;
        }
-       fr_tmp_pair_list_move(vps, &aka_vps);
 
-       RDEBUG2("Received EAP-Response/%pV", &(*subtype_vp)->data);
+       /*
+        *      Set the defaults for protected result indicator
+        */
+       if (eap_aka_sim_session->send_result_ind &&
+           !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
+               MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
+               vp->vp_bool = true;
+       }
 
-       RETURN_MODULE_OK;
+       return CALL_SECTION(send_aka_challenge_request);
 }
 
-/** FAILURE state - State machine exit point after sending EAP-Failure
+/** Resume after 'recv Challenge-Response { ... }'
  *
- * Should never actually be called. Is just a placeholder function to represent the FAILURE
- * termination state.  Could equally be a NULL pointer, but then on a logic error
- * we'd get a SEGV instead of a more friendly assert/failure rcode.
- */
-static unlang_action_t common_eap_failure(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
-                                         UNUSED request_t *request)
-{
-       fr_assert(0);   /* Should never actually be called */
-       RETURN_MODULE_FAIL;
-}
-
-/** FAILURE-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the SIM-Challenge message.
  *
- * - Continue based on received AT_SUBTYPE value:
- *   - EAP-Response/SIM-Client-Error - Call 'recv Failure-Notification-Ack { ... }'
- *   - Anything else, enter the FAILURE-NOTIFICATION state.
+ * Verify that MAC, and RES match what we expect.
  */
-static unlang_action_t common_failure_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(recv_sim_challenge_response)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_session_t           *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       uint8_t                 sres_cat[AKA_SIM_VECTOR_GSM_SRES_SIZE * 3];
+       uint8_t                 *p = sres_cat;
 
-       fr_pair_t                       *subtype_vp = NULL;
-       fr_pair_list_t                  vps;
+       uint8_t                 calc_mac[AKA_SIM_MAC_DIGEST_SIZE];
+       ssize_t                 slen;
+       fr_pair_t               *mac;
 
-       fr_pair_list_init(&vps);
-       common_decode(&subtype_vp, &vps, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+       SECTION_RCODE_PROCESS;
 
-#ifdef __clang_analyzer__
-       if (!subtype_vp) {
-               RETURN_MODULE_FAIL;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[0].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[1].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+       p += AKA_SIM_VECTOR_GSM_SRES_SIZE;
+       memcpy(p, eap_aka_sim_session->keys.gsm.vector[2].sres, AKA_SIM_VECTOR_GSM_SRES_SIZE);
+
+       mac = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_mac);
+       if (!mac) {
+               REDEBUG("Missing AT_MAC attribute");
+       failure:
+               return STATE_TRANSITION(common_failure_notification);
+       }
+       if (mac->vp_length != AKA_SIM_MAC_DIGEST_SIZE) {
+               REDEBUG("MAC has incorrect length, expected %u bytes got %zu bytes",
+                       AKA_SIM_MAC_DIGEST_SIZE, mac->vp_length);
+               goto failure;
        }
-#endif
 
-       switch (subtype_vp->vp_uint16) {
-       case FR_SUBTYPE_VALUE_AKA_SIM_NOTIFICATION:
-               RDEBUG2("Failure-Notification ACKed, sending EAP-Failure");
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_failure_notification_ack,
-                                                     RLM_MODULE_NOOP,
-                                                     common_failure_notification_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+       // TODO: MAC validation should be in the decoder
+       slen = fr_aka_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
+                                            eap_aka_sim_session->mac_md,
+                                            eap_aka_sim_session->keys.k_aut, eap_aka_sim_session->keys.k_aut_len,
+                                            sres_cat, sizeof(sres_cat));
+       if (slen < 0) {
+               RPEDEBUG("Failed calculating MAC");
+               goto failure;
+       } else if (slen == 0) {
+               REDEBUG("Zero length AT_MAC attribute");
+               goto failure;
+       }
 
-       default:
-               RWDEBUG("Failure-Notification not ACKed correctly, sending EAP-Failure anyway");
-               return common_eap_failure_enter(p_result, mctx, request, eap_session);
+       if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
+               RDEBUG2("Received MAC matches calculated MAC");
+       } else {
+               REDEBUG("Received MAC does not match calculated MAC");
+               RHEXDUMP_INLINE2(mac->vp_octets, AKA_SIM_MAC_DIGEST_SIZE, "Received");
+               RHEXDUMP_INLINE2(calc_mac, AKA_SIM_MAC_DIGEST_SIZE, "Expected");
+               goto failure;
        }
-}
 
-/** SUCCESS state - State machine exit point after sending EAP-Success
- *
- * Should never actually be called. Is just a placeholder function to represent the FAILURE
- * termination state.  Could equally be a NULL pointer, but then on a logic error
- * we'd get a SEGV instead of a more friendly assert/failure rcode.
- */
-static unlang_action_t common_eap_success(rlm_rcode_t *p_result, UNUSED module_ctx_t const *mctx,
-                                         UNUSED request_t *request)
-{
-       fr_assert(0);
-       RETURN_MODULE_FAIL;
-}
+       eap_aka_sim_session->challenge_success = true;
 
-/** SUCCESS-NOTIFICATION state - Continue the state machine after receiving a response to our EAP-Request/(AKA|SIM)-Notification
- *
- * - Call 'recv Success-Notification-Ack { ... }'
- */
-static unlang_action_t common_success_notification(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
-{
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
+       /*
+        *      If the peer wants a Success notification, and
+        *      we included AT_RESULT_IND then send a success
+        *      notification, otherwise send a normal EAP-Success.
+        */
+       if (eap_aka_sim_session->send_result_ind) {
+               if (!fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+                       RDEBUG("We wanted to use protected result indications, but peer does not");
+                       eap_aka_sim_session->send_result_ind = false;
+               } else {
+                       return STATE_TRANSITION(common_success_notification);
+               }
+       } else if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_result_ind)) {
+               RDEBUG("Peer wanted to use protected result indications, but we do not");
+       }
 
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.recv_success_notification_ack,
-                                             RLM_MODULE_NOOP,
-                                             common_success_notification_ack_recv_resume,
-                                             mod_signal,
-                                             NULL);
+       return STATE_TRANSITION(eap_success);
 }
 
-
-/** REAUTHENTICATION state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
+/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
  *
  * - Continue based on received AT_SUBTYPE value:
- *   - EAP-Response/(SIM|AKA)-Reauthentication - call 'recv Reauthentication-Response { ... }'
- *   - EAP-Response/(SIM|AKA)-Client-Error - call 'recv Client-Error { ... }' and after that
- *     send a EAP-Request/(SIM|AKA)-Notification indicating a General Failure.
+ *   - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'.
+ *   - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that
+ *     send a EAP-Request/SIM-Notification indicating a General Failure.
  *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t common_reauthentication(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(sim_challenge)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
-       fr_pair_t                       *subtype_vp = NULL;
-       fr_pair_list_t                  from_peer;
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                 *subtype_vp = NULL;
 
-       fr_pair_list_init(&from_peer);
-       common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-
-#ifdef __clang_analyzer__
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
        if (!subtype_vp) {
-               RETURN_MODULE_FAIL;
+               REDEBUG("Missing AT_SUBTYPE");
+               goto fail;
        }
-#endif
-       /*
-        *      These aren't allowed in Reauthentication responses as they don't apply:
-        *
-        *      EAP_AKA_AUTHENTICATION_REJECT   - We didn't provide an AUTN value
-        *      EAP_AKA_SYNCHRONIZATION_FAILURE - We didn't use new vectors.
-        */
+
        switch (subtype_vp->vp_uint16) {
-       case FR_SUBTYPE_VALUE_AKA_SIM_REAUTHENTICATION:
-               /*
-                *      AT_COUNTER_TOO_SMALL is handled
-                *      in common_reauthentication_response_process.
-                */
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_reauthentication_response,
-                                                     RLM_MODULE_NOOP,
-                                                     common_reauthentication_response_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+       case FR_SUBTYPE_VALUE_SIM_CHALLENGE:
+               return CALL_SECTION(recv_sim_challenge_response);
 
        /*
         *      Case 1 where we're allowed to send an EAP-Failure
         */
        case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
-               client_error_debug(request, &from_peer);
+               client_error_debug(request);
 
                eap_aka_sim_session->allow_encrypted = false;
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_client_error,
-                                                     RLM_MODULE_NOOP,
-                                                     common_client_error_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+               return CALL_SECTION(recv_common_client_error);
 
        /*
-        *      RFC 4187 says we *MUST* notify, not just
+        *      RFC 4186 says we *MUST* notify, not just
         *      send an EAP-Failure in this case.
         */
        default:
                REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+       fail:
                eap_aka_sim_session->allow_encrypted = false;
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+
+               return STATE_TRANSITION(common_failure_notification);
        }
 }
 
-/** AKA-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
+/** Resume after 'send Challenge-Request { ... }'
  *
- * - Continue based on received AT_SUBTYPE value:
- *   - EAP-Response/AKA-Challenge - call 'recv Challenge-Response { ... }'.
- *   - EAP-Response/AKA-Authentication-Reject - call 'recv Authentication-Reject { ... }'  and after that
- *     send a EAP-Request/SIM-Notification indicating a General Failure.
- *   - EAP-Response/AKA-Syncronization-Failure - call 'recv Syncronization-Failure { ... }'.
- *   - EAP-Response/AKA-Client-Error - call 'recv Client-Error { ... }' and after that
- *     send a EAP-Request/AKA-Notification indicating a General Failure.
- *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t aka_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(send_sim_challenge_request)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
-       fr_pair_t               *subtype_vp = NULL;
        fr_pair_t               *vp;
-       fr_pair_list_t          from_peer;
+       fr_aka_sim_vector_src_t src = AKA_SIM_VECTOR_SRC_AUTO;
+
+       fr_pair_t               *kdf_id;
 
-       fr_pair_list_init(&from_peer);
-       common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+       SECTION_RCODE_PROCESS;
 
-#ifdef __clang_analyzer__
-       if (!subtype_vp) {
+       /*
+        *      Allow override of KDF Identity
+        *
+        *      Because certain handset manufacturers don't
+        *      implement RFC 4187 correctly and use the
+        *      wrong identity as input the the PRF/KDF.
+        */
+       kdf_id = fr_pair_find_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+       if (kdf_id) {
+               crypto_identity_set(request, eap_aka_sim_session,
+                                   (uint8_t const *)kdf_id->vp_strvalue, kdf_id->vp_length);
+               fr_pair_delete_by_da(&request->control_pairs, attr_eap_aka_sim_kdf_identity);
+       }
+
+       RDEBUG2("Acquiring GSM vector(s)");
+       if ((fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 0,
+                                             &eap_aka_sim_session->keys, &src) != 0) ||
+           (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 1,
+                                             &eap_aka_sim_session->keys, &src) != 0) ||
+           (fr_aka_sim_vector_gsm_from_attrs(request, &request->control_pairs, 2,
+                                             &eap_aka_sim_session->keys, &src) != 0)) {
+               REDEBUG("Failed retrieving SIM vectors");
                RETURN_MODULE_FAIL;
        }
-#endif
 
-       switch (subtype_vp->vp_uint16) {
-       case FR_SUBTYPE_VALUE_AKA_CHALLENGE:
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_challenge_response,
-                                                     RLM_MODULE_NOOP,
-                                                     aka_challenge_response_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+       fr_aka_sim_crypto_gsm_kdf_0(&eap_aka_sim_session->keys);
+
+       if (RDEBUG_ENABLED3) fr_aka_sim_crypto_keys_log(request, &eap_aka_sim_session->keys);
 
        /*
-        *      Case 2 where we're allowed to send an EAP-Failure
+        *      Indicate we'd like to use protected success messages
+        *      with AT_RESULT_IND
+        *
+        *      Use our default, but allow user override too.
         */
-       case FR_SUBTYPE_VALUE_AKA_AUTHENTICATION_REJECT:
-               eap_aka_sim_session->allow_encrypted = false;
-
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.aka.recv_authentication_reject,
-                                                     RLM_MODULE_NOOP,
-                                                     aka_authentication_reject_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
-
-       case FR_SUBTYPE_VALUE_AKA_SYNCHRONIZATION_FAILURE:
-       {
-               uint64_t        new_sqn;
+       vp = fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind);
+       if (vp) eap_aka_sim_session->send_result_ind = vp->vp_bool;
 
-               eap_aka_sim_session->allow_encrypted = false;
+       /*
+        *      Okay, we got the challenges! Put them into attributes.
+        */
+       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[0].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
 
-               vp = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_auts);
-               if (!vp) {
-                       REDEBUG("EAP-Response/AKA-Synchronisation-Failure missing AT_AUTS");
-               failure:
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
-               }
+       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[1].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
 
-               switch (fr_aka_sim_umts_resync_from_attrs(&new_sqn,
-                                                         request, vp, &eap_aka_sim_session->keys)) {
-               /*
-                *      Add everything back that we'll need in the
-                *      next challenge round.
-                */
-               case 0:
-                       MEM(pair_append_control(&vp, attr_sim_sqn) >= 0);
-                       vp->vp_uint64 = new_sqn;
+       MEM(pair_append_reply(&vp, attr_eap_aka_sim_rand) >= 0);
+       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.gsm.vector[2].rand, AKA_SIM_VECTOR_GSM_RAND_SIZE, false);
 
-                       MEM(pair_append_control(&vp, attr_sim_ki) >= 0);
-                       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.ki,
-                                            sizeof(eap_aka_sim_session->keys.auc.ki), false);
+       /*
+        *      need to include an AT_MAC attribute so that it will get
+        *      calculated.
+        */
+       MEM(pair_update_reply(&vp, attr_eap_aka_sim_mac) >= 0);
+       fr_pair_value_memdup(vp, NULL, 0, false);
 
-                       MEM(pair_append_control(&vp, attr_sim_opc) >= 0);
-                       fr_pair_value_memdup(vp, eap_aka_sim_session->keys.auc.opc,
-                                            sizeof(eap_aka_sim_session->keys.auc.opc), false);
-                       break;
+       /*
+        *      We've sent the challenge so the peer should now be able
+        *      to accept encrypted attributes.
+        */
+       eap_aka_sim_session->allow_encrypted = true;
 
-               case 1: /* Don't have Ki or OPc so something else will need to deal with this */
-                       break;
+       return session_and_pseudonym_store(p_result, mctx,request, eap_aka_sim_session, sim_challenge_request_send);
+}
 
-               default:
-               case -1:
-                       goto failure;
-               }
+/** Enter the SIM-CHALLENGE state
+ *
+ */
+STATE_GUARD(sim_challenge)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       fr_pair_t                       *vp;
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.aka.recv_syncronization_failure,
-                                                     RLM_MODULE_NOOP,
-                                                     aka_synchronization_failure_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+       /*
+        *      If we've sent either of these identities it
+        *      means we've come here form a Reauthentication-Request
+        *      that failed.
+        */
+       if (eap_aka_sim_session->pseudonym_sent || eap_aka_sim_session->fastauth_sent) {
+               return session_and_pseudonym_clear(p_result, mctx, request,
+                                                  eap_aka_sim_session, guard_sim_challenge);
+                                                  /* come back when we're done */
        }
 
+       STATE_SET(sim_challenge);
+
        /*
-        *      Case 1 where we're allowed to send an EAP-Failure
+        *      Set the defaults for protected result indicator
         */
-       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
-               client_error_debug(request, &from_peer);
+       if (eap_aka_sim_session->send_result_ind &&
+           !fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_result_ind)) {
+               MEM(pair_append_reply(&vp, attr_eap_aka_sim_result_ind) >= 0);
+               vp->vp_bool = true;
+       }
 
-               eap_aka_sim_session->allow_encrypted = false;
+       return CALL_SECTION(send_sim_challenge_request);
+}
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_client_error,
-                                                     RLM_MODULE_NOOP,
-                                                     common_client_error_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+/** Enter the SIM-CHALLENGE or AKA-CHALLENGE state
+ *
+ * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
+ * to enter the correct challenge state.
+ */
+STATE_GUARD(common_challenge)
+{
+       switch (eap_aka_sim_session->type) {
+       case FR_EAP_METHOD_SIM:
+               return STATE_TRANSITION(sim_challenge);
+
+       case FR_EAP_METHOD_AKA:
+       case FR_EAP_METHOD_AKA_PRIME:
+               return STATE_TRANSITION(aka_challenge);
 
-       /*
-        *      RFC 4187 says we *MUST* notify, not just
-        *      send an EAP-Failure in this case.
-        */
        default:
-               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
-               eap_aka_sim_session->allow_encrypted = false;
-               goto failure;
+               break;
        }
+
+       fr_assert(0);
+       RETURN_MODULE_FAIL;
 }
 
-/** SIM-CHALLENGE state - Continue the state machine after receiving a response to our EAP-Request/SIM-Challenge
+/** Resume after 'recv Identity-Response { ... }' or 'recv AKA-Identity { ... }'
  *
- * - Continue based on received AT_SUBTYPE value:
- *   - EAP-Response/SIM-Challenge - call 'recv Challenge-Response { ... }'.
- *   - EAP-Response/SIM-Client-Error - call 'recv Client-Error { ... }' and after that
- *     send a EAP-Request/SIM-Notification indicating a General Failure.
- *   - Anything else, enter the FAILURE-NOTIFICATION state.
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the AKA-Identity message, mainly the AT_IDENTITY value.
+ * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
+ * - If the user requested another identity, re-enter the AKA-Identity sate.
+ * - ...or continue based on the value of &Identity-Type which was added by #aka_identity,
+ *   and possibly modified by the user.
+ *   - Fastauth - Enter the REAUTHENTICATION state.
+ *   - Pseudonym - Call 'load pseudonym { ... }'
+ *   - Permanent - Enter the CHALLENGE state.
  */
-static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+RESUME(recv_aka_identity_response)
 {
-       eap_aka_sim_common_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t           *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                            eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
 
-       fr_pair_t               *subtype_vp = NULL;
-       fr_pair_list_t          from_peer;
+       bool                            user_set_id_req;
+       fr_pair_t                       *identity_type;
 
-       fr_pair_list_init(&from_peer);
-       common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
+       SECTION_RCODE_PROCESS;
 
-#ifdef __clang_analyzer__
-       if (!subtype_vp) {
-               RETURN_MODULE_FAIL;
+#if 0
+       /*
+        *      Digest the identity response
+        */
+       if (eap_aka_sim_session->checkcode_md) {
+               // TODO: Need another way of doing this
+               if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
+                                                      eap_session->this_round->response) < 0) {
+                       RPEDEBUG("Failed updating checkcode");
+               failure:
+                       return STATE_TRANSITION(common_failure_notification);
+               }
        }
 #endif
 
-       switch (subtype_vp->vp_uint16) {
-       case FR_SUBTYPE_VALUE_SIM_CHALLENGE:
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_challenge_response,
-                                                     RLM_MODULE_NOOP,
-                                                     sim_challenge_response_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
-
        /*
-        *      Case 1 where we're allowed to send an EAP-Failure
+        *      See if the user wants us to request another
+        *      identity.
+        *
+        *      If they set one themselves don't override
+        *      what they set.
         */
-       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
-               client_error_debug(request, &from_peer);
+       user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
+       if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
+               if (!user_set_id_req) {
+                       switch (eap_aka_sim_session->last_id_req) {
+                       case AKA_SIM_ANY_ID_REQ:
+                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+                               break;
 
-               eap_aka_sim_session->allow_encrypted = false;
+                       case AKA_SIM_FULLAUTH_ID_REQ:
+                               eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
+                               break;
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_client_error,
-                                                     RLM_MODULE_NOOP,
-                                                     common_client_error_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+                       case AKA_SIM_NO_ID_REQ: /* Should not happen */
+                               fr_assert(0);
+                               FALL_THROUGH;
+
+                       case AKA_SIM_PERMANENT_ID_REQ:
+                               REDEBUG("Peer sent no usable identities");
+                               return STATE_TRANSITION(common_failure_notification);
+
+                       }
+                       RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
+                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
+                               fr_table_str_by_value(fr_aka_sim_id_request_table,
+                                                     eap_aka_sim_session->id_req, "<INVALID>"));
+               }
+               return STATE_TRANSITION(aka_identity);
+       }
 
        /*
-        *      RFC 4186 says we *MUST* notify, not just
-        *      send an EAP-Failure in this case.
+        *      If the identity looks like a fast re-auth id
+        *      run fast re-auth, otherwise do fullauth.
         */
-       default:
-               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+       identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
+       if (identity_type) switch (identity_type->vp_uint32) {
+       case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
+               return STATE_TRANSITION(common_reauthentication);
 
-               eap_aka_sim_session->allow_encrypted = false;
+       /*
+        *      It's a pseudonym, which now needs resolving.
+        *      The resume function here calls aka_challenge_enter
+        *      if pseudonym resolution went ok.
+        */
+       case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
+               eap_aka_sim_session->next = guard_aka_challenge;
+               return CALL_SECTION(load_pseudonym);
 
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+       default:
+               break;
        }
+
+       return STATE_TRANSITION(aka_challenge);
 }
 
 /** AKA-IDENTITY state - Continue the state machine after receiving a response to our EAP-Request/AKA-Identity
@@ -3786,24 +2996,16 @@ static unlang_action_t sim_challenge(rlm_rcode_t *p_result, module_ctx_t const *
  *     send a EAP-Request/SIM-Notification indicating a General Failure.
  *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(aka_identity)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
        fr_pair_t                       *subtype_vp = NULL;
-       fr_pair_list_t                  from_peer;
-
-       fr_pair_list_init(&from_peer);
-       common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
 
-#ifdef __clang_analyzer__
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
        if (!subtype_vp) {
-               RETURN_MODULE_FAIL;
+               REDEBUG("Missing AT_SUBTYPE");
+               goto fail;
        }
-#endif
 
        switch (subtype_vp->vp_uint16) {
        /*
@@ -3814,7 +3016,7 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m
                fr_pair_t               *id;
                fr_aka_sim_id_type_t    type;
 
-               id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity);
+               id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity);
                if (!id) {
                        /*
                         *  9.2.  EAP-Response/Identity
@@ -3825,63 +3027,281 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m
                         *  AT_IDENTITY is defined in Section 4.1.
                         */
                        REDEBUG("EAP-Response/Identity does not contain AT_IDENTITY");
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
+                       return STATE_TRANSITION(common_failure_notification);
+               }
+
+               /*
+                *      Add ID hint attributes to the request to help
+                *      the user make policy decisions.
+                */
+               identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue);
+               if (type == AKA_SIM_ID_TYPE_PERMANENT) {
+                       identity_to_permanent_identity(request, id,
+                                                      eap_aka_sim_session->type,
+                                                      inst->strip_permanent_identity_hint);
+               }
+
+               /*
+                *      Update cryptographic identity
+                */
+               crypto_identity_set(request, eap_aka_sim_session,
+                                   (uint8_t const *)id->vp_strvalue, id->vp_length);
+
+               return unlang_module_yield_to_section(p_result,
+                                                     request,
+                                                     inst->actions.recv_aka_identity_response ?
+                                                               inst->actions.recv_aka_identity_response:
+                                                               inst->actions.recv_common_identity_response,
+                                                     RLM_MODULE_NOOP,
+                                                     resume_recv_aka_identity_response,
+                                                     mod_signal,
+                                                     eap_aka_sim_session);
+       }
+
+       /*
+        *      Case 1 where we're allowed to send an EAP-Failure
+        *
+        *      This can happen in the case of a conservative
+        *      peer, where it refuses to provide the permanent
+        *      identity.
+        */
+       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
+               client_error_debug(request);
+
+               return CALL_SECTION(recv_common_client_error);
+
+       default:
+               /*
+                *      RFC 4187 says we *MUST* notify, not just
+                *      send an EAP-Failure in this case.
+                */
+               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+       fail:
+               return STATE_TRANSITION(common_failure_notification);
+       }
+}
+
+/** Resume after 'send Identity-Request { ... }'
+ *
+ * There are three types of user identities that can be implemented
+ * - Permanent identities such as 0123456789098765@myoperator.com
+ *   Permanent identities can be identified by the leading zero followed by
+ *   by 15 digits (the IMSI number).
+ * - Ephemeral identities (pseudonyms).  These are identities assigned for
+ *   identity privacy so the user can't be tracked.  These can identities
+ *   can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
+ *   document section 14, where a set of up to 16 encryption keys are used
+ *   to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
+ *   randomised and stored in a datastore.
+ * - A fast resumption ID which resolves to data used for fast resumption.
+ *
+ * In order to perform full authentication the original IMSI is required for
+ * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
+ * or can't perform fast resumption, we need to request the full identity from
+ * the supplicant.
+ *
+ */
+RESUME(send_aka_identity_request)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+
+       SECTION_RCODE_PROCESS;
+
+       /*
+        *      Update eap_aka_sim_session->id_req in case the the
+        *      user set attributes in `send Identity-Request { ... }`
+        *      Also removes all existing id_req attributes
+        *      from the reply.
+        */
+       identity_req_set_by_user(request, eap_aka_sim_session);
+
+       /*
+        *      Select the right type of identity request attribute
+        *
+        *      Implement checks on identity request order described
+        *      by RFC4187 section #4.1.5.
+        *
+        *      The internal state machine should always handle this
+        *      correctly, but the user may have other ideas...
+        */
+       if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+               return STATE_TRANSITION(common_failure_notification);
+       }
+       eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+
+       /*
+        *      Encode the packet
+        */
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_AKA_IDENTITY);
+
+#if 0
+       /*
+        *      Digest the packet contents, updating our checkcode.
+        */
+       if (eap_aka_sim_session->checkcode_md) {
+               if (!eap_aka_sim_session->checkcode_state &&
+                   fr_aka_sim_crypto_init_checkcode(eap_aka_sim_session, &eap_aka_sim_session->checkcode_state,
+                                                    eap_aka_sim_session->checkcode_md) < 0) {
+                       RPEDEBUG("Failed initialising checkcode");
+                       goto failure;
+               }
+               // TODO: Need another way of doing this
+               if (fr_aka_sim_crypto_update_checkcode(eap_aka_sim_session->checkcode_state,
+                                                      eap_session->this_round->request) < 0) {
+                       RPEDEBUG("Failed updating checkcode");
+                       goto failure;
+               }
+       }
+#endif
+
+       RETURN_MODULE_HANDLED;
+}
+
+/** Enter the AKA-IDENTITY state
+ *
+ */
+STATE_GUARD(aka_identity)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+       STATE_SET(aka_identity);
+
+       /*
+        *      If we have an send_aka_identity_request section
+        *      then run that, otherwise just run the normal
+        *      identity request section.
+        */
+       return unlang_module_yield_to_section(p_result,
+                                             request,
+                                             inst->actions.send_aka_identity_request ?
+                                                       inst->actions.send_aka_identity_request:
+                                                       inst->actions.send_common_identity_request,
+                                             RLM_MODULE_NOOP,
+                                             resume_send_aka_identity_request,
+                                             mod_signal,
+                                             eap_aka_sim_session);
+}
+
+/** Resume after 'recv Identity-Response { ... }' or 'recv SIM-Start { ... }'
+ *
+ * - If the previous section returned a failure rcode, enter the FAILURE-NOTIFICATION state.
+ * - ...or call a function to process the contents of the SIM-Start message, mainly the AT_IDENTITY value.
+ * - If the message does not contain AT_IDENTITY, then enter the FAILURE-NOTIFICATION state.
+ * - If the user requested another identity, re-enter the SIM-START sate.
+ * - ...or continue based on the value of &Identity-Type which was added by #sim_start,
+ *   and possibly modified by the user.
+ *   - Fastauth
+ *     - If AT_NONCE_MT or AT_SELECTED_VERSION are present, enter the FAILURE-NOTIFICATION state.
+ *     - ...or enter the REAUTHENTICATION state.
+ *   - Pseudonym - Verify selected version and AT_NONCE_MT, then call 'load pseudonym { ... }'
+ *   - Permanent - Verify selected version and AT_NONCE_MT, then enter the CHALLENGE state.
+ */
+RESUME(recv_sim_start_response)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       bool                            user_set_id_req;
+       fr_pair_t                       *identity_type;
+
+       SECTION_RCODE_PROCESS;
+
+       /*
+        *      See if the user wants us to request another
+        *      identity.
+        *
+        *      If they set one themselves don't override
+        *      what they set.
+        */
+       user_set_id_req = identity_req_set_by_user(request, eap_aka_sim_session);
+       if ((request->rcode == RLM_MODULE_NOTFOUND) || user_set_id_req) {
+               if (!user_set_id_req) {
+                       switch (eap_aka_sim_session->last_id_req) {
+                       case AKA_SIM_ANY_ID_REQ:
+                               eap_aka_sim_session->id_req = AKA_SIM_FULLAUTH_ID_REQ;
+                               break;
+
+                       case AKA_SIM_FULLAUTH_ID_REQ:
+                               eap_aka_sim_session->id_req = AKA_SIM_PERMANENT_ID_REQ;
+                               break;
+
+                       case AKA_SIM_NO_ID_REQ: /* Should not happen */
+                               fr_assert(0);
+                               FALL_THROUGH;
+
+                       case AKA_SIM_PERMANENT_ID_REQ:
+                               REDEBUG("Peer sent no usable identities");
+                       failure:
+                               return STATE_TRANSITION(common_failure_notification);
+                       }
+                       RDEBUG2("Previous section returned (%s), requesting next most permissive identity (%s)",
+                               fr_table_str_by_value(rcode_table, request->rcode, "<INVALID>"),
+                               fr_table_str_by_value(fr_aka_sim_id_request_table,
+                                                     eap_aka_sim_session->id_req, "<INVALID>"));
                }
+               return STATE_TRANSITION(sim_start);
+       }
 
+       /*
+        *      If the identity looks like a fast re-auth id
+        *      run fast re-auth, otherwise do fullauth.
+        */
+       identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
+       if (identity_type) switch (identity_type->vp_uint32) {
+       case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
                /*
-                *      Add ID hint attributes to the request to help
-                *      the user make policy decisions.
+                *  RFC 4186 Section #9.2
+                *
+                *  The AT_NONCE_MT attribute MUST NOT be included if the AT_IDENTITY
+                *  with a fast re-authentication identity is present for fast
+                *  re-authentication
                 */
-               identity_hint_pairs_add(&type, NULL, request, id->vp_strvalue);
-               if (type == AKA_SIM_ID_TYPE_PERMANENT) {
-                       identity_to_permanent_identity(request, id,
-                                                      eap_aka_sim_session->type,
-                                                      inst->strip_permanent_identity_hint);
+               if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_nonce_mt)) {
+                       REDEBUG("AT_NONCE_MT is not allowed in EAP-Response/SIM-Reauthentication messages");
+                       return STATE_TRANSITION(common_failure_notification);
                }
 
                /*
-                *      Update cryptographic identity
+                *  RFC 4186 Section #9.2
+                *
+                *  The AT_SELECTED_VERSION attribute MUST NOT be included if the
+                *  AT_IDENTITY attribute with a fast re-authentication identity is
+                *  present for fast re-authentication.
                 */
-               identity_to_crypto_identity(request, eap_aka_sim_session,
-                                           (uint8_t const *)id->vp_strvalue, id->vp_length);
+               if (fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_selected_version)) {
+                       REDEBUG("AT_SELECTED_VERSION is not allowed in EAP-Response/SIM-Reauthentication messages");
+                       return STATE_TRANSITION(common_failure_notification);
+               }
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.aka.recv_aka_identity_response?
-                                                               inst->actions.aka.recv_aka_identity_response:
-                                                               inst->actions.recv_identity_response,
-                                                     RLM_MODULE_NOOP,
-                                                     aka_identity_response_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
-       }
+               return STATE_TRANSITION(common_reauthentication);
 
        /*
-        *      Case 1 where we're allowed to send an EAP-Failure
-        *
-        *      This can happen in the case of a conservative
-        *      peer, where it refuses to provide the permanent
-        *      identity.
+        *      It's a pseudonym, which now needs resolving.
+        *      The resume function here calls aka_challenge_enter
+        *      if pseudonym resolution went ok.
         */
-       case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
-               client_error_debug(request, &from_peer);
+       case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
+               if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure;
+               if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure;
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_client_error,
-                                                     RLM_MODULE_NOOP,
-                                                     common_client_error_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+               eap_aka_sim_session->next = guard_sim_challenge;
+               return CALL_SECTION(load_pseudonym);
+
+       /*
+        *      If it's a permanent ID, copy it over to
+        *      the session state list for use in the
+        *      store pseudonym/store session sections
+        *      later.
+        */
+       case FR_IDENTITY_TYPE_VALUE_PERMANENT:
+               if (sim_start_selected_version_check(request, eap_aka_sim_session) < 0) goto failure;
+               if (sim_start_nonce_mt_check(request, eap_aka_sim_session) < 0) goto failure;
 
+               FALL_THROUGH;
        default:
-               /*
-                *      RFC 4187 says we *MUST* notify, not just
-                *      send an EAP-Failure in this case.
-                */
-               REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+               break;
        }
+
+       return STATE_TRANSITION(sim_challenge);
 }
 
 /** SIM-START state - Continue the state machine after receiving a response to our EAP-Request/SIM-Start
@@ -3897,33 +3317,23 @@ static unlang_action_t aka_identity(rlm_rcode_t *p_result, module_ctx_t const *m
  *     send a EAP-Request/SIM-Notification indicating a General Failure.
  *   - Anything else, enter the FAILURE-NOTIFICATION state.
  */
-static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(sim_start)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
-
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
        fr_pair_t                       *subtype_vp = NULL;
-       fr_pair_list_t                  from_peer;
 
-       fr_pair_list_init(&from_peer);
-       common_decode(&subtype_vp, &from_peer, p_result, mctx, request);
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-
-#ifdef __clang_analyzer__
+       subtype_vp = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_subtype);
        if (!subtype_vp) {
-               RETURN_MODULE_FAIL;
+               REDEBUG("Missing AT_SUBTYPE");
+               goto fail;
        }
-#endif
-
        switch (subtype_vp->vp_uint16) {
        case FR_SUBTYPE_VALUE_SIM_START:
        {
                fr_pair_t               *id;
                fr_aka_sim_id_type_t    type;
 
-               id = fr_pair_find_by_da(&from_peer, attr_eap_aka_sim_identity);
+               id = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity);
                if (!id) {
                        /*
                         *  RFC 4186 Section #9.2
@@ -3934,7 +3344,7 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx
                         *  AT_IDENTITY is defined in Section 4.1.
                         */
                        REDEBUG("EAP-Response/SIM/Start does not contain AT_IDENTITY");
-                       return common_failure_notification_enter(p_result, mctx, request, eap_session);
+                       return STATE_TRANSITION(common_failure_notification);
                }
 
                /*
@@ -3951,18 +3361,18 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx
                /*
                 *      Update cryptographic identity
                 */
-               identity_to_crypto_identity(request, eap_aka_sim_session,
-                                           (uint8_t const *)id->vp_strvalue, id->vp_length);
+               crypto_identity_set(request, eap_aka_sim_session,
+                                   (uint8_t const *)id->vp_strvalue, id->vp_length);
 
                return unlang_module_yield_to_section(p_result,
                                                      request,
-                                                     inst->actions.sim.recv_sim_start_response?
-                                                               inst->actions.sim.recv_sim_start_response:
-                                                               inst->actions.recv_identity_response,
+                                                     inst->actions.recv_sim_start_response?
+                                                               inst->actions.recv_sim_start_response:
+                                                               inst->actions.recv_common_identity_response,
                                                      RLM_MODULE_NOOP,
-                                                     sim_start_response_recv_resume,
+                                                     resume_recv_sim_start_response,
                                                      mod_signal,
-                                                     NULL);
+                                                     eap_aka_sim_session);
        }
 
        /*
@@ -3973,15 +3383,9 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx
         *      identity.
         */
        case FR_SUBTYPE_VALUE_AKA_SIM_CLIENT_ERROR:
-               client_error_debug(request, &from_peer);
+               client_error_debug(request);
 
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.recv_client_error,
-                                                     RLM_MODULE_NOOP,
-                                                     common_client_error_recv_resume,
-                                                     mod_signal,
-                                                     NULL);
+               return CALL_SECTION(recv_common_client_error);
 
        default:
                /*
@@ -3989,8 +3393,140 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx
                 *      send an EAP-Failure in this case.
                 */
                REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+       fail:
+               return STATE_TRANSITION(common_failure_notification);
+       }
+}
+
+/** Resume after 'send Start { ... }'
+ *
+ * Send a EAP-Request/SIM-Start message to the supplicant
+ *
+ * There are three types of user identities that can be implemented
+ * - Permanent identities such as 0123456789098765@myoperator.com
+ *   Permanent identities can be identified by the leading zero followed by
+ *   by 15 digits (the IMSI number).
+ * - Ephemeral identities (pseudonyms).  These are identities assigned for
+ *   identity privacy so the user can't be tracked.  These can identities
+ *   can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
+ *   document section 14, where a set of up to 16 encryption keys are used
+ *   to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
+ *   randomised and stored in a datastore.
+ * - A fast resumption ID which resolves to data used for fast resumption.
+ *
+ * In order to perform full authentication the original IMSI is required for
+ * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
+ * or can't perform fast resumption, we need to request the full identity from
+ * the supplicant.
+ */
+RESUME(send_sim_start)
+{
+       eap_aka_sim_session_t   *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
+       fr_pair_t               *vp;
+       uint8_t                 *p, *end;
+
+       SECTION_RCODE_PROCESS;
+
+       p = eap_aka_sim_session->keys.gsm.version_list;
+       end = p + sizeof(eap_aka_sim_session->keys.gsm.version_list);
+       eap_aka_sim_session->keys.gsm.version_list_len = 0;
+
+       /*
+        *      If the user provided no versions, then
+        *      just add the default (1).
+        */
+       if (!(fr_pair_find_by_da(&request->reply_pairs, attr_eap_aka_sim_version_list))) {
+               MEM(pair_append_reply(&vp, attr_eap_aka_sim_version_list) >= 0);
+               vp->vp_uint16 = EAP_SIM_VERSION;
+       }
+
+       /*
+        *      Iterate over the the versions adding them
+        *      to the version list we use for keying.
+        */
+       for (vp = fr_pair_list_head(&request->reply_pairs);
+            vp;
+            vp = fr_pair_list_next(&request->reply_pairs, vp)) {
+               if (vp->da != attr_eap_aka_sim_version_list) continue;
+
+               if ((end - p) < 2) break;
+
+               /*
+                *      Store as big endian
+                */
+               *p++ = (vp->vp_uint16 & 0xff00) >> 8;
+               *p++ = (vp->vp_uint16 & 0x00ff);
+               eap_aka_sim_session->keys.gsm.version_list_len += sizeof(uint16_t);
+       }
+
+       /*
+        *      Update eap_aka_sim_session->id_req in case the the
+        *      user set attributes in `send Identity-Request { ... }`
+        *      Also removes all existing id_req attributes
+        *      from the reply.
+        */
+       identity_req_set_by_user(request, eap_aka_sim_session);
+
+       /*
+        *      Select the right type of identity request attribute
+        *
+        *      Implement checks on identity request order described
+        *      by RFC4186 section #4.2.5.
+        *
+        *      The internal state machine should always handle this
+        *      correctly, but the user may have other ideas...
+        */
+       if (identity_req_pairs_add(request, eap_aka_sim_session) < 0) {
+               return STATE_TRANSITION(common_failure_notification);
+       }
+       eap_aka_sim_session->last_id_req = eap_aka_sim_session->id_req; /* Record what we last requested */
+
+       common_reply(request, eap_aka_sim_session, FR_SUBTYPE_VALUE_SIM_START);
+
+       RETURN_MODULE_HANDLED;
+}
+
+/** Enter the SIM-START state
+ *
+ */
+STATE_GUARD(sim_start)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+
+       STATE_SET(sim_start);
+
+       return unlang_module_yield_to_section(p_result,
+                                             request,
+                                             inst->actions.send_sim_start_request ?
+                                                       inst->actions.send_sim_start_request:
+                                                       inst->actions.send_common_identity_request,
+                                             RLM_MODULE_NOOP,
+                                             resume_send_sim_start,
+                                             mod_signal,
+                                             eap_aka_sim_session);
+}
+
+/** Enter the SIM-START or AKA-IDENTITY state
+ *
+ * Called by functions which are common to both the EAP-SIM and EAP-AKA state machines
+ * to enter the correct Identity-Request state.
+ */
+STATE_GUARD(common_identity)
+{
+       switch (eap_aka_sim_session->type) {
+       case FR_EAP_METHOD_SIM:
+               return STATE_TRANSITION(sim_start);
+
+       case FR_EAP_METHOD_AKA:
+       case FR_EAP_METHOD_AKA_PRIME:
+               return STATE_TRANSITION(aka_identity);
+
+       default:
+               break;
        }
+
+       fr_assert(0);
+       RETURN_MODULE_FAIL;
 }
 
 /** Resume after 'recv Identity-Response { ... }'
@@ -4002,18 +3538,14 @@ static unlang_action_t sim_start(rlm_rcode_t *p_result, module_ctx_t const *mctx
  *   - If identity is a fastauth identity, enter the REAUTHENTICATE state.
  *   - If identity is a permanent identity, enter the CHALLENGE state.
  */
-static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_ctx_t const *mctx,
-                                                 request_t *request, UNUSED void *rctx)
+RESUME(recv_common_identity_response)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
-       eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(eap_session->opaque,
-                                                                                    eap_aka_sim_session_t);
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t           *eap_aka_sim_session = talloc_get_type_abort(rctx, eap_aka_sim_session_t);
        fr_pair_t                       *eap_type, *method, *identity_type;
        fr_aka_sim_method_hint_t        running, hinted;
-       fr_pair_list_t                  *from_peer = &request->request_pairs;
 
-       section_rcode_process(p_result, mctx,request, eap_session, eap_aka_sim_session);
+       SECTION_RCODE_PROCESS;
 
        /*
         *      Ignore attempts to change the EAP-Type
@@ -4023,7 +3555,7 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
        eap_type = fr_pair_find_by_da(&request->control_pairs, attr_eap_type);
        if (eap_type) RWDEBUG("Ignoring &control.EAP-Type, this must be set *before* the EAP module is called");
 
-       method = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_method_hint);
+       method = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_method_hint);
 
        /*
         *      Set default configuration, we may allow these
@@ -4037,7 +3569,7 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
         *      use it... It has stronger keying, and
         *      binds authentication to the network.
         */
-       switch (eap_session->type) {
+       switch (eap_aka_sim_session->type) {
        case FR_EAP_METHOD_SIM:
                RDEBUG2("New EAP-SIM session");
 
@@ -4045,13 +3577,6 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
 
                eap_aka_sim_session->type = FR_EAP_METHOD_SIM;
                eap_aka_sim_session->mac_md = EVP_sha1();       /* no checkcode support, so no checkcode_md */
-
-               /*
-                *      RFC 5448 makes no mention of being
-                *      able to use this with EAP-SIM, so it's
-                *      permanently disabled for that EAP method.
-                */
-               eap_aka_sim_session->send_at_bidding_prefer_prime = false;
                break;
 
        case FR_EAP_METHOD_AKA:
@@ -4061,7 +3586,6 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
 
                eap_aka_sim_session->type = FR_EAP_METHOD_AKA;
                eap_aka_sim_session->checkcode_md = eap_aka_sim_session->mac_md = EVP_sha1();
-               eap_aka_sim_session->send_at_bidding_prefer_prime = inst->send_at_bidding_prefer_prime;
                break;
 
        case FR_EAP_METHOD_AKA_PRIME:
@@ -4135,18 +3659,16 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
         *      can 'decorate' the identity in the identity
         *      response.
         */
-       if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) {
-               return common_identity_enter(p_result, mctx, request, eap_session);
-       }
+       if (eap_aka_sim_session->id_req != AKA_SIM_NO_ID_REQ) return STATE_TRANSITION(common_identity);
 
        /*
         *      If the identity looks like a fast re-auth id
         *      run fast re-auth, otherwise do a fullauth.
         */
-       identity_type = fr_pair_find_by_da(from_peer, attr_eap_aka_sim_identity_type);
+       identity_type = fr_pair_find_by_da(&request->request_pairs, attr_eap_aka_sim_identity_type);
        if (identity_type) switch (identity_type->vp_uint32) {
        case FR_IDENTITY_TYPE_VALUE_FASTAUTH:
-               return common_reauthentication_enter(p_result, mctx, request, eap_session);
+               return STATE_TRANSITION(common_reauthentication);
 
        /*
         *      It's a pseudonym, which now needs resolving.
@@ -4154,58 +3676,29 @@ static unlang_action_t common_eap_identity_resume(rlm_rcode_t *p_result, module_
         *      if pseudonym resolution went ok.
         */
        case FR_IDENTITY_TYPE_VALUE_PSEUDONYM:
-               return unlang_module_yield_to_section(p_result,
-                                                     request,
-                                                     inst->actions.load_pseudonym,
-                                                     RLM_MODULE_NOOP,
-                                                     pseudonym_load_resume,
-                                                     mod_signal,
-                                                     (void *)common_challenge_enter);
+               eap_aka_sim_session->next = guard_common_challenge;
+               return CALL_SECTION(load_pseudonym);
 
        case FR_IDENTITY_TYPE_VALUE_PERMANENT:
        default:
                break;
        }
 
-       return common_challenge_enter(p_result, mctx, request, eap_session);
-}
-
-/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys
- *
- * @param[in] eap_aka_sim_session      to free.
- * @return 0
- */
-static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session)
-{
-       memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session));
-       return 0;
+       return STATE_TRANSITION(common_challenge);
 }
 
-/** Enter the EAP-IDENTITY state - State machine entry point
+/** Enter the EAP-IDENTITY state
  *
  * - Process the incoming EAP-Identity-Response
  * - Start EAP-SIM/EAP-AKA/EAP-AKA' state machine optionally calling 'recv Identity-Response { ... }'
  */
-unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+STATE(init)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_common_conf_t);
        eap_session_t                   *eap_session = eap_session_get(request->parent);
-       eap_aka_sim_session_t           *eap_aka_sim_session;
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
        fr_pair_t                       *vp;
        fr_aka_sim_id_type_t            type;
 
-
-       MEM(eap_aka_sim_session = talloc_zero(eap_session, eap_aka_sim_session_t));
-       talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free);
-
-       eap_session->opaque = eap_aka_sim_session;
-
-       /*
-        *      This value doesn't have be strong, but it is
-        *      good if it is different now and then.
-        */
-       eap_aka_sim_session->id = (fr_rand() & 0xff);
-
        /*
         *      Verify we received an EAP-Response/Identity
         *      message before the supplicant started sending
@@ -4213,16 +3706,11 @@ unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t
         */
        if (!eap_session->identity) {
                REDEBUG("All SIM or AKA exchanges must begin with a EAP-Response/Identity message");
-               return common_failure_notification_enter(p_result, mctx, request, eap_session);
+               return STATE_TRANSITION(common_failure_notification);
        }
 
        /*
-        *      Add ID hint attributes to the request to help
-        *      the user make policy decisions.
-        */
-
-       /*
-        *      Copy the EAP-Identity into and Identity
+        *      Copy the EAP-Identity into our Identity
         *      attribute to make policies easier.
         */
        MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >= 0);
@@ -4238,19 +3726,68 @@ unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t
                                               inst->strip_permanent_identity_hint);
        }
 
-       identity_to_crypto_identity(request, eap_aka_sim_session,
-                                   (uint8_t const *)eap_session->identity,
-                                   talloc_array_length(eap_session->identity) - 1);
+       /*
+        *      Set the initial crypto identity from
+        *      the EAP-Identity-Response
+        */
+       crypto_identity_set(request, eap_aka_sim_session,
+                           (uint8_t const *)eap_session->identity,
+                           talloc_array_length(eap_session->identity) - 1);
+
+       return CALL_SECTION(recv_common_identity_response);
+}
+
+/** Zero out the eap_aka_sim_session when we free it to clear knowledge of secret keys
+ *
+ * @param[in] eap_aka_sim_session      to free.
+ * @return 0
+ */
+static int _eap_aka_sim_session_free(eap_aka_sim_session_t *eap_aka_sim_session)
+{
+       memset(eap_aka_sim_session, 0, sizeof(*eap_aka_sim_session));
+       return 0;
+}
 
+/** Resumes the state machine when receiving a new response packet
+ *
+ */
+unlang_action_t eap_aka_sim_state_machine_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
+{
+       eap_aka_sim_process_conf_t *inst = talloc_get_type_abort(mctx->instance, eap_aka_sim_process_conf_t);
+       eap_aka_sim_session_t *eap_aka_sim_session = request_data_reference(request,
+                                                                           (void *)eap_aka_sim_state_machine_process,
+                                                                           0);
        /*
-        *      Running the same section as Identity-Response
-        *      makes policies significantly easier.
+        *      A new EAP-SIM/AKA/AKA' session!
         */
-       return unlang_module_yield_to_section(p_result,
-                                             request,
-                                             inst->actions.recv_identity_response,
-                                             RLM_MODULE_NOOP,
-                                             common_eap_identity_resume,
-                                             mod_signal,
-                                             NULL);
+       if (!eap_aka_sim_session) {
+               /*
+                *      Must be allocated in the NULL ctx as this will
+                *      need to persist over multiple rounds of EAP.
+                */
+               MEM(eap_aka_sim_session = talloc_zero(NULL, eap_aka_sim_session_t));
+               talloc_set_destructor(eap_aka_sim_session, _eap_aka_sim_session_free);
+
+               /*
+                *      Add new session data to the request
+                *      We only ever need to do this once as it's restored
+                *      during the next round of EAP automatically.
+                *
+                *      It will also be freed automatically if the request
+                *      is freed and persistable data hasn't been moved
+                *      into the parent.
+                */
+               if (unlikely(request_data_add(request, (void *)eap_aka_sim_state_machine_process, 0,
+                                             eap_aka_sim_session, true, true, true) < 0)) {
+                       RPEDEBUG("Failed creating new EAP-SIM/AKA/AKA' session");
+                       RETURN_MODULE_FAIL;
+               }
+               eap_aka_sim_session->type = inst->type;
+
+               return state_init(p_result, mctx, request, eap_aka_sim_session);
+       }
+
+       if (!fr_cond_assert(eap_aka_sim_session->state)) RETURN_MODULE_FAIL;
+
+       return eap_aka_sim_session->state(p_result, mctx, request, eap_aka_sim_session);
 }
index 6f474af9c463f93c935eaa8a2cbdb292e075c1cf..cd5a46025251b9f731425078db15bcf88da1d1ec 100644 (file)
@@ -28,76 +28,36 @@ RCSIDH(lib_eap_aka_sim_state_machine_h, "$Id$")
 
 #include <freeradius-devel/eap_aka_sim/base.h>
 
-/** Cache sections to call on various protocol events
- *
- */
-typedef struct {
-       union {
-               struct {
-                       CONF_SECTION    *send_aka_identity_request;     //!< Called when we're about to request a
-                                                                       ///< different identity.
-                       CONF_SECTION    *recv_aka_identity_response;    //!< Called when we receive a new identity.
-
-                       CONF_SECTION    *recv_authentication_reject;    //!< Called if the supplicant rejects the
-                                                                       ///< authentication attempt.
-                       CONF_SECTION    *recv_syncronization_failure;   //!< Called if the supplicant determines
-                                                                       ///< the AUTN value is invalid.
-                                                                       ///< Usually used for resyncing with the HLR.
-               } aka;
-
-               struct {
-                       CONF_SECTION    *send_sim_start_request;        //!< Called when we're about to request a
-                                                                       ///< different identity.
-                       CONF_SECTION    *recv_sim_start_response;       //!< Called when we receive a new identity.
-
-               } sim;
-       };
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-       CONF_SECTION    *send_identity_request;         //!< Called when we're about to request a
-                                                       ///< different identity.
-       CONF_SECTION    *recv_identity_response;        //!< Called when we receive a new identity.
+typedef struct eap_aka_sim_session_s eap_aka_sim_session_t;
 
-       CONF_SECTION    *send_challenge_request;        //!< Called when we're about to send a
-                                                       ///< a challenge.
-       CONF_SECTION    *recv_challenge_response;       //!< Called when we receive a response
-                                                       ///< to a previous challenge.
-
-       CONF_SECTION    *send_fast_reauth_request;      //!< Called when we're about to send a
-                                                       ///< Fast-Reauth-Request.
-       CONF_SECTION    *recv_fast_reauth_response;     //!< Called when we receive a response
-                                                       ///< to a previous Fast-Reauth-Request.
-
-       CONF_SECTION    *recv_client_error;             //!< Called if the supplicant experiences
-                                                       ///< an error of some kind.
-
-       CONF_SECTION    *send_reauthentication_request; //!< Challenge the supplicant with an MK
-                                                       ///< from an existing session.
-
-       CONF_SECTION    *recv_reauthentication_response; //!< Process the reauthentication response
-                                                       ///< from the supplicant.
-
-       CONF_SECTION    *send_failure_notification;     //!< Called when we're about to send a
-                                                       ///< failure notification.
-       CONF_SECTION    *send_success_notification;     //!< Called when we're about to send a
-                                                       ///< success notification.
-       CONF_SECTION    *recv_failure_notification_ack; //!< Called when the supplicant ACKs our
-                                                       ///< failure notification.
-       CONF_SECTION    *recv_success_notification_ack; //!< Called when the supplicant ACKs our
-                                                       ///< success notification.
+/** The state function which should receive any incoming packets
+ *
+ */
+typedef unlang_action_t (*eap_aka_sim_state_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx,
+                                              request_t *request, eap_aka_sim_session_t *eap_aka_sim_session);
 
-       CONF_SECTION    *send_eap_success;              //!< Called when we send an EAP-Success message.
-       CONF_SECTION    *send_eap_failure;              //!< Called when we send an EAP-Failure message.
+/** The function to execute after common code has completed
+ *
+ * Mostly used to set functions to run after pseudonym and session
+ * loading/clearing/storing is complete.
+ */
+typedef unlang_action_t (*eap_aka_sim_next_t)(rlm_rcode_t *p_result, module_ctx_t const *mctx,
+                                             request_t *request, eap_aka_sim_session_t *eap_aka_sim_session);
 
-       CONF_SECTION    *load_pseudonym;                //!< Resolve a pseudonym to a permanent ID.
-       CONF_SECTION    *store_pseudonym;               //!< Store a permanent ID to pseudonym mapping.
-       CONF_SECTION    *clear_pseudonym;               //!< Clear pseudonym to permanent ID mapping.
+struct eap_aka_sim_session_s {
+       /*
+        *      State machine management
+        */
+       eap_aka_sim_state_t             state;                          //!< The process function to run when we
+                                                                       ///< receive the next round of EAP-SIM/AKA/AKA'.
 
-       CONF_SECTION    *load_session;                  //!< Load cached authentication vectors.
-       CONF_SECTION    *store_session;                 //!< Store authentication vectors.
-       CONF_SECTION    *clear_session;                 //!< Clear authentication vectors.
-} eap_aka_sim_actions_t;
+       eap_aka_sim_next_t              next;                           //!< Resumption function to call after
+                                                                       ///< executing common code.
 
-typedef struct {
        eap_type_t                      type;                           //!< Either FR_TYPE_AKA, or FR_TYPE_AKA_PRIME.
 
        bool                            challenge_success;              //!< Whether we received the correct
@@ -130,8 +90,6 @@ typedef struct {
        bool                            send_result_ind;                //!< Say that we would like to use protected
                                                                        ///< result indications
                                                                        ///< (AKA-Notification-Success).
-       bool                            send_at_bidding_prefer_prime;   //!< Indicate that we prefer EAP-AKA' and
-                                                                       ///< include an AT_BIDDING attribute.
 
        bool                            prev_recv_sync_failure;         //!< We only allow one sync failure per
                                                                        ///< session for sanity.
@@ -153,30 +111,129 @@ typedef struct {
        EVP_MD const                    *mac_md;                        //!< HMAC-MD we use to generate the MAC.
                                                                        ///< EVP_sha1() for EAP-AKA, EVP_sha256()
                                                                        ///< for EAP-AKA'.
+};
 
-       int                             id;                             //!< Packet ID. (replay protection).
-} eap_aka_sim_session_t;
+/** Cache sections to call on various protocol events
+ *
+ */
+typedef struct {
+       union {
+               /** @name EAP-AKA specific sections
+                *
+                * @{
+                */
+                struct {
+                       CONF_SECTION    *send_aka_identity_request;     //!< Called when we're about to request a
+                                                                       ///< different identity.
+                       CONF_SECTION    *recv_aka_identity_response;    //!< Called when we receive a new identity.
+
+                       CONF_SECTION    *recv_aka_authentication_reject;//!< Called if the supplicant rejects the
+                                                                       ///< authentication attempt.
+                       CONF_SECTION    *recv_aka_syncronization_failure;//!< Called if the supplicant determines
+                                                                       ///< the AUTN value is invalid.
+                                                                       ///< Usually used for resyncing with the HLR.
+
+                       CONF_SECTION    *send_aka_challenge_request;    //!< Called when we're about to send a
+                                                                       ///< a challenge.
+                       CONF_SECTION    *recv_aka_challenge_response;   //!< Called when we receive a response
+                                                                       ///< to a previous challenge.
+               };
+               /** @} */
+
+               /** @name EAP-SIM specific sections
+                *
+                * @{
+                */
+               struct {
+                       CONF_SECTION    *send_sim_challenge_request;    //!< Called when we're about to send a
+                                                                       ///< a challenge.
+                       CONF_SECTION    *recv_sim_challenge_response;   //!< Called when we receive a response
+                                                                       ///< to a previous challenge.
+
+                       CONF_SECTION    *send_sim_start_request;        //!< Called when we're about to request a
+                                                                       ///< different identity.
+                       CONF_SECTION    *recv_sim_start_response;       //!< Called when we receive a new identity.
+               };
+               /** @} */
+       };
+
+       /** @name Common protocol sections for all methods
+        *
+        * @{
+        */
+       CONF_SECTION    *send_common_identity_request;                  //!< Called when we're about to request a
+                                                                       ///< different identity.
+       CONF_SECTION    *recv_common_identity_response;                 //!< Called when we receive a new identity.
+
+       CONF_SECTION    *recv_common_client_error;                      //!< Called if the supplicant experiences
+                                                                       ///< an error of some kind.
+
+       CONF_SECTION    *send_common_reauthentication_request;          //!< Challenge the supplicant with an MK
+                                                                       ///< from an existing session.
+
+       CONF_SECTION    *recv_common_reauthentication_response;         //!< Process the reauthentication response
+                                                                       ///< from the supplicant.
+
+       CONF_SECTION    *recv_common_failure_notification_ack;          //!< Called when the supplicant ACKs our
+                                                                       ///< failure notification.
+
+       CONF_SECTION    *send_common_failure_notification;              //!< Called when we're about to send a
+                                                                       ///< failure notification.
+
+       CONF_SECTION    *recv_common_success_notification_ack;          //!< Called when the supplicant ACKs our
+                                                                       ///< success notification.
+
+       CONF_SECTION    *send_common_success_notification;              //!< Called when we're about to send a
+                                                                       ///< success notification.
+
+
+       CONF_SECTION    *send_eap_success;                              //!< Called when we send an EAP-Success message.
+       CONF_SECTION    *send_eap_failure;                              //!< Called when we send an EAP-Failure message.
+       /** @} */
+
+       /** @name Internal sections for caching
+        *
+        * @{
+        */
+       CONF_SECTION    *load_pseudonym;                                //!< Resolve a pseudonym to a permanent ID.
+       CONF_SECTION    *store_pseudonym;                               //!< Store a permanent ID to pseudonym mapping.
+       CONF_SECTION    *clear_pseudonym;                               //!< Clear pseudonym to permanent ID mapping.
+
+       CONF_SECTION    *load_session;                                  //!< Load cached authentication vectors.
+       CONF_SECTION    *store_session;                                 //!< Store authentication vectors.
+       CONF_SECTION    *clear_session;                                 //!< Clear authentication vectors.
+       /** @} */
+} eap_aka_sim_actions_t;
 
 typedef struct {
+       eap_type_t                      type;                           //!< The preferred EAP-Type of this instance
+                                                                       ///< of the EAP-SIM/AKA/AKA' state machine.
+
        char const                      *network_name;                  //!< Network ID as described by RFC 5448.
        fr_aka_sim_id_req_type_t        request_identity;               //!< Whether we always request the identity of
                                                                        ///< the subscriber.
        size_t                          ephemeral_id_length;            //!< The length of any identities we're
                                                                        ///< generating.
-       CONF_SECTION                    *virtual_server;                //!< Virtual server.
+
        bool                            protected_success;              //!< Send a success notification as well as
                                                                        ///< and EAP-Success packet.
-       bool                            send_at_bidding_prefer_prime;   //!< Include the AT bidding attribute in
-                                                                       ///< challenge requests.
-       bool                            send_at_bidding_prefer_prime_is_set;    //!< Whether the user specified a value.
 
        bool                            strip_permanent_identity_hint;  //!< Control whether the hint byte is stripped
                                                                        ///< when populating Permanent-Identity.
 
+       EVP_MD const                    *hmac_md;                       //!< The hmac used for validating packets.
+                                                                       ///< EVP_sha1() for EAP-AKA, EVP_sha256()
+                                                                       ///< for EAP-AKA'.
+
+       EVP_MD const                    *checkcode_md;                  //!< The hmac used for validating packets
+                                                                       ///< checkcodes.
+
        eap_aka_sim_actions_t           actions;                        //!< Pre-compiled virtual server sections.
-} eap_aka_sim_common_conf_t;
+} eap_aka_sim_process_conf_t;
+
+unlang_action_t eap_aka_sim_state_machine_process(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
+
+#ifdef __cplusplus
+}
+#endif
 
-/*
- *     The main entry point
- */
-unlang_action_t aka_sim_state_machine_start(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request);
index 1ee756d7e73953a398e4b30274bdd4e643022c24..62676e47bda751445316329958da290db047112c 100644 (file)
@@ -21,6 +21,7 @@
  *
  * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  * @copyright 2016 The FreeRADIUS server project
  * @copyright 2016 Network RADIUS SARL (sales@networkradius.com)
  */
@@ -28,134 +29,39 @@ RCSID("$Id$")
 
 #include <freeradius-devel/eap/base.h>
 #include <freeradius-devel/eap_aka_sim/attrs.h>
-#include <freeradius-devel/eap_aka_sim/state_machine.h>
-#include <freeradius-devel/unlang/compile.h>
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/module.h>
 #include <freeradius-devel/unlang/module.h>
 #include <freeradius-devel/util/debug.h>
 
 static CONF_PARSER submodule_config[] = {
-       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ),
-         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
-       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t,
-                        strip_permanent_identity_hint ), .dflt = "yes" },
-       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */
-       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" },
-       { FR_CONF_OFFSET_IS_SET("prefer_aka_prime", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, send_at_bidding_prefer_prime ), .dflt = "no" },
-       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse },
-       CONF_PARSER_TERMINATOR
-};
-
-static virtual_server_compile_t compile_list[] = {
-       /*
-        *      Identity negotiation
-        */
-       EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"),
-       EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"),
-
-       /*
-        *      Optional override sections if the user *really*
-        *      wants to apply special policies for subsequent
-        *      request/response rounds.
-        */
-       EAP_SECTION_DEFINE(aka.send_aka_identity_request, "send", "AKA-Identity-Request"),
-       EAP_SECTION_DEFINE(aka.recv_aka_identity_response, "recv", "AKA-Identity-Response"),
-
-       /*
-        *      Full-Authentication
-        */
-       EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"),
-       EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"),
-
-       /*
-        *      Fast-Reauthentication
-        */
-       EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"),
-       EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"),
+       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse },
+       { FR_CONF_OFFSET_IS_SET("prefer_aka_prime", FR_TYPE_BOOL, eap_aka_sim_module_conf_t, aka.send_at_bidding_prefer_prime ), .dflt = "no" },
 
-       /*
-        *      Failures originating from the supplicant
-        */
-       EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"),
-       EAP_SECTION_DEFINE(aka.recv_authentication_reject, "recv", "Authentication-Reject"),
-       EAP_SECTION_DEFINE(aka.recv_syncronization_failure, "recv", "Syncronization-Failure"),
-
-       /*
-        *      Failure originating from the server
-        */
-       EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"),
-       EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"),
-
-       /*
-        *      Protected success indication
-        */
-       EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"),
-       EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"),
-
-       /*
-        *      Final EAP-Success and EAP-Failure messages
-        */
-       EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"),
-       EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"),
-
-       /*
-        *      Fast-Reauth vectors
-        */
-       EAP_SECTION_DEFINE(store_session, "store", "session"),
-       EAP_SECTION_DEFINE(load_session, "load", "session"),
-       EAP_SECTION_DEFINE(clear_session, "clear", "session"),
-
-       /*
-        *      Pseudonym processing
-        */
-       EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"),
-       EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"),
-       EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"),
-
-       COMPILE_TERMINATOR
+       CONF_PARSER_TERMINATOR
 };
 
+extern rlm_eap_submodule_t rlm_eap_aka;
 
-/** Compile virtual server sections
- *
- * Called twice, once when a server with an eap-aka namespace is found, and once
- * when an EAP-AKA module is instantiated.
- *
- * The first time is with actions == NULL and is to compile the sections and
- * perform validation.
- * The second time is to write out pointers to the compiled sections which the
- * EAP-AKA module will use to execute unlang code.
- *
- */
-static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs)
+static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       int found;
-       tmpl_rules_t parse_rules;
-
-       if (!fr_cond_assert(server_cs)) return -1;
-
-       memset(&parse_rules, 0, sizeof(parse_rules));
-       parse_rules.dict_def = dict_eap_aka_sim;
+       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_mod_session_t       *mod_session;
 
-       found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions);
-       if (found < 0) return -1;
+       MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t));
+       mod_session->id = (uint8_t)(fr_rand() & 0xff);
+       mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha1();
+       eap_session->opaque = mod_session;
+       eap_session->process = eap_aka_sim_process;
 
-       /*
-        *      Warn if we couldn't find any actions.
-        */
-       if (!found) {
-               cf_log_warn(server_cs, "No \"eap-aka\" actions found in virtual server \"%s\"",
-                           cf_section_name2(server_cs));
-       }
-
-       return 0;
+       return eap_session->process(p_result, mctx, request);
 }
 
-
 static int mod_instantiate(void *instance, CONF_SECTION *conf)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t);
+       eap_aka_sim_module_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t);
 
-       if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1;
+       inst->type = rlm_eap_aka.provides[0];
 
        /*
         *      If the user didn't specify a bidding value
@@ -163,14 +69,15 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf)
         *      attribute, by whether the EAP module has
         *      has the AKA-Prime module enabled.
         */
-       if (!inst->send_at_bidding_prefer_prime_is_set) {
+       if (!inst->aka.send_at_bidding_prefer_prime_is_set) {
                CONF_SECTION    *parent = cf_item_to_section(cf_parent(conf));
                CONF_PAIR       *cp = NULL;
 
                while ((cp = cf_pair_find_next(parent, cp, "type"))) {
                        if (strcmp(cf_pair_value(cp), "aka-prime") == 0) {
                                cf_log_debug(conf, "Setting 'prefer_aka_prime = yes', as EAP-AKA-Prime is enabled");
-                               inst->send_at_bidding_prefer_prime = true;
+                               inst->aka.send_at_bidding_prefer_prime = true;
+                               inst->aka.send_at_bidding_prefer_prime_is_set = true;
                                break;
                        }
                }
@@ -179,20 +86,10 @@ static int mod_instantiate(void *instance, CONF_SECTION *conf)
        return 0;
 }
 
-/** Compile any virtual servers with the "eap-aka" namespace
- *
- */
-static int mod_namespace_load(CONF_SECTION *server_cs)
-{
-       return mod_section_compile(NULL, server_cs);
-}
-
 static int mod_load(void)
 {
        if (fr_aka_sim_init() < 0) return -1;
 
-       if (virtual_namespace_register("eap-aka", dict_eap_aka_sim, mod_namespace_load) < 0) return -1;
-
        fr_aka_sim_xlat_register();
 
        return 0;
@@ -209,15 +106,14 @@ static void mod_unload(void)
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
  */
-extern rlm_eap_submodule_t rlm_eap_aka;
 rlm_eap_submodule_t rlm_eap_aka = {
        .name           = "eap_aka",
        .magic          = RLM_MODULE_INIT,
 
        .provides       = { FR_EAP_METHOD_AKA },
 
-       .inst_size      = sizeof(eap_aka_sim_common_conf_t),
-       .inst_type      = "eap_aka_sim_common_conf_t",
+       .inst_size      = sizeof(eap_aka_sim_module_conf_t),
+       .inst_type      = "eap_aka_sim_module_conf_t",
        .config         = submodule_config,
 
        .onload         = mod_load,
@@ -225,6 +121,7 @@ rlm_eap_submodule_t rlm_eap_aka = {
 
        .instantiate    = mod_instantiate,
 
-       .session_init   = aka_sim_state_machine_start,  /* Initialise a new EAP session */
+       .session_init   = mod_session_init,
+       .entry_point    = eap_aka_sim_process,
        .namespace      = &dict_eap_aka_sim
 };
index c914b1e3a38a57173e8cc955d442659ade206bbb..32e9a5def497473bf181112fe347133ba5765b87 100644 (file)
@@ -21,6 +21,7 @@
  *
  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  *
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  * @copyright 2019 The FreeRADIUS server project
  * @copyright 2019 Network RADIUS SARL <sales@networkradius.com>
  */
@@ -28,151 +29,46 @@ RCSID("$Id$")
 
 #include <freeradius-devel/eap/base.h>
 #include <freeradius-devel/eap_aka_sim/attrs.h>
-#include <freeradius-devel/eap_aka_sim/state_machine.h>
-#include <freeradius-devel/unlang/compile.h>
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/module.h>
 #include <freeradius-devel/unlang/module.h>
 #include <freeradius-devel/util/debug.h>
 
 static CONF_PARSER submodule_config[] = {
-       { FR_CONF_OFFSET("network_name", FR_TYPE_STRING, eap_aka_sim_common_conf_t, network_name ) },
-       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ),
-         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
-       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t,
-                        strip_permanent_identity_hint ), .dflt = "yes" },
-       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */
-       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" },
-       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse },
-       CONF_PARSER_TERMINATOR
-};
+       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse },
 
-static virtual_server_compile_t compile_list[] = {
-       /*
-        *      Identity negotiation
-        */
-       EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"),
-       EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"),
-
-       /*
-        *      Optional override sections if the user *really*
-        *      wants to apply special policies for subsequent
-        *      request/response rounds.
-        */
-       EAP_SECTION_DEFINE(aka.send_aka_identity_request, "send", "AKA-Identity-Request"),
-       EAP_SECTION_DEFINE(aka.recv_aka_identity_response, "recv", "AKA-Identity-Response"),
-
-       /*
-        *      Full-Authentication
-        */
-       EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"),
-       EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"),
-
-       /*
-        *      Fast-Re-Authentication
-        */
-       EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"),
-       EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"),
-
-       /*
-        *      Failures originating from the supplicant
-        */
-       EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"),
-       EAP_SECTION_DEFINE(aka.recv_authentication_reject, "recv", "Authentication-Reject"),
-       EAP_SECTION_DEFINE(aka.recv_syncronization_failure, "recv", "Syncronization-Failure"),
-
-       /*
-        *      Failure originating from the server
-        */
-       EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"),
-       EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"),
-
-       /*
-        *      Protected success indication
-        */
-       EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"),
-       EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"),
-
-       /*
-        *      Final EAP-Success and EAP-Failure messages
-        */
-       EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"),
-       EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"),
-
-       /*
-        *      Fast-Reauth vectors
-        */
-       EAP_SECTION_DEFINE(store_session, "store", "session"),
-       EAP_SECTION_DEFINE(load_session, "load", "session"),
-       EAP_SECTION_DEFINE(clear_session, "clear", "session"),
-
-       /*
-        *      Pseudonym processing
-        */
-       EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"),
-       EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"),
-       EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"),
-
-       COMPILE_TERMINATOR
+       CONF_PARSER_TERMINATOR
 };
 
+extern rlm_eap_submodule_t rlm_eap_aka_prime;
 
-/** Compile virtual server sections
- *
- * Called twice, once when a server with an eap-aka namespace is found, and once
- * when an EAP-AKA module is instantiated.
- *
- * The first time is with actions == NULL and is to compile the sections and
- * perform validation.
- * The second time is to write out pointers to the compiled sections which the
- * EAP-AKA module will use to execute unlang code.
- *
- */
-static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs)
+static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       int found;
-       tmpl_rules_t parse_rules;
-
-       if (!fr_cond_assert(server_cs)) return -1;
-
-       memset(&parse_rules, 0, sizeof(parse_rules));
-       parse_rules.dict_def = dict_eap_aka_sim;
+       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_mod_session_t       *mod_session;
 
-       found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions);
-       if (found < 0) return -1;
+       MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t));
+       mod_session->id = (uint8_t)(fr_rand() & 0xff);
+       mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha256();
+       eap_session->opaque = mod_session;
+       eap_session->process = eap_aka_sim_process;
 
-       /*
-        *      Warn if we couldn't find any actions.
-        */
-       if (!found) {
-               cf_log_warn(server_cs, "No \"eap-aka-prime\" actions found in virtual server \"%s\"",
-                           cf_section_name2(server_cs));
-       }
-
-       return 0;
+       return eap_session->process(p_result, mctx, request);
 }
 
 static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t);
+       eap_aka_sim_module_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t);
 
-       if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1;
+       inst->type = rlm_eap_aka_prime.provides[0];
 
        return 0;
 }
 
-/** Compile any virtual servers with the "eap-aka" namespace
- *
- */
-static int mod_namespace_load(CONF_SECTION *server_cs)
-{
-       return mod_section_compile(NULL, server_cs);
-}
-
 static int mod_load(void)
 {
        if (fr_aka_sim_init() < 0) return -1;
 
-       if (virtual_namespace_register("eap-aka-prime", dict_eap_aka_sim, mod_namespace_load) < 0) return -1;
-
        fr_aka_sim_xlat_register();
 
        return 0;
@@ -189,15 +85,14 @@ static void mod_unload(void)
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
  */
-extern rlm_eap_submodule_t rlm_eap_aka_prime;
 rlm_eap_submodule_t rlm_eap_aka_prime = {
        .name           = "eap_aka_prime",
        .magic          = RLM_MODULE_INIT,
 
        .provides       = { FR_EAP_METHOD_AKA_PRIME },
 
-       .inst_size      = sizeof(eap_aka_sim_common_conf_t),
-       .inst_type      = "eap_aka_sim_common_conf_t",
+       .inst_size      = sizeof(eap_aka_sim_module_conf_t),
+       .inst_type      = "eap_aka_sim_module_conf_t",
        .config         = submodule_config,
 
        .onload         = mod_load,
@@ -205,6 +100,7 @@ rlm_eap_submodule_t rlm_eap_aka_prime = {
 
        .instantiate    = mod_instantiate,
 
-       .session_init   = aka_sim_state_machine_start,  /* Initialise a new EAP session */
+       .session_init   = mod_session_init,
+       .entry_point    = eap_aka_sim_process,  /* Initialise a new EAP session */
        .namespace      = &dict_eap_aka_sim
 };
index b9efc963fa07217cb3ef990f340c10abf531f68a..b16b53d5006b14223d00eae875026ccbbf9e6e3a 100644 (file)
  *
  * @author Arran Cudbard-Bell <a.cudbardb@freeradius.org>
  *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  * @copyright 2016 The FreeRADIUS server project
- * @copyright 2016 Network RADIUS SARL <sales@networkradius.com>
+ * @copyright 2016 Network RADIUS SARL (sales@networkradius.com)
  */
 RCSID("$Id$")
 
 #include <freeradius-devel/eap/base.h>
 #include <freeradius-devel/eap_aka_sim/attrs.h>
-#include <freeradius-devel/eap_aka_sim/state_machine.h>
-#include <freeradius-devel/unlang/compile.h>
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/module.h>
 #include <freeradius-devel/unlang/module.h>
 #include <freeradius-devel/util/debug.h>
 
 static CONF_PARSER submodule_config[] = {
-       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_common_conf_t, request_identity ),
-         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
-       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_common_conf_t,
-                        strip_permanent_identity_hint ), .dflt = "yes" },
-       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_common_conf_t, ephemeral_id_length ), .dflt = "14" }, /* 14 for compatibility */
-       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_common_conf_t, protected_success ), .dflt = "no" },
-       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID | FR_TYPE_REQUIRED, eap_aka_sim_common_conf_t, virtual_server), .func = virtual_server_cf_parse },
+       { FR_CONF_OFFSET("virtual_server", FR_TYPE_VOID | FR_TYPE_REQUIRED, eap_aka_sim_module_conf_t, virtual_server), .func = virtual_server_cf_parse },
        CONF_PARSER_TERMINATOR
 };
 
-static virtual_server_compile_t compile_list[] = {
-       /*
-        *      Identity negotiation
-        */
-       EAP_SECTION_DEFINE(send_identity_request, "send", "Identity-Request"),
-       EAP_SECTION_DEFINE(recv_identity_response, "recv", "Identity-Response"),
-
-       /*
-        *      Optional override sections if the user *really*
-        *      wants to apply special policies for subsequent
-        *      request/response rounds.
-        */
-       EAP_SECTION_DEFINE(sim.send_sim_start_request, "send", "Start"),
-       EAP_SECTION_DEFINE(sim.recv_sim_start_response, "recv", "Start"),
-
-       /*
-        *      Full-Authentication
-        */
-       EAP_SECTION_DEFINE(send_challenge_request, "send", "Challenge-Request"),
-       EAP_SECTION_DEFINE(recv_challenge_response, "recv", "Challenge-Response"),
-
-       /*
-        *      Fast-Re-Authentication
-        */
-       EAP_SECTION_DEFINE(send_reauthentication_request, "send", "Reauthentication-Request"),
-       EAP_SECTION_DEFINE(recv_reauthentication_response, "recv", "Reauthentication-Response"),
-
-       /*
-        *      Failures originating from the supplicant
-        */
-       EAP_SECTION_DEFINE(recv_client_error, "recv", "Client-Error"),
-
-       /*
-        *      Failure originating from the server
-        */
-       EAP_SECTION_DEFINE(send_failure_notification, "send", "Failure-Notification"),
-       EAP_SECTION_DEFINE(recv_failure_notification_ack, "recv", "Failure-Notification-ACK"),
-
-       /*
-        *      Protected success indication
-        */
-       EAP_SECTION_DEFINE(send_success_notification, "send", "Success-Notification"),
-       EAP_SECTION_DEFINE(recv_success_notification_ack, "recv", "Success-Notification-ACK"),
-
-       /*
-        *      Final EAP-Success and EAP-Failure messages
-        */
-       EAP_SECTION_DEFINE(send_eap_success, "send", "EAP-Success"),
-       EAP_SECTION_DEFINE(send_eap_failure, "send", "EAP-Failure"),
-
-       /*
-        *      Fast-Reauth vectors
-        */
-       EAP_SECTION_DEFINE(store_session, "store", "session"),
-       EAP_SECTION_DEFINE(load_session, "load", "session"),
-       EAP_SECTION_DEFINE(clear_session, "clear", "session"),
-
-       /*
-        *      Pseudonym processing
-        */
-       EAP_SECTION_DEFINE(store_pseudonym, "store", "pseudonym"),
-       EAP_SECTION_DEFINE(load_pseudonym, "load", "pseudonym"),
-       EAP_SECTION_DEFINE(clear_pseudonym, "clear", "pseudonym"),
-
-       COMPILE_TERMINATOR
-};
+extern rlm_eap_submodule_t rlm_eap_sim;
 
-/** Compile virtual server sections
- *
- * Called twice, once when a server with an eap-aka namespace is found, and once
- * when an EAP-AKA module is instantiated.
- *
- * The first time is with actions == NULL and is to compile the sections and
- * perform validation.
- * The second time is to write out pointers to the compiled sections which the
- * EAP-AKA module will use to execute unlang code.
- *
- */
-static int mod_section_compile(eap_aka_sim_actions_t *actions, CONF_SECTION *server_cs)
+static unlang_action_t mod_session_init(rlm_rcode_t *p_result, module_ctx_t const *mctx, request_t *request)
 {
-       int found;
-       tmpl_rules_t parse_rules;
-
-       if (!fr_cond_assert(server_cs)) return -1;
-
-       memset(&parse_rules, 0, sizeof(parse_rules));
-       parse_rules.dict_def = dict_eap_aka_sim;
-
-       found = virtual_server_compile_sections(server_cs, compile_list, &parse_rules, actions);
-       if (found < 0) return -1;
+       eap_session_t                   *eap_session = eap_session_get(request->parent);
+       eap_aka_sim_mod_session_t       *mod_session;
 
-       /*
-        *      Warn if we couldn't find any actions.
-        */
-       if (!found) {
-               cf_log_warn(server_cs, "No \"eap-sim\" actions found in virtual server \"%s\"",
-                           cf_section_name2(server_cs));
-       }
+       MEM(mod_session = talloc_zero(eap_session, eap_aka_sim_mod_session_t));
+       mod_session->id = (uint8_t)(fr_rand() & 0xff);
+       mod_session->ctx.hmac_md = mod_session->ctx.checkcode_md = EVP_sha1();
+       eap_session->opaque = mod_session;
+       eap_session->process = eap_aka_sim_process;
 
-       return 0;
+       return eap_session->process(p_result, mctx, request);
 }
 
 static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
 {
-       eap_aka_sim_common_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_common_conf_t);
+       eap_aka_sim_module_conf_t       *inst = talloc_get_type_abort(instance, eap_aka_sim_module_conf_t);
 
-       if (mod_section_compile(&inst->actions, inst->virtual_server) < 0) return -1;
+       inst->type = rlm_eap_sim.provides[0];
 
        return 0;
 }
-
-/** Compile any virtual servers with the "eap-aka" namespace
- *
- */
-static int mod_namespace_load(CONF_SECTION *server_cs)
-{
-       return mod_section_compile(NULL, server_cs);
-}
-
 static int mod_load(void)
 {
        if (fr_aka_sim_init() < 0) return -1;
 
-       if (virtual_namespace_register("eap-sim", dict_eap_aka_sim, mod_namespace_load) < 0) return -1;
-
        fr_aka_sim_xlat_register();
 
        return 0;
@@ -185,15 +83,14 @@ static void mod_unload(void)
  *     The module name should be the only globally exported symbol.
  *     That is, everything else should be 'static'.
  */
-extern rlm_eap_submodule_t rlm_eap_sim;
 rlm_eap_submodule_t rlm_eap_sim = {
        .name           = "eap_sim",
        .magic          = RLM_MODULE_INIT,
 
        .provides       = { FR_EAP_METHOD_SIM },
 
-       .inst_size      = sizeof(eap_aka_sim_common_conf_t),
-       .inst_type      = "eap_aka_sim_common_conf_t",
+       .inst_size      = sizeof(eap_aka_sim_module_conf_t),
+       .inst_type      = "eap_aka_sim_module_conf_t",
        .config         = submodule_config,
 
        .onload         = mod_load,
@@ -201,6 +98,7 @@ rlm_eap_submodule_t rlm_eap_sim = {
 
        .instantiate    = mod_instantiate,
 
-       .session_init   = aka_sim_state_machine_start,  /* Initialise a new EAP session */
+       .session_init   = mod_session_init,     /* Initialise a new EAP session */
+       .entry_point    = eap_aka_sim_process,
        .namespace      = &dict_eap_aka_sim
 };
diff --git a/src/process/eap_aka/all.mk b/src/process/eap_aka/all.mk
new file mode 100644 (file)
index 0000000..efed1c1
--- /dev/null
@@ -0,0 +1,10 @@
+TARGETNAME     := process_eap_aka
+
+ifneq "$(OPENSSL_LIBS)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := base.c
+
+TGT_PREREQS    := libfreeradius-util.a libfreeradius-eap-aka-sim.a
+
diff --git a/src/process/eap_aka/base.c b/src/process/eap_aka/base.c
new file mode 100644 (file)
index 0000000..62c8ac9
--- /dev/null
@@ -0,0 +1,274 @@
+/*
+ *   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 src/process/eap_aka/base.c
+ * @brief EAP-AKA process module
+ *
+ * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods
+ * and is in src/lib/eap_aka_sim/state_machine.c
+ *
+ * The process modules for the different EAP methods just define the sections
+ * for that EAP method, and parse different config items.
+ *
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/attrs.h>
+#include <freeradius-devel/eap_aka_sim/state_machine.h>
+#include <freeradius-devel/server/virtual_servers.h>
+#include <freeradius-devel/server/process.h>
+
+static CONF_PARSER submodule_config[] = {
+       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ),
+         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
+       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t,
+                        strip_permanent_identity_hint ), .dflt = "yes" },
+       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" },        /* 14 for compatibility */
+       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" },
+
+       CONF_PARSER_TERMINATOR
+};
+
+static virtual_server_compile_t const compile_list[] = {
+       /*
+        *      Identity negotiation
+        *      The initial identity here is the EAP-Identity.
+        *      We can then choose to request additional
+        *      identities.
+        */
+       {
+               .name = "recv",
+               .name2 = "Identity-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response)
+       },
+       {
+               .name = "send",
+               .name2 = "Identity-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request)
+       },
+
+       /*
+        *      Optional override sections if the user *really*
+        *      wants to apply special policies for subsequent
+        *      request/response rounds.
+        */
+       {
+               .name = "send",
+               .name2 = "AKA-Identity-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_identity_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "AKA-Identity-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_identity_response)
+       },
+
+       /*
+        *      Full-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Challenge-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_challenge_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Challenge-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_challenge_response)
+       },
+
+       /*
+        *      Fast-Re-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Reauthentication-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Reauthentication-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response)
+       },
+
+       /*
+        *      Failures originating from the supplicant
+        */
+       {
+               .name = "recv",
+               .name2 = "Client-Error",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error)
+       },
+       {
+               .name = "recv",
+               .name2 = "Authentication-Reject",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_authentication_reject)
+       },
+       {
+               .name = "recv",
+               .name2 = "Syncronization-Failure",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_syncronization_failure)
+       },
+
+       /*
+        *      Failure originating from the server
+        */
+       {
+               .name = "send",
+               .name2 = "Failure-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Failure-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack)
+       },
+
+       /*
+        *      Protected success indication
+        */
+       {
+               .name = "send",
+               .name2 = "Success-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Success-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack)
+       },
+
+       /*
+        *      Final EAP-Success and EAP-Failure messages
+        */
+       {
+               .name = "send",
+               .name2 = "EAP-Success",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success)
+       },
+       {
+               .name = "send",
+               .name2 = "EAP-Failure",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure)
+       },
+
+       /*
+        *      Fast-Reauth vectors
+        */
+       {
+               .name = "store",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session)
+       },
+       {
+               .name = "load",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session)
+       },
+       {
+               .name = "clear",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session)
+       },
+
+       /*
+        *      Pseudonym processing
+        */
+       {
+               .name = "store",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym)
+       },
+       {
+               .name = "load",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym)
+       },
+       {
+               .name = "clear",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym)
+       },
+
+       COMPILE_TERMINATOR
+};
+
+static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t);
+
+       inst->type = FR_EAP_METHOD_AKA;
+
+       return 0;
+}
+
+static int mod_load(void)
+{
+       if (unlikely(fr_aka_sim_init() < 0)) return -1;
+
+       fr_aka_sim_xlat_register();
+
+       return 0;
+}
+
+static void mod_unload(void)
+{
+       fr_aka_sim_xlat_unregister();
+
+       fr_aka_sim_free();
+}
+
+extern fr_process_module_t process_eap_aka;
+fr_process_module_t process_eap_aka = {
+       .magic          = RLM_MODULE_INIT,
+       .name           = "process_eap_aka",
+       .onload         = mod_load,
+       .unload         = mod_unload,
+       .config         = submodule_config,
+       .instantiate    = mod_instantiate,
+       .inst_size      = sizeof(eap_aka_sim_process_conf_t),
+       .inst_type      = "eap_aka_sim_process_conf_t",
+
+       .process        = eap_aka_sim_state_machine_process,
+       .compile_list   = compile_list,
+       .dict           = &dict_eap_aka_sim,
+};
diff --git a/src/process/eap_aka_prime/all.mk b/src/process/eap_aka_prime/all.mk
new file mode 100644 (file)
index 0000000..08b8bd7
--- /dev/null
@@ -0,0 +1,10 @@
+TARGETNAME     := process_eap_aka_prime
+
+ifneq "$(OPENSSL_LIBS)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := base.c
+
+TGT_PREREQS    := libfreeradius-util.a libfreeradius-eap-aka-sim.a
+
diff --git a/src/process/eap_aka_prime/base.c b/src/process/eap_aka_prime/base.c
new file mode 100644 (file)
index 0000000..b426dfa
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ *   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 src/process/eap_aka_prime/base.c
+ * @brief EAP-AKA' process module
+ *
+ * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods
+ * and is in src/lib/eap_aka_sim/state_machine.c
+ *
+ * The process modules for the different EAP methods just define the sections
+ * for that EAP method, and parse different config items.
+ *
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/attrs.h>
+#include <freeradius-devel/eap_aka_sim/state_machine.h>
+#include <freeradius-devel/server/virtual_servers.h>
+#include <freeradius-devel/server/process.h>
+
+static CONF_PARSER submodule_config[] = {
+       { FR_CONF_OFFSET("network_name", FR_TYPE_STRING, eap_aka_sim_process_conf_t, network_name ) },
+       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ),
+         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
+       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t,
+                        strip_permanent_identity_hint ), .dflt = "yes" },
+       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" },        /* 14 for compatibility */
+       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" },
+
+       CONF_PARSER_TERMINATOR
+};
+
+static virtual_server_compile_t const compile_list[] = {
+       /*
+        *      Identity negotiation
+        *      The initial identity here is the EAP-Identity.
+        *      We can then choose to request additional
+        *      identities.
+        */
+       {
+               .name = "recv",
+               .name2 = "Identity-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response)
+       },
+       {
+               .name = "send",
+               .name2 = "Identity-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request)
+       },
+
+       /*
+        *      Optional override sections if the user *really*
+        *      wants to apply special policies for subsequent
+        *      request/response rounds.
+        */
+       {
+               .name = "send",
+               .name2 = "AKA-Identity-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_identity_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "AKA-Identity-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_identity_response)
+       },
+
+       /*
+        *      Full-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Challenge-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_aka_challenge_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Challenge-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_challenge_response)
+       },
+
+       /*
+        *      Fast-Re-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Reauthentication-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Reauthentication-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response)
+       },
+
+       /*
+        *      Failures originating from the supplicant
+        */
+       {
+               .name = "recv",
+               .name2 = "Client-Error",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error)
+       },
+       {
+               .name = "recv",
+               .name2 = "Authentication-Reject",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_authentication_reject)
+       },
+       {
+               .name = "recv",
+               .name2 = "Syncronization-Failure",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_aka_syncronization_failure)
+       },
+
+       /*
+        *      Failure originating from the server
+        */
+       {
+               .name = "send",
+               .name2 = "Failure-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Failure-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack)
+       },
+
+       /*
+        *      Protected success indication
+        */
+       {
+               .name = "send",
+               .name2 = "Success-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Success-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack)
+       },
+
+       /*
+        *      Final EAP-Success and EAP-Failure messages
+        */
+       {
+               .name = "send",
+               .name2 = "EAP-Success",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success)
+       },
+       {
+               .name = "send",
+               .name2 = "EAP-Failure",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure)
+       },
+
+       /*
+        *      Fast-Reauth vectors
+        */
+       {
+               .name = "store",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session)
+       },
+       {
+               .name = "load",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session)
+       },
+       {
+               .name = "clear",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session)
+       },
+
+       /*
+        *      Pseudonym processing
+        */
+       {
+               .name = "store",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym)
+       },
+       {
+               .name = "load",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym)
+       },
+       {
+               .name = "clear",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym)
+       },
+
+       COMPILE_TERMINATOR
+};
+
+static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t);
+
+       inst->type = FR_EAP_METHOD_AKA_PRIME;
+
+       return 0;
+}
+
+static int mod_load(void)
+{
+       if (unlikely(fr_aka_sim_init() < 0)) return -1;
+
+       fr_aka_sim_xlat_register();
+
+       return 0;
+}
+
+static void mod_unload(void)
+{
+       fr_aka_sim_xlat_unregister();
+
+       fr_aka_sim_free();
+}
+
+extern fr_process_module_t process_eap_aka_prime;
+fr_process_module_t process_eap_aka_prime = {
+       .magic          = RLM_MODULE_INIT,
+       .name           = "process_eap_aka_prime",
+       .onload         = mod_load,
+       .unload         = mod_unload,
+       .config         = submodule_config,
+       .instantiate    = mod_instantiate,
+       .inst_size      = sizeof(eap_aka_sim_process_conf_t),
+       .inst_type      = "eap_aka_sim_process_conf_t",
+
+       .process        = eap_aka_sim_state_machine_process,
+       .compile_list   = compile_list,
+       .dict           = &dict_eap_aka_sim,
+};
diff --git a/src/process/eap_sim/all.mk b/src/process/eap_sim/all.mk
new file mode 100644 (file)
index 0000000..eac5be8
--- /dev/null
@@ -0,0 +1,10 @@
+TARGETNAME     := process_eap_sim
+
+ifneq "$(OPENSSL_LIBS)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := base.c
+
+TGT_PREREQS    := libfreeradius-util.a libfreeradius-eap-aka-sim.a
+
diff --git a/src/process/eap_sim/base.c b/src/process/eap_sim/base.c
new file mode 100644 (file)
index 0000000..563fa2e
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ *   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 src/process/eap_sim/base.c
+ * @brief EAP-SIM process module
+ *
+ * The state machine for EAP-SIM, EAP-AKA and EAP-AKA' is common to all methods
+ * and is in src/lib/eap_aka_sim/state_machine.c
+ *
+ * The process modules for the different EAP methods just define the sections
+ * for that EAP method, and parse different config items.
+ *
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+
+#include <freeradius-devel/eap_aka_sim/base.h>
+#include <freeradius-devel/eap_aka_sim/attrs.h>
+#include <freeradius-devel/eap_aka_sim/state_machine.h>
+#include <freeradius-devel/server/virtual_servers.h>
+#include <freeradius-devel/server/process.h>
+
+static CONF_PARSER submodule_config[] = {
+       { FR_CONF_OFFSET("request_identity", FR_TYPE_UINT32, eap_aka_sim_process_conf_t, request_identity ),
+         .func = cf_table_parse_uint32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_aka_sim_id_request_table, .len = &fr_aka_sim_id_request_table_len }},
+       { FR_CONF_OFFSET("strip_permanent_identity_hint", FR_TYPE_BOOL, eap_aka_sim_process_conf_t,
+                        strip_permanent_identity_hint ), .dflt = "yes" },
+       { FR_CONF_OFFSET("ephemeral_id_length", FR_TYPE_SIZE, eap_aka_sim_process_conf_t, ephemeral_id_length ), .dflt = "14" },        /* 14 for compatibility */
+       { FR_CONF_OFFSET("protected_success", FR_TYPE_BOOL, eap_aka_sim_process_conf_t, protected_success ), .dflt = "no" },
+
+       CONF_PARSER_TERMINATOR
+};
+
+static virtual_server_compile_t compile_list[] = {
+       /*
+        *      Identity negotiation
+        *      The initial identity here is the EAP-Identity.
+        *      We can then choose to request additional
+        *      identities.
+        */
+       {
+               .name = "recv",
+               .name2 = "Identity-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_identity_response)
+       },
+       {
+               .name = "send",
+               .name2 = "Identity-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_identity_request)
+       },
+
+       /*
+        *      Optional override sections if the user *really*
+        *      wants to apply special policies for subsequent
+        *      request/response rounds.
+        */
+       {
+               .name = "send",
+               .name2 = "Start",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_sim_start_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Start",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_sim_start_response)
+       },
+
+       /*
+        *      Full-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Challenge-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_sim_challenge_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Challenge-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_sim_challenge_response)
+       },
+
+       /*
+        *      Fast-Re-Authentication
+        */
+       {
+               .name = "send",
+               .name2 = "Reauthentication-Request",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_reauthentication_request)
+       },
+       {
+               .name = "recv",
+               .name2 = "Reauthentication-Response",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_reauthentication_response)
+       },
+
+       /*
+        *      Failures originating from the supplicant
+        */
+       {
+               .name = "recv",
+               .name2 = "Client-Error",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_client_error)
+       },
+
+       /*
+        *      Failure originating from the server
+        */
+       {
+               .name = "send",
+               .name2 = "Failure-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_failure_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Failure-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_failure_notification_ack)
+       },
+
+       /*
+        *      Protected success indication
+        */
+       {
+               .name = "send",
+               .name2 = "Success-Notification",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_common_success_notification)
+       },
+       {
+               .name = "recv",
+               .name2 = "Success-Notification-ACK",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.recv_common_success_notification_ack)
+       },
+
+       /*
+        *      Final EAP-Success and EAP-Failure messages
+        */
+       {
+               .name = "send",
+               .name2 = "EAP-Success",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_success)
+       },
+       {
+               .name = "send",
+               .name2 = "EAP-Failure",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.send_eap_failure)
+       },
+
+       /*
+        *      Fast-Reauth vectors
+        */
+       {
+               .name = "store",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_session)
+       },
+       {
+               .name = "load",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_session)
+       },
+       {
+               .name = "clear",
+               .name2 = "session",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_session)
+       },
+
+       /*
+        *      Pseudonym processing
+        */
+       {
+               .name = "store",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.store_pseudonym)
+       },
+       {
+               .name = "load",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.load_pseudonym)
+       },
+       {
+               .name = "clear",
+               .name2 = "pseudonym",
+               .component = MOD_AUTHORIZE,
+               .offset = offsetof(eap_aka_sim_process_conf_t, actions.clear_pseudonym)
+       },
+
+       COMPILE_TERMINATOR
+};
+
+static int mod_instantiate(void *instance, UNUSED CONF_SECTION *conf)
+{
+       eap_aka_sim_process_conf_t      *inst = talloc_get_type_abort(instance, eap_aka_sim_process_conf_t);
+
+       inst->type = FR_EAP_METHOD_SIM;
+
+       return 0;
+}
+
+static int mod_load(void)
+{
+       if (unlikely(fr_aka_sim_init() < 0)) return -1;
+
+       fr_aka_sim_xlat_register();
+
+       return 0;
+}
+
+static void mod_unload(void)
+{
+       fr_aka_sim_xlat_unregister();
+
+       fr_aka_sim_free();
+}
+
+extern fr_process_module_t process_eap_sim;
+fr_process_module_t process_eap_sim = {
+       .magic          = RLM_MODULE_INIT,
+       .name           = "process_eap_sim",
+       .onload         = mod_load,
+       .unload         = mod_unload,
+       .config         = submodule_config,
+       .instantiate    = mod_instantiate,
+       .inst_size      = sizeof(eap_aka_sim_process_conf_t),
+       .inst_type      = "eap_aka_sim_process_conf_t",
+
+       .process        = eap_aka_sim_state_machine_process,
+       .compile_list   = compile_list,
+       .dict           = &dict_eap_aka_sim,
+};
index 47df1bf44b672c745ea8b1a4ee0878da330b690f..f751c59d38ae3c053171abaf7f889aed7b74f60b 100644 (file)
@@ -2,6 +2,5 @@ type = aka_prime
 default_eap_type = aka-prime
 
 aka_prime {
-       network_name = "testing123"
        virtual_server = "eap-aka-prime"
 }
index 876097648537f80112600dd272d5b9dedca16460..3135d3bf345a4dfe3ee026daff0917be064abd97 100644 (file)
@@ -1,6 +1,10 @@
 server eap-aka-prime {
        namespace = eap-aka-prime
 
+       eap-aka-prime {
+               network_name = "testing123"
+       }
+
        recv Identity-Response {
                "%(debug_attr:&session-state.)"
                if (!&session-state.Tmp-String-0) {
index da4fd3ac8ea60817714294965b663c6cd5037df2..8b184ac36d1c664895af6a737b9c1cc8707b938a 100644 (file)
@@ -2,6 +2,5 @@ type = aka
 default_eap_type = aka
 
 aka {
-       network_name = "testing123"
        virtual_server = "eap-aka"
 }
index a3e422ac6302faac26337d6e7fb27fefe9807358..01fc66a9de78d22d3990aa5c24c241ea7b19d382 100644 (file)
@@ -2,6 +2,10 @@
 server eap-aka {
        namespace = eap-aka
 
+       eap-aka {
+               network_name = "testing123"
+       }
+
        recv Identity-Response {
                "%(debug_attr:&session-state.)"
                if (!&session-state.Tmp-String-0) {